Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<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">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
<version>4.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>it.aboutbits</groupId>
<artifactId>archunit-toolbox</artifactId>
<version>1.1.0</version>
<version>1.2.0-RC1</version>
<description>Common ArchUnit tooling for Java / Spring Boot projects.</description>

<properties>
<java.version>25</java.version>
<errorprone.version>2.45.0</errorprone.version>
<nullaway.version>0.12.14</nullaway.version>
<errorprone.version>2.49.0</errorprone.version>
<nullaway.version>0.13.4</nullaway.version>
</properties>

<dependencies>
Expand All @@ -37,18 +38,18 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.14.2</version>
<version>5.14.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.tngtech.archunit/archunit-junit5 -->
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5-api</artifactId>
<version>1.4.1</version>
<version>1.4.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
Expand All @@ -58,7 +59,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
<version>3.15.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
Expand Down Expand Up @@ -124,7 +125,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>12.3.0</version>
<version>13.4.1</version>
</dependency>
<dependency>
<groupId>it.aboutbits</groupId>
Expand Down
530 changes: 0 additions & 530 deletions src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package it.aboutbits.archunit.toolbox;

import it.aboutbits.archunit.toolbox.rule.base.BlacklistAnnotationsArchRule;
import it.aboutbits.archunit.toolbox.rule.base.BlacklistClassesArchRule;
import it.aboutbits.archunit.toolbox.rule.base.BlacklistMethodsArchRule;
import it.aboutbits.archunit.toolbox.rule.base.EnforceJspecifyArchRule;
import it.aboutbits.archunit.toolbox.rule.base.NoSystemOutOrErrArchRule;
import it.aboutbits.archunit.toolbox.rule.base.RecordPropertiesMustBeAccessedViaAccessorArchRule;
import it.aboutbits.archunit.toolbox.rule.base.TestClassInCorrectPackageArchRule;
import it.aboutbits.archunit.toolbox.rule.base.TestClassVisibilityArchRule;
import it.aboutbits.archunit.toolbox.rule.base.TestMethodVisibilityArchRule;
import it.aboutbits.archunit.toolbox.rule.base.TestNestedClassMatchNameArchRule;
import it.aboutbits.archunit.toolbox.rule.base.TestNestedClassVisibilityArchRule;
import org.jspecify.annotations.NullMarked;

