diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index 482c587..3438dde 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -118,6 +118,7 @@ The default mode is `WITHOUT_TESTS`, which excludes test classes from the import | 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. | +| General | `methodsShouldBeAnnotatedWithAll` | Methods annotated with a specific annotation should be annotated with a specified annotations. | | General | `noUsageOf` | Disallow usage of specific classes. | | General | `noUsageOfDeprecatedAPIs` | No usage of deprecated APIs annotated with `@Deprecated`. | | General | `noUsageOfSystemOutOrErr` | Disallow usage of `System.out` or `System.err`. | @@ -268,8 +269,8 @@ Taikai.builder() Taikai.builder() .namespace("com.company.project") .java(java -> java - .classesShouldNotBeAnnotatedWithAll(Modifying.class, List.of(Transactional.class, Query.class)) - .classesShouldNotBeAnnotatedWithAll("org.springframework.data.jpa.repository.Modifying", List.of("org.springframework.transaction.annotation.Transactional", "org.springframework.data.jpa.repository.Query")) + .classesShouldBeAnnotatedWithAll(RestController.class, List.of(RequestMapping.class)) + .classesShouldBeAnnotatedWithAll("org.springframework.web.bind.annotation.RestController", List.of("org.springframework.web.bind.annotation.RequestMapping")) .build() .check(); ``` @@ -333,6 +334,20 @@ Taikai.builder() .check(); ``` + +- **Methods Annotated with a Specified Annotation Should Be Annotated with Specified Annotations**: Ensure that methods annotated with a specific annotations should be annotated with the specified annotations. + +```java +Taikai.builder() + .namespace("com.company.project") + .java(java -> java + .methodsShouldBeAnnotatedWithAll(Modifying.class, List.of(Transactional.class, Query.class)) + .methodsShouldBeAnnotatedWithAll("org.springframework.data.jpa.repository.Modifying", List.of("org.springframework.transaction.annotation.Transactional", "org.springframework.data.jpa.repository.Query")) + .build() + .check(); +``` + + - **Utility Classes Should Be Final and Have Private Constructor**: Ensure that utility classes with only `static` methods except `main` should be declared as `final` and have `private` constructors to prevent instantiation. ```java diff --git a/src/main/java/com/enofex/taikai/java/JavaConfigurer.java b/src/main/java/com/enofex/taikai/java/JavaConfigurer.java index 47399a9..64f3850 100644 --- a/src/main/java/com/enofex/taikai/java/JavaConfigurer.java +++ b/src/main/java/com/enofex/taikai/java/JavaConfigurer.java @@ -82,6 +82,28 @@ public JavaConfigurer methodsShouldNotDeclareException(String regex, String type configuration)); } + public JavaConfigurer methodsShouldBeAnnotatedWithAll(Class annotationType, + Collection> requiredAnnotationTypes) { + return methodsShouldBeAnnotatedWithAll(annotationType.getName(), + requiredAnnotationTypes.stream().map(Class::getName).toList(), defaultConfiguration()); + } + + public JavaConfigurer methodsShouldBeAnnotatedWithAll(String annotationType, + Collection requiredAnnotationTypes) { + return methodsShouldBeAnnotatedWithAll(annotationType, requiredAnnotationTypes, + defaultConfiguration()); + } + + public JavaConfigurer methodsShouldBeAnnotatedWithAll(String annotationType, + Collection requiredAnnotationTypes, Configuration configuration) { + return addRule(TaikaiRule.of(methods() + .that().areMetaAnnotatedWith(annotationType) + .should(be(annotatedWithAll(requiredAnnotationTypes, true))) + .as("Methods annotated with %s should be annotated with %s".formatted( + annotationType, String.join(", ", requiredAnnotationTypes))), + configuration)); + } + public JavaConfigurer noUsageOfDeprecatedAPIs() { return noUsageOfDeprecatedAPIs(defaultConfiguration()); }