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

Provide a new Java Rule checking if final classes do not have protect… #40

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
94 changes: 53 additions & 41 deletions docs/USERGUIDE.md

Large diffs are not rendered by default.

21 changes: 0 additions & 21 deletions src/main/java/com/enofex/taikai/AnnotationPredicates.java

This file was deleted.

77 changes: 77 additions & 0 deletions src/main/java/com/enofex/taikai/JavaPredicates.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.enofex.taikai;

import com.tngtech.archunit.base.DescribedPredicate;
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.core.domain.properties.CanBeAnnotated;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;

public final class JavaPredicates {

private JavaPredicates() {
}

public static DescribedPredicate<CanBeAnnotated> annotatedWith(String annotation,
boolean isMetaAnnotated) {
return new DescribedPredicate<>("annotated with %s".formatted(annotation)) {
@Override
public boolean test(CanBeAnnotated canBeAnnotated) {
return isMetaAnnotated ? canBeAnnotated.isMetaAnnotatedWith(annotation) :
canBeAnnotated.isAnnotatedWith(annotation);
}
};
}

public static ArchCondition<JavaMethod> notDeclareThrownExceptions() {
return new ArchCondition<>("not declare thrown exceptions") {
@Override
public void check(JavaMethod method, ConditionEvents events) {
if (!method.getThrowsClause().isEmpty()) {
String message = String.format("Method %s declares thrown exceptions",
method.getFullName());
events.add(SimpleConditionEvent.violated(method, message));
}
}
};
}

public static ArchCondition<JavaField> notBePublic() {
return new ArchCondition<>("not be public") {
@Override
public void check(JavaField field, ConditionEvents events) {
if (!field.getModifiers().contains(JavaModifier.STATIC)
&& field.getModifiers().contains(JavaModifier.PUBLIC)) {
String message = String.format("Field %s in class %s is public",
field.getName(),
field.getOwner().getFullName());
events.add(SimpleConditionEvent.violated(field, message));
}
}
};
}

public static DescribedPredicate<JavaClass> areFinal() {
return new DescribedPredicate<>("are final") {
@Override
public boolean test(JavaClass javaClass) {
return javaClass.getModifiers().contains(JavaModifier.FINAL);
}
};
}


public static ArchCondition<JavaClass> beFinal() {
return new ArchCondition<>("be final") {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
boolean isFinal = javaClass.getModifiers().contains(JavaModifier.FINAL);
String message = String.format("Class %s is not final", javaClass.getName());
events.add(new SimpleConditionEvent(javaClass, isFinal, message));
}
};
}
}
31 changes: 31 additions & 0 deletions src/main/java/com/enofex/taikai/java/ConstantNaming.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.enofex.taikai.java;

import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import java.util.regex.Pattern;

final class ConstantNaming {

private ConstantNaming() {
}

static ArchCondition<JavaField> shouldFollowConstantNamingConvention() {
return new ArchCondition<>("follow constant naming convention") {

private static final Pattern CONSTANT_NAME_PATTERN = Pattern.compile("^[A-Z][A-Z0-9_]*$");

@Override
public void check(JavaField field, ConditionEvents events) {
if (!field.getOwner().isEnum() && !CONSTANT_NAME_PATTERN.matcher(field.getName())
.matches()) {
String message = String.format(
"Constant %s in class %s does not follow the naming convention", field.getName(),
field.getOwner().getName());
events.add(SimpleConditionEvent.violated(field, message));
}
}
};
}
}
28 changes: 0 additions & 28 deletions src/main/java/com/enofex/taikai/java/FieldsShouldNotBePublic.java

This file was deleted.

17 changes: 15 additions & 2 deletions src/main/java/com/enofex/taikai/java/JavaConfigurer.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.enofex.taikai.java;

import static com.enofex.taikai.JavaPredicates.areFinal;
import static com.enofex.taikai.JavaPredicates.beFinal;
import static com.enofex.taikai.JavaPredicates.notBePublic;
import static com.enofex.taikai.java.Deprecations.notUseDeprecatedAPIs;
import static com.enofex.taikai.java.FieldsShouldNotBePublic.notBePublic;
import static com.enofex.taikai.java.HashCodeAndEquals.implementHashCodeAndEquals;
import static com.enofex.taikai.java.NoSystemOutOrErr.notUseSystemOutOrErr;
import static com.enofex.taikai.java.UtilityClasses.beFinal;
import static com.enofex.taikai.java.ProtectedMembers.notHaveProtectedMembers;
import static com.enofex.taikai.java.UtilityClasses.havePrivateConstructor;
import static com.enofex.taikai.java.UtilityClasses.utilityClasses;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
Expand Down Expand Up @@ -104,6 +106,17 @@ public JavaConfigurer noUsageOfSystemOutOrErr(Configuration configuration) {
return addRule(TaikaiRule.of(classes().should(notUseSystemOutOrErr()), configuration));
}

public JavaConfigurer finalClassesShouldNotHaveProtectedMembers() {
return finalClassesShouldNotHaveProtectedMembers(null);
}

public JavaConfigurer finalClassesShouldNotHaveProtectedMembers(Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that(areFinal())
.should(notHaveProtectedMembers())
.as("Final classes should not have protected members"), configuration));
}

@Override
public void disable() {
disable(ImportsConfigurer.class);
Expand Down
21 changes: 1 addition & 20 deletions src/main/java/com/enofex/taikai/java/NamingConfigurer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.enofex.taikai.java;

import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConvention;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
Expand All @@ -12,12 +13,10 @@
import com.enofex.taikai.configures.AbstractConfigurer;
import com.enofex.taikai.configures.ConfigurerContext;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaField;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import java.lang.annotation.Annotation;
import java.util.regex.Pattern;

public final class NamingConfigurer extends AbstractConfigurer {

Expand Down Expand Up @@ -160,22 +159,4 @@ public NamingConfigurer constantsShouldFollowConvention(Configuration configurat
fields().that().areFinal().and().areStatic().should(shouldFollowConstantNamingConvention())
.as("Constants should follow constant naming convention"), configuration));
}