@NullMarked
public interface BaseArchRuleCollection extends
BlacklistAnnotationsArchRule,
BlacklistClassesArchRule,
BlacklistMethodsArchRule,
EnforceJspecifyArchRule,
NoSystemOutOrErrArchRule,
RecordPropertiesMustBeAccessedViaAccessorArchRule,
TestClassInCorrectPackageArchRule,
TestClassVisibilityArchRule,
TestMethodVisibilityArchRule,
TestNestedClassMatchNameArchRule,
TestNestedClassVisibilityArchRule {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package it.aboutbits.archunit.toolbox;

import it.aboutbits.archunit.toolbox.rule.common.ControllerRequestMappingsMustBeSecurityTested;
import it.aboutbits.archunit.toolbox.rule.common.SortMappingsExhaustiveArchRule;
import org.jspecify.annotations.NullMarked;

@NullMarked
public interface CommonArchRuleCollection extends
ControllerRequestMappingsMustBeSecurityTested,
SortMappingsExhaustiveArchRule {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package it.aboutbits.archunit.toolbox.config;

import org.jspecify.annotations.NullMarked;

import java.util.HashSet;
import java.util.Set;

@NullMarked
public final class ArchRuleConfig {
private ArchRuleConfig() {
}

/**
* List of supported test class name suffixes.
* <p>
* When introducing a new test type (e.g. IntegrationTest), add its suffix here
* instead of directly modifying the regex pattern.
**/
public static final Set<String> TEST_CLASS_SUFFIXES = new HashSet<>(
Set.of(
"Test",
"CacheTest",
"EventTest",
"SecurityTest"
)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package it.aboutbits.archunit.toolbox.rule.base;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import org.jspecify.annotations.NullMarked;

import java.util.HashSet;
import java.util.Set;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
@NullMarked
public interface BlacklistAnnotationsArchRule {
@SuppressWarnings("java:S2386")
Set<String> BLACKLISTED_ANNOTATIONS = new HashSet<>(
Set.of(
"org.junit.After",
"org.junit.AfterClass",
"org.junit.Before",
"org.junit.BeforeClass",
"org.junit.ClassRule",
"org.junit.FixMethodOrder",
"org.junit.Ignore",
"org.junit.Rule",
"org.junit.Test",
// @NonNull (allowed is only org.jspecify.annotations.NonNull)
"lombok.NonNull",
"edu.umd.cs.findbugs.annotations.NonNull",
"io.micrometer.common.lang.NonNull",
"io.micrometer.core.lang.NonNull",
"org.springframework.lang.NonNull",
"org.testcontainers.shaded.org.checkerframework.checker.nullness.qual.NonNull",
// @NotNull (allowed is only jakarta.validation.constraints.NotNull)
"com.drew.lang.annotations.NotNull",
"com.sun.istack.NotNull",
"org.antlr.v4.runtime.misc.NotNull",
"org.jetbrains.annotations.NotNull",
"software.amazon.awssdk.annotations.NotNull",
// @Nullable (allowed is only org.jspecify.annotations.Nullable)
"org.springframework.lang.Nullable",
"com.drew.lang.annotations.Nullable",
"com.sun.istack.Nullable",
"edu.umd.cs.findbugs.annotations.Nullable",
"io.micrometer.common.lang.Nullable",
"io.micrometer.core.lang.Nullable",
"jakarta.annotation.Nullable",
"javax.annotation.Nullable",
"org.jetbrains.annotations.Nullable",
"org.testcontainers.shaded.org.checkerframework.checker.nullness.qual.Nullable",
// @Transactional (allowed is only org.springframework.transaction.annotation.Transactional)
"jakarta.transaction.Transactional"
)
);

@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
@ArchTest
default void no_blacklisted_annotations_are_used(JavaClasses classes) {
classes()
.should(new NotUseBlacklistedAnnotations())
.check(classes);
}

class NotUseBlacklistedAnnotations extends ArchCondition<JavaClass> {
public NotUseBlacklistedAnnotations() {
super("not use blacklisted annotations on classes, methods, method parameters, or fields");
}

@Override
public void check(JavaClass javaClass, ConditionEvents events) {
// Check annotations on the class itself
for (var annotation : javaClass.getAnnotations()) {
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
var message = String.format(
"Class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
javaClass.getFullName(),
annotation.getRawType().getFullName(),
javaClass.getSimpleName(),
javaClass.getSourceCodeLocation().getLineNumber()
);
events.add(SimpleConditionEvent.violated(javaClass, message));
}
}

// Check annotations on methods and their parameters
for (var method : javaClass.getMethods()) {
// Check method annotations
for (var annotation : method.getAnnotations()) {
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
var message = String.format(
"Method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
method.getFullName(),
annotation.getRawType().getFullName(),
javaClass.getSimpleName(),
method.getSourceCodeLocation().getLineNumber()
);
events.add(SimpleConditionEvent.violated(method, message));
}
}
// Check method parameter annotations
for (var parameter : method.getParameters()) {
for (var annotation : parameter.getAnnotations()) {
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
var message = String.format(
"Parameter %s of method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
parameter.getIndex(),
method.getFullName(),
annotation.getRawType().getFullName(),
javaClass.getSimpleName(),
method.getSourceCodeLocation().getLineNumber()
); // Parameter doesn't have its own SLOC, use method's
events.add(SimpleConditionEvent.violated(parameter, message));
}
}
}
}

// Check annotations on fields (ArchUnit includes record components as fields)
for (var field : javaClass.getFields()) {
for (var annotation : field.getAnnotations()) {
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
var message = String.format(
"Field %s in class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
field.getName(),
javaClass.getFullName(),
annotation.getRawType().getFullName(),
javaClass.getSimpleName(),
field.getSourceCodeLocation().getLineNumber()
);
events.add(SimpleConditionEvent.violated(field, message));
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package it.aboutbits.archunit.toolbox.rule.base;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.junit.ArchTest;
import org.jspecify.annotations.NullMarked;

import java.util.HashSet;
import java.util.Set;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
@NullMarked
public interface BlacklistClassesArchRule {
@SuppressWarnings("java:S2386")
Set<String> BLACKLISTED_CLASSES = new HashSet<>(
Set.of(
// use FakerExtended from toolbox
"net.datafaker.Faker"
)
);

@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
@ArchTest
default void no_blacklisted_classes_are_used(JavaClasses classes) {
noClasses()
.should()
.dependOnClassesThat(
new DescribedPredicate<>("not use blacklisted classes") {
@Override
public boolean test(JavaClass javaClass) {
return BLACKLISTED_CLASSES.contains(javaClass.getFullName());
}
}
)
.check(classes);
}
}
Loading