From 0f69a08e54ed32bea3d238c707cb210f87c599fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Jun 2024 06:13:45 +0000 Subject: [PATCH 1/7] Perform release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5e1336a..06ce37c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.enofex taikai - 0.7.1-SNAPSHOT + 0.7.1 Taikai 2024 From 7423bf4430d690f4516a9b01ab5f42d227749e65 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 20 Jun 2024 06:20:44 +0000 Subject: [PATCH 2/7] Prepare for next release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 06ce37c..2177563 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.enofex taikai - 0.7.1 + 0.7.2-SNAPSHOT Taikai 2024 From f45b70053d27bc9bd793fadccf909d12c1100064 Mon Sep 17 00:00:00 2001 From: mnhock Date: Thu, 20 Jun 2024 17:46:41 +0200 Subject: [PATCH 3/7] Provide a new Java Naming Rule checking if fields of a specific type match a regex Closes gh-48 --- README.md | 36 +++++++++-------- docs/USERGUIDE.md | 21 +++++----- .../enofex/taikai/java/NamingConfigurer.java | 40 +++++++++++++++---- src/test/java/com/enofex/taikai/Usage.java | 1 + 4 files changed, 66 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ab12b3f..1ca7a58 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,25 @@ Here's an example demonstrating the usage of Taikai with JUnit 5. Customize rule void shouldFulfillConstraints() { Taikai.builder() .namespace("com.enofex.taikai") + .java(java -> java + .noUsageOfDeprecatedAPIs() + .methodsShouldNotDeclareGenericExceptions() + .utilityClassesShouldBeFinalAndHavePrivateConstructor() + .imports(imports -> imports + .shouldHaveNoCycles() + .shouldNotImport("..shaded..") + .shouldNotImport("org.junit..")) + .naming(naming -> naming + .classesShouldNotMatch(".*Impl") + .methodsShouldNotMatch("foo") + .fieldsShouldNotMatch("bar") + .fieldsShouldMatch("com.awesome.Foo", "foo") + .constantsShouldFollowConvention() + .interfacesShouldNotHavePrefixI())) + .test(test -> test + .junit5(junit5 -> junit5 + .classesShouldNotBeAnnotatedWithDisabled() + .methodsShouldNotBeAnnotatedWithDisabled())) .spring(spring -> spring .noAutowiredFields() .boot(boot -> boot @@ -47,22 +66,7 @@ void shouldFulfillConstraints() { .repositories(repositories -> repositories .shouldBeAnnotatedWithRepository() .shouldNotDependOnServices() - .namesShouldEndWithRepository())) - .test(test -> test - .junit5(junit5 -> junit5 - .classesShouldNotBeAnnotatedWithDisabled() - .methodsShouldNotBeAnnotatedWithDisabled())) - .java(java -> java - .noUsageOfDeprecatedAPIs() - .methodsShouldNotDeclareGenericExceptions() - .utilityClassesShouldBeFinalAndHavePrivateConstructor() - .imports(imports -> imports - .shouldHaveNoCycles() - .shouldNotImport("..shaded..") - .shouldNotImport("org.junit..")) - .naming(naming -> naming - .classesShouldNotMatch(".*Impl") - .interfacesShouldNotHavePrefixI())) + .namesShouldEndWithRepository())) .addRule(TaikaiRule.of(...)) // Add custom ArchUnit rule here .build() .check(); diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index 095d554..fea21c2 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -42,10 +42,11 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import | Imports | `shouldHaveNoCycles` | No cyclic dependencies in imports | | Imports | `shouldNotImport` | Disallow specific imports (e.g., `..shaded..`) | | Naming | `classesShouldNotMatch` | Classes should not match specific naming patterns (e.g., `.*Impl`) | -| Naming | `methodsShouldNotMatch` | Methods should not match specific naming patterns | -| Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns | | Naming | `classesAnnotatedWithShouldMatch` | Classes annotated with should match specific naming patterns | +| Naming | `methodsShouldNotMatch` | Methods should not match specific naming patterns | | Naming | `methodsAnnotatedWithShouldMatch` | Methods annotated with should match specific naming patterns | +| Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns | +| Naming | `fieldsShouldMatch` | Fields should match specific naming patterns for specific classes | | Naming | `fieldsAnnotatedWithShouldMatch` | Fields annotated with should match specific naming patterns | | Naming | `constantsShouldFollowConvention` | Constants should follow naming conventions, except `serialVersionUID` | | Naming | `interfacesShouldNotHavePrefixI` | Interfaces should not have the prefix `I` | @@ -170,12 +171,14 @@ Taikai.builder() .java(java -> java .naming(naming -> naming .classesShouldNotMatch(".*Impl") + .classesAnnotatedWithShouldMatch(Annotation.class, "coolClass") .methodsShouldNotMatch("coolMethod") - .fieldsShouldNotMatch("coolField") - .constantsShouldFollowConvention() - .classesAnnotatedWithShouldMatch(Annotation.class, "coolClass") .methodsAnnotatedWithShouldMatch(Annotation.class, "coolMethods") + .fieldsShouldNotMatch("coolField") + .fieldsShouldMatch("com.awesome.Foo", "foo") + .fieldsShouldMatch(Foo.class, "foo") .fieldsAnnotatedWithShouldMatch(Annotation.class, "coolField") + .constantsShouldFollowConvention() .interfacesShouldNotHavePrefixI()))) .build() .check(); @@ -447,15 +450,15 @@ class ArchitectureTest { void shouldFulfilConstrains() { Taikai.builder() .namespace("com.company.yourproject") - .test(test -> test - .junit5(junit5 -> junit5 - .classesShouldNotBeAnnotatedWithDisabled() - .methodsShouldNotBeAnnotatedWithDisabled())) .java(java -> java .noUsageOfDeprecatedAPIs() .classesShouldImplementHashCodeAndEquals() .methodsShouldNotDeclareGenericExceptions() .utilityClassesShouldBeFinalAndHavePrivateConstructor()) + .test(test -> test + .junit5(junit5 -> junit5 + .classesShouldNotBeAnnotatedWithDisabled() + .methodsShouldNotBeAnnotatedWithDisabled())) .spring(spring -> spring .repositories(repositories -> repositories .namesShouldEndWithRepository() diff --git a/src/main/java/com/enofex/taikai/java/NamingConfigurer.java b/src/main/java/com/enofex/taikai/java/NamingConfigurer.java index 4ffec3c..1cfed58 100644 --- a/src/main/java/com/enofex/taikai/java/NamingConfigurer.java +++ b/src/main/java/com/enofex/taikai/java/NamingConfigurer.java @@ -89,6 +89,16 @@ public NamingConfigurer methodsAnnotatedWithShouldMatch(String annotationType, S annotationType, regex)), configuration)); } + public NamingConfigurer methodsShouldNotMatch(String regex) { + return methodsShouldNotMatch(regex, null); + } + + public NamingConfigurer methodsShouldNotMatch(String regex, Configuration configuration) { + return addRule(TaikaiRule.of(noMethods() + .should().haveNameMatching(regex) + .as("Methods should not have names matching %s".formatted(regex)), configuration)); + } + public NamingConfigurer fieldsAnnotatedWithShouldMatch( Class annotationType, String regex) { return fieldsAnnotatedWithShouldMatch(annotationType, regex, null); @@ -116,14 +126,30 @@ public NamingConfigurer fieldsAnnotatedWithShouldMatch(String annotationType, St annotationType, regex)), configuration)); } - public NamingConfigurer methodsShouldNotMatch(String regex) { - return methodsShouldNotMatch(regex, null); + public NamingConfigurer fieldsShouldMatch(String typeName, String regex) { + return fieldsShouldMatch(typeName, regex, null); } - public NamingConfigurer methodsShouldNotMatch(String regex, Configuration configuration) { - return addRule(TaikaiRule.of(noMethods() - .should().haveNameMatching(regex) - .as("Methods should not have names matching %s".formatted(regex)), configuration)); + public NamingConfigurer fieldsShouldMatch(String typeName, String regex, + Configuration configuration) { + return addRule(TaikaiRule.of(fields() + .that().haveRawType(typeName) + .should().haveNameMatching(regex) + .as("Fields of type %s should have names matching %s".formatted(typeName, regex)), + configuration)); + } + + public NamingConfigurer fieldsShouldMatch(Class clazz, String regex) { + return fieldsShouldMatch(clazz, regex, null); + } + + public NamingConfigurer fieldsShouldMatch(Class clazz, String regex, + Configuration configuration) { + return addRule(TaikaiRule.of(fields() + .that().haveRawType(clazz) + .should().haveNameMatching(regex) + .as("Fields of type %s should have names matching %s".formatted(clazz, regex)), + configuration)); } public NamingConfigurer fieldsShouldNotMatch(String regex) { @@ -148,7 +174,7 @@ public NamingConfigurer interfacesShouldNotHavePrefixI(Configuration configurati } private static ArchCondition notBePrefixedWithI() { - return new ArchCondition<>("not be prefixed with I.") { + return new ArchCondition<>("not be prefixed with I") { @Override public void check(JavaClass javaClass, ConditionEvents events) { if (javaClass.getSimpleName().startsWith("I") && Character.isUpperCase( diff --git a/src/test/java/com/enofex/taikai/Usage.java b/src/test/java/com/enofex/taikai/Usage.java index 2595adb..f9cf296 100644 --- a/src/test/java/com/enofex/taikai/Usage.java +++ b/src/test/java/com/enofex/taikai/Usage.java @@ -60,6 +60,7 @@ public static void main(String[] args) { .classesShouldNotMatch(".*Impl") .methodsShouldNotMatch("foo") .fieldsShouldNotMatch("bar") + .fieldsShouldMatch("com.awesome.Foo", "foo") .constantsShouldFollowConvention() .interfacesShouldNotHavePrefixI())) .build() From 5d3f5f1668065efd38122b2b2f7b0d4a13efe561 Mon Sep 17 00:00:00 2001 From: Martin Hock Date: Thu, 20 Jun 2024 18:58:10 +0200 Subject: [PATCH 4/7] Update USERGUIDE.md --- docs/USERGUIDE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index fea21c2..c5a9c7e 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -29,7 +29,7 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import | Category | Method Name | Rule Description | |----------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------| -| General | `classesShouldImplementHashCodeAndEquals` | Classes should implement `hashCode` and `equals` | +| General | `classesShouldImplementHashCodeAndEquals` | Classes should implement `hashCode` and `equals` together | | General | `fieldsShouldNotBePublic` | Fields should not be `public`, except constants | | General | `methodsShouldNotDeclareGenericExceptions` | Methods should not declare generic exceptions, like `Exception`, `RuntimeException` | | General | `noUsageOf` | Disallow usage of specific classes | @@ -105,7 +105,7 @@ Taikai.builder() .check(); ``` -- **Classes Should Implement `hashCode` and `equals`**: Ensure that classes override the `hashCode` and `equals` methods. +- **Classes Should Implement `hashCode` and `equals` together**: Ensure that classes override the `hashCode` and `equals` methods together. ```java Taikai.builder() @@ -482,4 +482,4 @@ class ArchitectureTest { .check(); } } -``` \ No newline at end of file +``` From be05c2279a60ac983a05252a821352328ac55fd1 Mon Sep 17 00:00:00 2001 From: mnhock Date: Thu, 20 Jun 2024 20:42:41 +0200 Subject: [PATCH 5/7] Remove unused imports --- src/main/java/com/enofex/taikai/test/JUnit5Configurer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java b/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java index 1cd843e..bf25e49 100644 --- a/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java +++ b/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java @@ -6,8 +6,6 @@ import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_PARAMETRIZED_TEST; import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_TEST; import static com.enofex.taikai.test.JUnit5DescribedPredicates.annotatedWithTestOrParameterizedTest; -import static com.tngtech.archunit.base.DescribedPredicate.not; -import static com.tngtech.archunit.lang.conditions.ArchConditions.beInterfaces; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; From 035cf8c059aa1e3aa3aa964ccbe3b1473e5c262f Mon Sep 17 00:00:00 2001 From: mnhock Date: Thu, 20 Jun 2024 20:55:07 +0200 Subject: [PATCH 6/7] Fis spelling mistake --- .../java/com/enofex/taikai/spring/ControllersConfigurer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/enofex/taikai/spring/ControllersConfigurer.java b/src/main/java/com/enofex/taikai/spring/ControllersConfigurer.java index 75e5004..c6cb37a 100644 --- a/src/main/java/com/enofex/taikai/spring/ControllersConfigurer.java +++ b/src/main/java/com/enofex/taikai/spring/ControllersConfigurer.java @@ -104,6 +104,6 @@ public ControllersConfigurer shouldNotDependOnOtherControllers(Configuration con return addRule(TaikaiRule.of(classes() .that(are(annotatedWithControllerOrRestController(true))) .should(not(dependOnClassesThat(are(annotatedWithControllerOrRestController(true))))) - .as("Controllers should not be depend on other Controller"), configuration)); + .as("Controllers should not be depend on other Controllers"), configuration)); } } From 69b0136c6dabc7201e557ae91c446da6e297bf16 Mon Sep 17 00:00:00 2001 From: mnhock Date: Fri, 21 Jun 2024 14:45:39 +0200 Subject: [PATCH 7/7] Provide a New Logging Rule checking logger consistent usage Closes gh-52 --- README.md | 6 +- docs/USERGUIDE.md | 68 +++++++++++++++--- src/main/java/com/enofex/taikai/Taikai.java | 9 ++- .../enofex/taikai/java/ConstantNaming.java | 2 +- .../enofex/taikai/java/NamingConfigurer.java | 12 ++-- .../taikai/logging/LoggerConventions.java | 44 ++++++++++++ .../taikai/logging/LoggingConfigurer.java | 42 +++++++++++ .../com/enofex/taikai/ArchitectureTest.java | 18 ++--- src/test/java/com/enofex/taikai/Usage.java | 71 ++++++++++--------- 9 files changed, 211 insertions(+), 61 deletions(-) create mode 100644 src/main/java/com/enofex/taikai/logging/LoggerConventions.java create mode 100644 src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java diff --git a/README.md b/README.md index 1ca7a58..733fb4a 100644 --- a/README.md +++ b/README.md @@ -42,12 +42,14 @@ void shouldFulfillConstraints() { .methodsShouldNotMatch("foo") .fieldsShouldNotMatch("bar") .fieldsShouldMatch("com.awesome.Foo", "foo") - .constantsShouldFollowConvention() + .constantsShouldFollowConventions() .interfacesShouldNotHavePrefixI())) + .logging(logging -> logging + .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL))) .test(test -> test .junit5(junit5 -> junit5 .classesShouldNotBeAnnotatedWithDisabled() - .methodsShouldNotBeAnnotatedWithDisabled())) + .methodsShouldNotBeAnnotatedWithDisabled())) .spring(spring -> spring .noAutowiredFields() .boot(boot -> boot diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index c5a9c7e..c5526fc 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -19,7 +19,32 @@ To use Taikai, include it as a dependency in your Maven `pom.xml`: Ensure to configure `${taikai.version}` to the latest stable version compatible with your project's ArchUnit version. -## 3. Rules Overview +## 3. Usage + +### 3.1 Setting the Namespace + +The `namespace` setting specifies the base package of your project. Taikai will analyze all classes within this namespace. The default mode is `WITHOUT_TESTS`, which excludes test classes from the import check. + +```java +Taikai.builder() + .namespace("com.company.yourproject") + .build() + .check(); +``` + +### 3.2 Enforcing Rules on Empty Sets + +The `failOnEmpty` setting determines whether the build should fail if no classes match a given rule. This is useful to ensure that your rules are applied consistently and to avoid false positives. The default is `false`. + +```java +Taikai.builder() + .namespace("com.company.yourproject") + .failOnEmpty(true) + .build() + .check(); +``` + +## 4. Rules Overview Taikai's architecture rules cover a wide range of categories to enforce best practices and maintain consistency. @@ -48,9 +73,17 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import | Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns | | Naming | `fieldsShouldMatch` | Fields should match specific naming patterns for specific classes | | Naming | `fieldsAnnotatedWithShouldMatch` | Fields annotated with should match specific naming patterns | -| Naming | `constantsShouldFollowConvention` | Constants should follow naming conventions, except `serialVersionUID` | +| Naming | `constantsShouldFollowConventions` | Constants should follow naming conventions, except `serialVersionUID` | | Naming | `interfacesShouldNotHavePrefixI` | Interfaces should not have the prefix `I` | +### Logging Rules + +The default mode is `WITHOUT_TESTS`, which checks only test classes. + +| Category | Method Name | Rule Description | +|----------|-------------------|----------------------------------------------------------------------------------------------------| +| General | `loggersShouldFollowConventions` | Ensure that the specified logger follow a specific naming pattern and have the required modifiers | + ### Test Rules The default mode is `ONLY_TESTS`, which checks only test classes. @@ -90,7 +123,7 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import | Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` | | Services | `shouldNotDependOnControllers` | Services annotated with `@Service.` should not depend on controllers annotated with `@Controller` or `@RestController` | -## 4. Java Rules +## 5. Java Rules Java configuration involves defining constraints related to Java language features, coding standards, and architectural patterns. @@ -178,7 +211,7 @@ Taikai.builder() .fieldsShouldMatch("com.awesome.Foo", "foo") .fieldsShouldMatch(Foo.class, "foo") .fieldsAnnotatedWithShouldMatch(Annotation.class, "coolField") - .constantsShouldFollowConvention() + .constantsShouldFollowConventions() .interfacesShouldNotHavePrefixI()))) .build() .check(); @@ -206,7 +239,7 @@ Taikai.builder() .check(); ``` -- **No Usage of System.out or System.err**: Enforce disallowing the use of `System.out` and `System.err` for logging, encouraging the use of proper logging frameworks instead. +- **No Usage of `System.out` or `System.err`**: Enforce disallowing the use of `System.out` and `System.err` for logging, encouraging the use of proper logging frameworks instead. ```java Taikai.builder() @@ -228,7 +261,22 @@ Taikai.builder() .check(); ``` -## 5. Test Rules +## 6. Logging Rules + +Logging configuration involves specifying constraints related to logging frameworks and practices. + +- **Ensure Logger Field Conforms to Standards**: Ensure that classes use a logger field of the specified type, with the correct name and modifiers. + +```java +Taikai.builder() + .namespace("com.company.yourproject") + .logging(logging -> logging + .loggersShouldFollowConventions(org.slf4j.Logger.class, "logger", EnumSet.of(PRIVATE, FINAL))) + .build() + .check(); +``` + +## 7. Test Rules Test configuration involves specifying constraints related to testing frameworks and practices. @@ -317,7 +365,7 @@ Taikai.builder() .check(); ``` -## 6. Spring Rules +## 8. Spring Rules Spring configuration involves defining constraints specific to Spring Framework usage. @@ -403,7 +451,7 @@ Taikai.builder() .check(); ``` -## 7. Customization +## 9. Customization ### Custom Configuration for Import Rules @@ -439,7 +487,7 @@ Taikai.builder() ``` By using the `addRule()` method and providing a custom ArchUnit rule, you can extend Taikai's capabilities to enforce additional architectural constraints that are not covered by the predefined rules. This flexibility allows you to adapt Taikai to suit the unique architectural needs of your Java project. -## 8. Examples +## 10. Examples Below are some examples demonstrating the usage of Taikai to define and enforce architectural rules in Java projects, including Spring-specific configurations: @@ -478,6 +526,8 @@ class ArchitectureTest { .namesShouldMatch("regex") .shouldNotDependOnOtherControllers() .shouldBePackagePrivate())) + .logging(logging -> logging + .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL))) .build() .check(); } diff --git a/src/main/java/com/enofex/taikai/Taikai.java b/src/main/java/com/enofex/taikai/Taikai.java index 4314804..fcae95c 100644 --- a/src/main/java/com/enofex/taikai/Taikai.java +++ b/src/main/java/com/enofex/taikai/Taikai.java @@ -5,6 +5,7 @@ import com.enofex.taikai.configures.Configurers; import com.enofex.taikai.configures.Customizer; import com.enofex.taikai.java.JavaConfigurer; +import com.enofex.taikai.logging.LoggingConfigurer; import com.enofex.taikai.spring.SpringConfigurer; import com.enofex.taikai.test.TestConfigurer; import com.tngtech.archunit.ArchConfiguration; @@ -100,14 +101,18 @@ public Builder java(Customizer customizer) { return configure(customizer, JavaConfigurer::new); } - public Builder spring(Customizer customizer) { - return configure(customizer, SpringConfigurer::new); + public Builder logging(Customizer customizer) { + return configure(customizer, LoggingConfigurer::new); } public Builder test(Customizer customizer) { return configure(customizer, TestConfigurer::new); } + public Builder spring(Customizer customizer) { + return configure(customizer, SpringConfigurer::new); + } + private Builder configure(Customizer customizer, Function supplier) { Objects.requireNonNull(customizer); diff --git a/src/main/java/com/enofex/taikai/java/ConstantNaming.java b/src/main/java/com/enofex/taikai/java/ConstantNaming.java index b9f338b..c73abcc 100644 --- a/src/main/java/com/enofex/taikai/java/ConstantNaming.java +++ b/src/main/java/com/enofex/taikai/java/ConstantNaming.java @@ -15,7 +15,7 @@ final class ConstantNaming { private ConstantNaming() { } - static ArchCondition shouldFollowConstantNamingConvention() { + static ArchCondition shouldFollowConstantNamingConventions() { return new ArchCondition<>("follow constant naming convention") { @Override public void check(JavaField field, ConditionEvents events) { diff --git a/src/main/java/com/enofex/taikai/java/NamingConfigurer.java b/src/main/java/com/enofex/taikai/java/NamingConfigurer.java index 1cfed58..de7b110 100644 --- a/src/main/java/com/enofex/taikai/java/NamingConfigurer.java +++ b/src/main/java/com/enofex/taikai/java/NamingConfigurer.java @@ -1,6 +1,6 @@ package com.enofex.taikai.java; -import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConvention; +import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConventions; 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; @@ -185,14 +185,14 @@ public void check(JavaClass javaClass, ConditionEvents events) { }; } - public NamingConfigurer constantsShouldFollowConvention() { - return constantsShouldFollowConvention(null); + public NamingConfigurer constantsShouldFollowConventions() { + return constantsShouldFollowConventions(null); } - public NamingConfigurer constantsShouldFollowConvention(Configuration configuration) { + public NamingConfigurer constantsShouldFollowConventions(Configuration configuration) { return addRule(TaikaiRule.of(fields() .that().areFinal().and().areStatic() - .should(shouldFollowConstantNamingConvention()) - .as("Constants should follow constant naming convention"), configuration)); + .should(shouldFollowConstantNamingConventions()) + .as("Constants should follow constant naming conventions"), configuration)); } } diff --git a/src/main/java/com/enofex/taikai/logging/LoggerConventions.java b/src/main/java/com/enofex/taikai/logging/LoggerConventions.java new file mode 100644 index 0000000..ab89faa --- /dev/null +++ b/src/main/java/com/enofex/taikai/logging/LoggerConventions.java @@ -0,0 +1,44 @@ +package com.enofex.taikai.logging; + +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaField; +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.Set; + +final class LoggerConventions { + + private LoggerConventions() { + } + + static ArchCondition followLoggerConventions(String typeName, String regex, + Set requiredModifiers) { + return new ArchCondition<>( + "have a logger field of type %s with name pattern %s and modifiers %s".formatted( + typeName, regex, requiredModifiers)) { + @Override + public void check(JavaClass javaClass, ConditionEvents events) { + for (JavaField field : javaClass.getAllFields()) { + if (field.getRawType().isAssignableTo(typeName)) { + if (!field.getName().matches(regex)) { + events.add(SimpleConditionEvent.violated(field, + "Field '%s' in class %s does not match the naming pattern '%s'".formatted( + field.getName(), + javaClass.getName(), regex))); + } + + if (!field.getModifiers().containsAll(requiredModifiers)) { + events.add(SimpleConditionEvent.violated(field, + "Field '%s' in class %s does not have the required modifiers %s".formatted( + field.getName(), + javaClass.getName(), + requiredModifiers))); + } + } + } + } + }; + } +} diff --git a/src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java b/src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java new file mode 100644 index 0000000..84e1316 --- /dev/null +++ b/src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java @@ -0,0 +1,42 @@ +package com.enofex.taikai.logging; + +import static com.enofex.taikai.logging.LoggerConventions.followLoggerConventions; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +import com.enofex.taikai.TaikaiRule; +import com.enofex.taikai.TaikaiRule.Configuration; +import com.enofex.taikai.configures.AbstractConfigurer; +import com.enofex.taikai.configures.ConfigurerContext; +import com.tngtech.archunit.core.domain.JavaModifier; +import java.util.Set; + +public final class LoggingConfigurer extends AbstractConfigurer { + + public LoggingConfigurer(ConfigurerContext configurerContext) { + super(configurerContext); + } + + public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex, + Set requiredModifiers) { + return loggersShouldFollowConventions(typeName, regex, requiredModifiers, null); + } + + public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex, + Set requiredModifiers, Configuration configuration) { + return addRule(TaikaiRule.of(classes().should( + followLoggerConventions(typeName, regex, requiredModifiers)), + configuration)); + } + + public LoggingConfigurer loggersShouldFollowConventions(Class clazz, String regex, + Set requiredModifiers) { + return loggersShouldFollowConventions(clazz, regex, requiredModifiers, null); + } + + public LoggingConfigurer loggersShouldFollowConventions(Class clazz, String regex, + Set requiredModifiers, Configuration configuration) { + return addRule(TaikaiRule.of(classes().should( + followLoggerConventions(clazz.getName(), regex, requiredModifiers)), + configuration)); + } +} \ No newline at end of file diff --git a/src/test/java/com/enofex/taikai/ArchitectureTest.java b/src/test/java/com/enofex/taikai/ArchitectureTest.java index 1714767..58c8962 100644 --- a/src/test/java/com/enofex/taikai/ArchitectureTest.java +++ b/src/test/java/com/enofex/taikai/ArchitectureTest.java @@ -11,14 +11,6 @@ class ArchitectureTest { void shouldFulfilConstrains() { Taikai.builder() .namespace("com.enofex.taikai") - .test(test -> test - .junit5(junit5 -> junit5 - .classesShouldNotBeAnnotatedWithDisabled() - .classesShouldBePackagePrivate(".*Test") - .methodsShouldNotBeAnnotatedWithDisabled() - .methodsShouldMatch("should.*") - .methodsShouldBePackagePrivate() - .methodsShouldNotDeclareExceptions())) .java(java -> java .noUsageOfDeprecatedAPIs() .noUsageOfSystemOutOrErr() @@ -39,7 +31,15 @@ void shouldFulfilConstrains() { .naming(naming -> naming .classesShouldNotMatch(".*Impl") .interfacesShouldNotHavePrefixI() - .constantsShouldFollowConvention())) + .constantsShouldFollowConventions())) + .test(test -> test + .junit5(junit5 -> junit5 + .classesShouldNotBeAnnotatedWithDisabled() + .classesShouldBePackagePrivate(".*Test") + .methodsShouldNotBeAnnotatedWithDisabled() + .methodsShouldMatch("should.*") + .methodsShouldBePackagePrivate() + .methodsShouldNotDeclareExceptions())) .build() .check(); } diff --git a/src/test/java/com/enofex/taikai/Usage.java b/src/test/java/com/enofex/taikai/Usage.java index f9cf296..85bc108 100644 --- a/src/test/java/com/enofex/taikai/Usage.java +++ b/src/test/java/com/enofex/taikai/Usage.java @@ -1,13 +1,52 @@ package com.enofex.taikai; +import static com.tngtech.archunit.core.domain.JavaModifier.FINAL; +import static com.tngtech.archunit.core.domain.JavaModifier.PRIVATE; + import java.util.Calendar; import java.util.Date; +import java.util.EnumSet; +import java.util.logging.Logger; class Usage { public static void main(String[] args) { Taikai.builder() .namespace("com.enofex.taikai") + .java(java -> java + .noUsageOf(Date.class) + .noUsageOf(Calendar.class) + .noUsageOf("java.text.SimpleDateFormat") + .noUsageOfSystemOutOrErr() + .noUsageOfDeprecatedAPIs() + .classesShouldImplementHashCodeAndEquals() + .methodsShouldNotDeclareGenericExceptions() + .finalClassesShouldNotHaveProtectedMembers() + .utilityClassesShouldBeFinalAndHavePrivateConstructor() + .serialVersionUIDFieldsShouldBeStaticFinalLong() + .imports(imports -> imports + .shouldHaveNoCycles() + .shouldNotImport("..shaded..") + .shouldNotImport("..lombok..") + .shouldNotImport("org.junit..")) + .naming(naming -> naming + .classesShouldNotMatch(".*Impl") + .methodsShouldNotMatch("foo") + .fieldsShouldNotMatch("bar") + .fieldsShouldMatch("com.awesome.Foo", "foo") + .constantsShouldFollowConventions() + .interfacesShouldNotHavePrefixI())) + .logging(logging -> logging + .loggersShouldFollowConventions(Logger.class, "logger", EnumSet.of(PRIVATE, FINAL))) + .test(test -> test + .junit5(junit5 -> junit5 + .methodsShouldNotDeclareExceptions() + .methodsShouldMatch("should.*") + .methodsShouldBePackagePrivate() + .methodsShouldBeAnnotatedWithDisplayName() + .methodsShouldNotBeAnnotatedWithDisabled() + .classesShouldBePackagePrivate(".*Test") + .classesShouldNotBeAnnotatedWithDisabled())) .spring(spring -> spring .noAutowiredFields() .boot(boot -> boot @@ -31,38 +70,6 @@ public static void main(String[] args) { .shouldBeAnnotatedWithRepository() .namesShouldMatch("regex") .namesShouldEndWithRepository())) - .test(test -> test - .junit5(junit5 -> junit5 - .methodsShouldNotDeclareExceptions() - .methodsShouldMatch("should.*") - .methodsShouldBePackagePrivate() - .methodsShouldBeAnnotatedWithDisplayName() - .methodsShouldNotBeAnnotatedWithDisabled() - .classesShouldBePackagePrivate(".*Test") - .classesShouldNotBeAnnotatedWithDisabled())) - .java(java -> java - .noUsageOf(Date.class) - .noUsageOf(Calendar.class) - .noUsageOf("java.text.SimpleDateFormat") - .noUsageOfSystemOutOrErr() - .noUsageOfDeprecatedAPIs() - .classesShouldImplementHashCodeAndEquals() - .methodsShouldNotDeclareGenericExceptions() - .finalClassesShouldNotHaveProtectedMembers() - .utilityClassesShouldBeFinalAndHavePrivateConstructor() - .serialVersionUIDFieldsShouldBeStaticFinalLong() - .imports(imports -> imports - .shouldHaveNoCycles() - .shouldNotImport("..shaded..") - .shouldNotImport("..lombok..") - .shouldNotImport("org.junit..")) - .naming(naming -> naming - .classesShouldNotMatch(".*Impl") - .methodsShouldNotMatch("foo") - .fieldsShouldNotMatch("bar") - .fieldsShouldMatch("com.awesome.Foo", "foo") - .constantsShouldFollowConvention() - .interfacesShouldNotHavePrefixI())) .build() .check(); }