Improve handling of annotation properties in architecture checks
Closes gh-47437
This commit is contained in:
		
							parent
							
								
									61acc52137
								
							
						
					
					
						commit
						3ba61c3fa7
					
				|  | @ -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<JavaMethod> conditionalAnnotation = item | ||||
| 				.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean"); | ||||
| 			Map<String, Object> 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<String, Object> 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<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType() { | ||||
| 		return check("not specify only a type that is the same as the method's parameter type", | ||||
| 	private static ArchCondition<? super JavaMethod> notHaveValueThatIsTheSameAsTheTypeOfTheMethodsFirstParameter() { | ||||
| 		return check("not have a value that is the same as the type of the method's first parameter", | ||||
| 				ArchitectureRules::notSpecifyOnlyATypeThatIsTheSameAsTheMethodParameterType); | ||||
| 	} | ||||
| 
 | ||||
|  | @ -250,16 +258,14 @@ final class ArchitectureRules { | |||
| 			ConditionEvents events) { | ||||
| 		JavaAnnotation<JavaMethod> enumSourceAnnotation = item | ||||
| 			.getAnnotationOfType("org.junit.jupiter.params.provider.EnumSource"); | ||||
| 		Map<String, Object> properties = enumSourceAnnotation.getProperties(); | ||||
| 		if (properties.size() == 1 && item.getParameterTypes().size() == 1) { | ||||
| 		enumSourceAnnotation.get("value").ifPresent((value) -> { | ||||
| 				if (value.equals(item.getParameterTypes().get(0))) { | ||||
| 			JavaType parameterType = item.getParameterTypes().get(0); | ||||
| 			if (value.equals(parameterType)) { | ||||
| 				addViolation(events, item, enumSourceAnnotation.getDescription() | ||||
| 							+ " should not specify only a value that is the same as the method's parameter type"); | ||||
| 						+ " should not specify a value that is the same as the type of the method's first parameter"); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| 	} | ||||
| 
 | ||||
| 	private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic() { | ||||
| 		return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and() | ||||
|  |  | |||
|  | @ -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 <org.springframework.boot.build.architecture.junit.enumsource.sameasparametertype" | ||||
| 						+ ".EnumSourceSameAsParameterType.exampleMethod(org.springframework.boot.build." | ||||
| 						+ "architecture.junit.enumsource.sameasparametertype.EnumSourceSameAsParameterType$Example)>", | ||||
| 				"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( | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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 | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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 | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -45,8 +45,7 @@ class JacksonHttpMessageConvertersConfiguration { | |||
| 	static class MappingJackson2HttpMessageConverterConfiguration { | ||||
| 
 | ||||
| 		@Bean | ||||
| 		@ConditionalOnMissingBean(value = MappingJackson2HttpMessageConverter.class, | ||||
| 				ignoredType = { | ||||
| 		@ConditionalOnMissingBean(ignoredType = { | ||||
| 				"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter", | ||||
| 				"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" }) | ||||
| 		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) { | ||||
|  |  | |||
|  | @ -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<ViewResolver> viewResolvers, | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue