Skip to content

Commit 4a0206e

Browse files
committed
update rules and add new
1 parent 44b0829 commit 4a0206e

7 files changed

Lines changed: 147 additions & 1 deletion

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import it.aboutbits.archunit.toolbox.rule.base.BlacklistClassesArchRule;
55
import it.aboutbits.archunit.toolbox.rule.base.BlacklistMethodsArchRule;
66
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;
79
import it.aboutbits.archunit.toolbox.rule.base.TestClassInCorrectPackageArchRule;
810
import it.aboutbits.archunit.toolbox.rule.base.TestClassVisibilityArchRule;
911
import it.aboutbits.archunit.toolbox.rule.base.TestMethodVisibilityArchRule;
@@ -17,6 +19,8 @@ public interface BaseArchRuleCollection extends
1719
BlacklistClassesArchRule,
1820
BlacklistMethodsArchRule,
1921
EnforceJspecifyArchRule,
22+
NoSystemOutOrErrArchRule,
23+
RecordPropertiesMustBeAccessedViaAccessorArchRule,
2024
TestClassInCorrectPackageArchRule,
2125
TestClassVisibilityArchRule,
2226
TestMethodVisibilityArchRule,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import it.aboutbits.archunit.toolbox.rule.common.ControllerRequestMappingsMustBeSecurityTested;
44
import it.aboutbits.archunit.toolbox.rule.common.SortMappingsExhaustiveArchRule;
5+
import org.jspecify.annotations.NullMarked;
56

