Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add New Rule for Fields Matching Specific Naming Patterns to Have Spe… #114

Merged
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
13 changes: 13 additions & 0 deletions docs/USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import
| General | `classesShouldNotBeAnnotatedWith` | Classes matching specific naming patterns should not be annotated with a specified annotation. |
| General | `classesShouldBeAssignableTo` | Classes matching specific naming patterns should be assignable to a certain type. |
| General | `classesShouldImplement` | Classes matching specific naming patterns should implement to a interface. |
| General | `fieldsShouldHaveModifiers` | Fields matching specific naming patterns should have specified modifiers. |
| General | `fieldsShouldNotBePublic` | Fields should not be `public`, except constants. |
| General | `methodsShouldNotDeclareGenericExceptions` | Methods should not declare generic exceptions, like `Exception` or `RuntimeException`. |
| General | `methodsShouldNotDeclareException` | Methods with names matching a specified pattern should not declare a specified exception type. |
Expand Down Expand Up @@ -372,6 +373,18 @@ Taikai.builder()
.check();
```


- **Fields Should Have Modifiers**: Ensure that fields matching a specific naming pattern have the required modifiers.

```java
Taikai.builder()
.namespace("com.enofex.taikai")
.java(java -> java
.fieldsShouldHaveModifiers("^[A-Z][A-Z0-9_]*$", List.of(STATIC, FINAL)))
.build()
.check();
```

- **Fields Should Not Be Public**: Ensure that no fields in your Java classes are declared as `public`, except constants.

```java
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/com/enofex/taikai/internal/ArchConditions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import java.util.Collection;
import java.util.stream.Collectors;

/**
* Internal utility class for defining general ArchCondition used in architectural rules.
Expand Down Expand Up @@ -80,4 +83,26 @@ public void check(JavaClass item, ConditionEvents events) {
}
};
}

/**
* Creates a condition that checks if a field contains all the specified modifiers.
*
* @param requiredModifiers the collection of modifiers that the field is required to have
* @return an architectural condition for checking if a field has the required modifiers
*/
public static ArchCondition<JavaField> hasFieldModifiers(
Collection<JavaModifier> requiredModifiers) {
return new ArchCondition<>("has field modifiers") {
@Override
public void check(JavaField field, ConditionEvents events) {
if (!field.getModifiers().containsAll(requiredModifiers)) {
events.add(SimpleConditionEvent.violated(field,
"Field %s in class %s is missing one of this %s modifier".formatted(
field.getName(),
field.getOwner().getFullName(),
requiredModifiers.stream().map(Enum::name).collect(Collectors.joining(", ")))));
}
}
};
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/enofex/taikai/java/JavaConfigurer.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.enofex.taikai.java;

import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
import static com.enofex.taikai.internal.ArchConditions.hasFieldModifiers;
import static com.enofex.taikai.internal.ArchConditions.notBePublicButNotStatic;
import static com.enofex.taikai.internal.DescribedPredicates.annotatedWithAll;
import static com.enofex.taikai.internal.DescribedPredicates.areFinal;
Expand All @@ -25,8 +26,10 @@
import com.enofex.taikai.configures.ConfigurerContext;
import com.enofex.taikai.configures.Customizer;
import com.enofex.taikai.configures.DisableableConfigurer;
import com.tngtech.archunit.core.domain.JavaModifier;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.stream.Collectors;

public class JavaConfigurer extends AbstractConfigurer {

Expand Down Expand Up @@ -327,6 +330,23 @@ public JavaConfigurer fieldsShouldNotBePublic(Configuration configuration) {
.should(notBePublicButNotStatic()), configuration));
}

public JavaConfigurer fieldsShouldHaveModifiers(String regex,
Collection<JavaModifier> requiredModifiers) {
return fieldsShouldHaveModifiers(regex, requiredModifiers, defaultConfiguration());
}

public JavaConfigurer fieldsShouldHaveModifiers(String regex,
Collection<JavaModifier> requiredModifiers,
Configuration configuration) {
return addRule(TaikaiRule.of(fields()
.that().haveNameMatching(regex)
.should(hasFieldModifiers(requiredModifiers))
.as("Fields have name matching %s should have all of this modifiers %s".formatted(
regex,
requiredModifiers.stream().map(Enum::name).collect(Collectors.joining(", ")))),
configuration));
}

public JavaConfigurer noUsageOf(Class<?> clazz) {
return noUsageOf(clazz.getName(), null, defaultConfiguration());
}
Expand Down
5 changes: 5 additions & 0 deletions src/test/java/com/enofex/taikai/ArchitectureTest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.enofex.taikai;

import static com.tngtech.archunit.core.domain.JavaModifier.FINAL;
import static com.tngtech.archunit.core.domain.JavaModifier.STATIC;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.junit.jupiter.api.Test;

class ArchitectureTest {
Expand All @@ -17,6 +21,7 @@ void shouldFulfilConstrains() {
.noUsageOf(Date.class)
.noUsageOf(Calendar.class)
.noUsageOf(SimpleDateFormat.class)
.fieldsShouldHaveModifiers("^[A-Z][A-Z0-9_]*$", List.of(STATIC, FINAL))
.classesShouldImplementHashCodeAndEquals()
.finalClassesShouldNotHaveProtectedMembers()
.utilityClassesShouldBeFinalAndHavePrivateConstructor()
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/com/enofex/taikai/Usage.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public static void main(String[] args) {
.noUsageOfDeprecatedAPIs()
.noUsageOfDeprecatedAPIs(defaultConfiguration())

.fieldsShouldNotBePublic()
.fieldsShouldNotBePublic(defaultConfiguration())

.fieldsShouldHaveModifiers("regex", List.of(PRIVATE, FINAL))
.fieldsShouldHaveModifiers("regex", List.of(PRIVATE, FINAL), defaultConfiguration())

.classesShouldImplementHashCodeAndEquals()
.classesShouldImplementHashCodeAndEquals(defaultConfiguration())

Expand Down
Loading