private static ArchCondition<JavaField> shouldFollowConstantNamingConvention() {
return new ArchCondition<>("follow constant naming convention") {

private static final Pattern CONSTANT_NAME_PATTERN = Pattern.compile("^[A-Z][A-Z0-9_]*$");

@Override
public void check(JavaField field, ConditionEvents events) {
if (!field.getOwner().isEnum() && !CONSTANT_NAME_PATTERN.matcher(field.getName())
.matches()) {
String message = String.format(
"Constant %s in class %s does not follow the naming convention", field.getName(),
field.getOwner().getName());
events.add(SimpleConditionEvent.violated(field, message));
}
}
};
}
}
37 changes: 37 additions & 0 deletions src/main/java/com/enofex/taikai/java/ProtectedMembers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.enofex.taikai.java;

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;

final class ProtectedMembers {

private ProtectedMembers() {
}

static ArchCondition<JavaClass> notHaveProtectedMembers() {
return new ArchCondition<>("not have protected members") {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
for (JavaField field : javaClass.getFields()) {
if (field.getModifiers().contains(JavaModifier.PROTECTED)) {
String message = String.format("Field %s in final class %s is protected",
field.getName(), javaClass.getName());
events.add(SimpleConditionEvent.violated(field, message));
}
}
for (JavaMethod method : javaClass.getMethods()) {
if (method.getModifiers().contains(JavaModifier.PROTECTED)) {
String message = String.format("Method %s in final class %s is protected",
method.getName(), javaClass.getName());
events.add(SimpleConditionEvent.violated(method, message));
}
}
}
};
}
}
11 changes: 0 additions & 11 deletions src/main/java/com/enofex/taikai/java/UtilityClasses.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,6 @@ public boolean test(JavaClass javaClass) {
};
}

static ArchCondition<JavaClass> beFinal() {
return new ArchCondition<>("be final") {
@Override
public void check(JavaClass javaClass, ConditionEvents events) {
boolean isFinal = javaClass.getModifiers().contains(JavaModifier.FINAL);
String message = String.format("Class %s is not final", javaClass.getName());
events.add(new SimpleConditionEvent(javaClass, isFinal, message));
}
};
}

static ArchCondition<JavaClass> havePrivateConstructor() {
return new ArchCondition<>("have a private constructor") {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.enofex.taikai.spring;

import static com.enofex.taikai.AnnotationPredicates.annotatedWith;
import static com.enofex.taikai.JavaPredicates.annotatedWith;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
Expand Down
18 changes: 1 addition & 17 deletions src/main/java/com/enofex/taikai/test/JUnit5Configurer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.enofex.taikai.test;

import static com.enofex.taikai.JavaPredicates.notDeclareThrownExceptions;
import static com.enofex.taikai.test.JUnit5Predicates.ANNOTATION_DISABLED;
import static com.enofex.taikai.test.JUnit5Predicates.ANNOTATION_DISPLAY_NAME;
import static com.enofex.taikai.test.JUnit5Predicates.ANNOTATION_PARAMETRIZED_TEST;
Expand All @@ -15,10 +16,6 @@
import com.enofex.taikai.TaikaiRule.Configuration;
import com.enofex.taikai.configures.AbstractConfigurer;
import com.enofex.taikai.configures.ConfigurerContext;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;

public final class JUnit5Configurer extends AbstractConfigurer {

Expand Down Expand Up @@ -101,17 +98,4 @@ public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled(Configuration co
.as("Classes should not be annotated with %s".formatted(ANNOTATION_DISABLED)),
configuration));
}

private ArchCondition<JavaMethod> notDeclareThrownExceptions() {
return new ArchCondition<>("not declare thrown exceptions") {
@Override
public void check(JavaMethod method, ConditionEvents events) {
if (!method.getThrowsClause().isEmpty()) {
String message = String.format("Method %s declares thrown exceptions",
method.getFullName());
events.add(SimpleConditionEvent.violated(method, message));
}
}
};
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/enofex/taikai/test/JUnit5Predicates.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.enofex.taikai.test;

import static com.enofex.taikai.AnnotationPredicates.annotatedWith;
import static com.enofex.taikai.JavaPredicates.annotatedWith;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/enofex/taikai/ArchitectureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ void shouldFulfilConstrains() {
.methodsShouldNotBeAnnotatedWithDisabled()))
.java(java -> java
.noUsageOfDeprecatedAPIs()
.finalClassesShouldNotHaveProtectedMembers()
.classesShouldImplementHashCodeAndEquals()
.methodsShouldNotThrowGenericException()
.utilityClassesShouldBeFinalAndHavePrivateConstructor()
Expand Down
6 changes: 5 additions & 1 deletion src/test/java/com/enofex/taikai/Usage.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import java.util.Calendar;
import java.util.Date;

class Usage {
final class Usage {

private Usage() {
}

public static void main(String[] args) {
Taikai taikai = Taikai.builder()
Expand Down Expand Up @@ -45,6 +48,7 @@ public static void main(String[] args) {
.noUsageOfDeprecatedAPIs()
.classesShouldImplementHashCodeAndEquals()
.methodsShouldNotThrowGenericException()
.finalClassesShouldNotHaveProtectedMembers()
.utilityClassesShouldBeFinalAndHavePrivateConstructor()
.imports(imports -> imports
.shouldHaveNoCycles()
Expand Down
Loading