7+
@NullMarked
68
public interface CommonArchRuleCollection extends
79
ControllerRequestMappingsMustBeSecurityTested,
810
SortMappingsExhaustiveArchRule {

src/main/java/it/aboutbits/archunit/toolbox/rule/base/BlacklistMethodsArchRule.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public interface BlacklistMethodsArchRule {
2121
Set.of(
2222
// We should use `assertThatExceptionOfType(...).isThrownBy(...)` instead of `assertThatThrownBy(...)`
2323
"org.assertj.core.api.Assertions.assertThatThrownBy",
24+
"org.assertj.core.api.Assertions.assertThrows",
25+
"org.assertj.core.api.Assertions.assertThrowsExactly",
26+
"org.assertj.core.api.Assertions.assertDoesNotThrow",
2427
"org.junit.jupiter.api.Assertions.assertThrows",
2528
"org.junit.jupiter.api.Assertions.assertDoesNotThrow",
2629
// assertThat (allowed is only org.assertj.core.api.Assertions.assertThat)
@@ -54,7 +57,10 @@ public interface BlacklistMethodsArchRule {
5457
"org.junit.jupiter.api.Assertions.assertNotNull",
5558
"org.junit.jupiter.api.Assertions.assertNull",
5659
"org.junit.jupiter.api.Assertions.assertTrue",
57-
"org.testcontainers.shaded.org.hamcrest.MatcherAssert.assertThat"
60+
"org.testcontainers.shaded.org.hamcrest.MatcherAssert.assertThat",
61+
// use regular Mockito instead of BDDMockito
62+
"org.mockito.BDDMockito.given",
63+
"org.mockito.BDDMockito.then"
5864
)
5965
);
6066

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
12+
13+
@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
14+
@NullMarked
15+
public interface NoSystemOutOrErrArchRule {
16+
@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
17+
@ArchTest
18+
default void no_system_out_or_err_is_used(JavaClasses classes) {
19+
classes()
20+
.should(new NotUseSystemOutOrErr())
21+
.check(classes);
22+
}
23+
24+
class NotUseSystemOutOrErr extends ArchCondition<JavaClass> {
25+
private static final String SYSTEM_CLASS = "java.lang.System";
26+
private static final String FIELD_OUT = "out";
27+
private static final String FIELD_ERR = "err";
28+
29+
public NotUseSystemOutOrErr() {
30+
super("not use System.out or System.err");
31+
}
32+
33+
@Override
34+
public void check(JavaClass javaClass, ConditionEvents events) {
35+
checkCodeUnits(javaClass, events);
36+
javaClass.getStaticInitializer().ifPresent(staticInitializer -> {
37+
for (var fieldAccess : staticInitializer.getFieldAccesses()) {
38+
if (isSystemOutOrErr(
39+
fieldAccess.getTargetOwner().getFullName(),
40+
fieldAccess.getTarget().getName()
41+
)) {
42+
var message = String.format(
43+
"Static initializer in %s accesses %s.%s (%s.java:%d)",
44+
javaClass.getFullName(),
45+
SYSTEM_CLASS,
46+
fieldAccess.getTarget().getName(),
47+
javaClass.getSimpleName(),
48+
fieldAccess.getSourceCodeLocation().getLineNumber()
49+
);
50+
events.add(SimpleConditionEvent.violated(staticInitializer, message));
51+
}
52+
}
53+
});
54+
}
55+
56+
private void checkCodeUnits(JavaClass javaClass, ConditionEvents events) {
57+
for (var method : javaClass.getMethods()) {
58+
for (var fieldAccess : method.getFieldAccesses()) {
59+
if (isSystemOutOrErr(
60+
fieldAccess.getTargetOwner().getFullName(),
61+
fieldAccess.getTarget().getName()
62+
)) {
63+
var message = String.format(
64+
"Method %s accesses %s.%s (%s.java:%d)",
65+
method.getFullName(),
66+
SYSTEM_CLASS,
67+
fieldAccess.getTarget().getName(),
68+
javaClass.getSimpleName(),
69+
fieldAccess.getSourceCodeLocation().getLineNumber()
70+
);
71+
events.add(SimpleConditionEvent.violated(method, message));
72+
}
73+
}
74+
}
75+
}
76+
77+
private boolean isSystemOutOrErr(String ownerFullName, String fieldName) {
78+
return SYSTEM_CLASS.equals(ownerFullName)
79+
&& (FIELD_OUT.equals(fieldName) || FIELD_ERR.equals(fieldName));
80+
}
81+
}
82+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package it.aboutbits.archunit.toolbox.rule.base;
2+
3+
import com.tngtech.archunit.core.domain.JavaField;
4+
import com.tngtech.archunit.junit.ArchTest;
5+
import com.tngtech.archunit.lang.ArchCondition;
6+
import com.tngtech.archunit.lang.ArchRule;
7+
import com.tngtech.archunit.lang.ConditionEvents;
8+
import com.tngtech.archunit.lang.SimpleConditionEvent;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
12+
13+
@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
14+
@NullMarked
15+
public interface RecordPropertiesMustBeAccessedViaAccessorArchRule {
16+
String ARCH_ALLOW_DIRECT_ACCESS = "it.aboutbits.springboot.toolbox.archunit.ArchAllowDirectAccess";
17+
18+
@SuppressWarnings({"unused", "checkstyle:ConstantName", "java:S115"})
19+
@ArchTest
20+
ArchRule record_properties_must_be_accessed_via_accessor = fields()
21+
.that().areDeclaredInClassesThat().areRecords()
22+
.and().areNotStatic()
23+
.should(new ArchCondition<>("only be accessed by the record itself") {
24+
@Override
25+
public void check(JavaField field, ConditionEvents events) {
26+
if (field.isAnnotatedWith(ARCH_ALLOW_DIRECT_ACCESS)
27+
|| field.getOwner().isAnnotatedWith(ARCH_ALLOW_DIRECT_ACCESS)) {
28+
return;
29+
}
30+
31+
for (var access : field.getAccessesToSelf()) {
32+
// Check if the origin of the access is NOT the record class that owns the field
33+
if (!access.getOrigin().getOwner().equals(field.getOwner())) {
34+
var message = "Record property [%s] in [%s] accessed directly by [%s]. Use accessor method [%s()] instead. (%s.java:%d)"
35+
.formatted(
36+
field.getName(),
37+
field.getOwner().getSimpleName(),
38+
access.getOrigin().getFullName(),
39+
field.getName(),
40+
access.getOrigin().getOwner().getSimpleName(),
41+
access.getLineNumber()
42+
);
43+
events.add(SimpleConditionEvent.violated(access, message));
44+
}
45+
}
46+
}
47+
});
48+
}

src/main/java/it/aboutbits/archunit/toolbox/rule/common/ControllerRequestMappingsMustBeSecurityTested.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
import com.tngtech.archunit.lang.ArchCondition;
1010
import com.tngtech.archunit.lang.ConditionEvents;
1111
import com.tngtech.archunit.lang.SimpleConditionEvent;
12+
import org.jspecify.annotations.NullMarked;
1213

1314
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
1415

16+
@NullMarked
1517
public interface ControllerRequestMappingsMustBeSecurityTested {
1618
@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
1719
@ArchTest

src/main/java/it/aboutbits/archunit/toolbox/rule/common/SortMappingsExhaustiveArchRule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.tngtech.archunit.lang.SimpleConditionEvent;
1010
import lombok.extern.slf4j.Slf4j;
1111
import org.jspecify.annotations.NonNull;
12+
import org.jspecify.annotations.NullMarked;
1213

1314
import java.util.HashMap;
1415
import java.util.Map;
@@ -18,6 +19,7 @@
1819

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

22+
@NullMarked
2123
public interface SortMappingsExhaustiveArchRule {
2224
@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
2325
@ArchTest

0 commit comments

Comments
 (0)