From 3ba61c3fa76ebd239ee3ac760a99df6e6d0d992c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 9 Oct 2025 09:55:25 +0100 Subject: [PATCH] Improve handling of annotation properties in architecture checks Closes gh-47437 --- .../build/architecture/ArchitectureRules.java | 36 +++++++++++-------- .../architecture/ArchitectureCheckTests.java | 25 +++++++++++++ .../EnumSourceInferredFromParameterType.java | 34 ++++++++++++++++++ .../EnumSourceSameAsParameterType.java | 34 ++++++++++++++++++ .../EnumSourceValueNecessary.java | 34 ++++++++++++++++++ .../sbom/SbomEndpointWebExtensionTests.java | 2 +- ...sonHttpMessageConvertersConfiguration.java | 7 ++-- .../error/ErrorWebFluxAutoConfiguration.java | 2 +- ...aSourceScriptDatabaseInitializerTests.java | 4 +-- 9 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/inferredfromparametertype/EnumSourceInferredFromParameterType.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/sameasparametertype/EnumSourceSameAsParameterType.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/valuenecessary/EnumSourceValueNecessary.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java index 54c154261fe..31cbb842a23 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java @@ -89,7 +89,7 @@ final class ArchitectureRules { rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale()); rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale()); rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType()); - rules.add(enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType()); + rules.add(enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter()); rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic()); return List.copyOf(rules); } @@ -222,7 +222,7 @@ final class ArchitectureRules { JavaAnnotation conditionalAnnotation = item .getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); Map properties = conditionalAnnotation.getProperties(); - if (!properties.containsKey("type") && !properties.containsKey("name")) { + if (!hasProperty("type", properties) && !hasProperty("name", properties)) { conditionalAnnotation.get("value").ifPresent((value) -> { if (containsOnlySingleType((JavaType[]) value, item.getReturnType())) { addViolation(events, item, conditionalAnnotation.getDescription() @@ -233,16 +233,24 @@ final class ArchitectureRules { }); } - private static ArchRule enumSourceShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodParameterType() { + private static boolean hasProperty(String name, Map properties) { + Object property = properties.get(name); + if (property == null) { + return false; + } + return !property.getClass().isArray() || ((Object[]) property).length > 0; + } + + private static ArchRule enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter() { return ArchRuleDefinition.methods() .that() .areAnnotatedWith("org.junit.jupiter.params.provider.EnumSource") - .should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType()) + .should(notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter()) .allowEmptyShould(true); } - private static ArchCondition notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType() { - return check("not specify only a type that is the same as the method's parameter type", + private static ArchCondition notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter() { + return check("not have a value that is the same as the type of the method's first parameter", ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); } @@ -250,15 +258,13 @@ final class ArchitectureRules { ConditionEvents events) { JavaAnnotation enumSourceAnnotation = item .getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); - Map properties = enumSourceAnnotation.getProperties(); - if (properties.size() == 1 && item.getParameterTypes().size() == 1) { - enumSourceAnnotation.get("value").ifPresent((value) -> { - if (value.equals(item.getParameterTypes().get(0))) { - addViolation(events, item, enumSourceAnnotation.getDescription() - + " should not specify only a value that is the same as the method's parameter type"); - } - }); - } + enumSourceAnnotation.get("value").ifPresent((value) -> { + JavaType parameterType = item.getParameterTypes().get(0); + if (value.equals(parameterType)) { + addViolation(events, item, enumSourceAnnotation.getDescription() + + " should not specify a value that is the same as the type of the method's first parameter"); + } + }); } private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic() { diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java index f299aa596a9..20a0c177da6 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -57,6 +57,8 @@ class ArchitectureCheckTests { private static final String SPRING_CONTEXT = "org.springframework:spring-context:6.2.9"; + private static final String JUNIT_JUPITER = "org.junit.jupiter:junit-jupiter:5.12.0"; + private static final String SPRING_INTEGRATION_JMX = "org.springframework.integration:spring-integration-jmx:6.5.1"; private GradleBuild gradleBuild; @@ -270,6 +272,29 @@ class ArchitectureCheckTests { build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task); } + @Test + void whenEnumSourceValueIsInferredShouldSucceedAndWriteEmptyReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/inferredfromparametertype"); + build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST); + } + + @Test + void whenEnumSourceValueIsNotTheSameAsTypeOfMethodsFirstParameterShouldSucceedAndWriteEmptyReport() + throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/valuenecessary"); + build(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST); + } + + @Test + void whenEnumSourceValueIsSameAsTypeOfMethodsFirstParameterShouldFailAndWriteReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_TEST, "junit/enumsource/sameasparametertype"); + buildAndFail(this.gradleBuild.withDependencies(JUNIT_JUPITER), Task.CHECK_ARCHITECTURE_TEST, + "method ", + "should not have a value that is the same as the type of the method's first parameter"); + } + private void prepareTask(Task task, String... sourceDirectories) throws IOException { for (String sourceDirectory : sourceDirectories) { FileSystemUtils.copyRecursively( diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/inferredfromparametertype/EnumSourceInferredFromParameterType.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/inferredfromparametertype/EnumSourceInferredFromParameterType.java new file mode 100644 index 00000000000..c2fd99dfdf6 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/inferredfromparametertype/EnumSourceInferredFromParameterType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.architecture.junit.enumsource.inferredfromparametertype; + +import org.junit.jupiter.params.provider.EnumSource; + +class EnumSourceInferredFromParameterType { + + @EnumSource + void exampleMethod(Example example) { + + } + + enum Example { + + ONE, TWO, THREE + + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/sameasparametertype/EnumSourceSameAsParameterType.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/sameasparametertype/EnumSourceSameAsParameterType.java new file mode 100644 index 00000000000..1695833abf9 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/sameasparametertype/EnumSourceSameAsParameterType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.architecture.junit.enumsource.sameasparametertype; + +import org.junit.jupiter.params.provider.EnumSource; + +class EnumSourceSameAsParameterType { + + @EnumSource(Example.class) + void exampleMethod(Example example) { + + } + + enum Example { + + ONE, TWO, THREE + + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/valuenecessary/EnumSourceValueNecessary.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/valuenecessary/EnumSourceValueNecessary.java new file mode 100644 index 00000000000..b92f4d2bdd2 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/junit/enumsource/valuenecessary/EnumSourceValueNecessary.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.architecture.junit.enumsource.valuenecessary; + +import org.junit.jupiter.params.provider.EnumSource; + +class EnumSourceValueNecessary { + + @EnumSource(Example.class) + void exampleMethod(String thing, Example example) { + + } + + enum Example { + + ONE, TWO, THREE + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java index a4534b569d9..ba14483c021 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/sbom/SbomEndpointWebExtensionTests.java @@ -112,7 +112,7 @@ class SbomEndpointWebExtensionTests { } @ParameterizedTest - @EnumSource(value = SbomType.class, names = "UNKNOWN", mode = Mode.EXCLUDE) + @EnumSource(names = "UNKNOWN", mode = Mode.EXCLUDE) void shouldAutodetectFormats(SbomType type) throws IOException { String content = getSbomContent(type); assertThat(type.matches(content)).isTrue(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JacksonHttpMessageConvertersConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JacksonHttpMessageConvertersConfiguration.java index 1e17509aca9..e2eee7717a7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JacksonHttpMessageConvertersConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JacksonHttpMessageConvertersConfiguration.java @@ -45,10 +45,9 @@ class JacksonHttpMessageConvertersConfiguration { static class MappingJackson2HttpMessageConverterConfiguration { @Bean - @ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, - ignoredType = { - "org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", - "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) + @ConditionalOnMissingBean(ignoredType = { + "org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", + "org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { return new MappingJackson2HttpMessageConverter(objectMapper); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java index a6a83e54f36..16343faebc7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java @@ -59,7 +59,7 @@ public class ErrorWebFluxAutoConfiguration { } @Bean - @ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT) + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) @Order(-1) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties, ObjectProvider viewResolvers, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java index b89ccf296d8..d7374b0bef1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchDataSourceScriptDatabaseInitializerTests.java @@ -62,8 +62,8 @@ class BatchDataSourceScriptDatabaseInitializerTests { } @ParameterizedTest - @EnumSource(value = DatabaseDriver.class, mode = Mode.EXCLUDE, names = { "CLICKHOUSE", "FIREBIRD", "INFORMIX", - "JTDS", "PHOENIX", "REDSHIFT", "TERADATA", "TESTCONTAINERS", "UNKNOWN" }) + @EnumSource(mode = Mode.EXCLUDE, names = { "CLICKHOUSE", "FIREBIRD", "INFORMIX", "JTDS", "PHOENIX", "REDSHIFT", + "TERADATA", "TESTCONTAINERS", "UNKNOWN" }) void batchSchemaCanBeLocated(DatabaseDriver driver) throws SQLException { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); BatchProperties properties = new BatchProperties();