Detect use of deprecated API
This commit is a best effort attempt at identifying the members that code generation invokes and might be deprecated. It introduces a CodeWarnings helper class that records warnings, with special handling for `@Deprecated`. See gh-29597
This commit is contained in:
@@ -26,6 +26,7 @@ import java.util.function.Supplier;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedMethod;
|
||||
@@ -52,6 +53,7 @@ import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Implementation;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.One;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.TestHierarchy.Two;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.test.io.support.MockSpringFactoriesLoader;
|
||||
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
|
||||
@@ -66,6 +68,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link BeanDefinitionMethodGenerator} and
|
||||
@@ -740,6 +743,32 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SuppressWarnings("deprecation")
|
||||
class DeprecationTests {
|
||||
|
||||
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
|
||||
.withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror");
|
||||
|
||||
@Test
|
||||
void generateBeanDefinitionMethodWithDeprecatedTargetClass() {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(DeprecatedBean.class);
|
||||
RegisteredBean registeredBean = registerBean(beanDefinition);
|
||||
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
|
||||
methodGeneratorFactory, registeredBean, null,
|
||||
Collections.emptyList());
|
||||
MethodReference method = generator.generateBeanDefinitionMethod(
|
||||
generationContext, beanRegistrationsCode);
|
||||
compileAndCheckWarnings(method);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(MethodReference methodReference) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, methodReference,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void testBeanDefinitionMethodInCurrentFile(Class<?> targetType, RootBeanDefinition beanDefinition) {
|
||||
RegisteredBean registeredBean = registerBean(new RootBeanDefinition(beanDefinition));
|
||||
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
|
||||
@@ -764,6 +793,10 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
}
|
||||
|
||||
private void compile(MethodReference method, BiConsumer<RootBeanDefinition, Compiled> result) {
|
||||
compile(TestCompiler.forSystem(), method, result);
|
||||
}
|
||||
|
||||
private void compile(TestCompiler testCompiler, MethodReference method, BiConsumer<RootBeanDefinition, Compiled> result) {
|
||||
this.beanRegistrationsCode.getTypeBuilder().set(type -> {
|
||||
CodeBlock methodInvocation = method.toInvokeCodeBlock(ArgumentCodeGenerator.none(),
|
||||
this.beanRegistrationsCode.getClassName());
|
||||
@@ -775,7 +808,7 @@ class BeanDefinitionMethodGeneratorTests {
|
||||
.addCode("return $L;", methodInvocation).build());
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(this.generationContext).compile(compiled ->
|
||||
testCompiler.with(this.generationContext).compile(compiled ->
|
||||
result.accept((RootBeanDefinition) compiled.getInstance(Supplier.class).get(), compiled));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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.beans.factory.aot;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.testfixture.beans.factory.aot.DeferredTypeBuilder;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalBean;
|
||||
import org.springframework.core.test.tools.Compiled;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.javapoet.MethodSpec;
|
||||
import org.springframework.javapoet.MethodSpec.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CodeWarnings}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CodeWarningsTests {
|
||||
|
||||
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
|
||||
.withCompilerOptions("-Xlint:all", "-Werror");
|
||||
|
||||
private final CodeWarnings codeWarnings;
|
||||
|
||||
private final TestGenerationContext generationContext;
|
||||
|
||||
CodeWarningsTests() {
|
||||
this.codeWarnings = new CodeWarnings();
|
||||
this.generationContext = new TestGenerationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerNoWarningDoesNotIncludeAnnotation() {
|
||||
compile(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = $S", String.class, "Hello");
|
||||
}, compiled -> assertThat(compiled.getSourceFile()).doesNotContain("@SuppressWarnings"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void registerWarningSuppressesIt() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
compile(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
|
||||
}, compiled -> assertThat(compiled.getSourceFile())
|
||||
.contains("@SuppressWarnings(\"deprecation\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
void registerSeveralWarningsSuppressesThem() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
this.codeWarnings.register("removal");
|
||||
compile(method -> {
|
||||
this.codeWarnings.suppress(method);
|
||||
method.addStatement("$T bean = new $T()", DeprecatedBean.class, DeprecatedBean.class);
|
||||
method.addStatement("$T another = new $T()", DeprecatedForRemovalBean.class, DeprecatedForRemovalBean.class);
|
||||
}, compiled -> assertThat(compiled.getSourceFile())
|
||||
.contains("@SuppressWarnings({ \"deprecation\", \"removal\" })"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
void detectDeprecationOnAnnotatedElementWithDeprecated() {
|
||||
this.codeWarnings.detectDeprecation(DeprecatedBean.class);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("deprecation");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("removal")
|
||||
void detectDeprecationOnAnnotatedElementWithDeprecatedForRemoval() {
|
||||
this.codeWarnings.detectDeprecation(DeprecatedForRemovalBean.class);
|
||||
assertThat(this.codeWarnings.getWarnings()).containsExactly("removal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringIncludeWarnings() {
|
||||
this.codeWarnings.register("deprecation");
|
||||
this.codeWarnings.register("rawtypes");
|
||||
assertThat(this.codeWarnings).hasToString("CodeWarnings[deprecation, rawtypes]");
|
||||
}
|
||||
|
||||
private void compile(Consumer<Builder> method,
|
||||
Consumer<Compiled> result) {
|
||||
DeferredTypeBuilder typeBuilder = new DeferredTypeBuilder();
|
||||
this.generationContext.getGeneratedClasses().addForFeature("TestCode", typeBuilder);
|
||||
typeBuilder.set(type -> {
|
||||
type.addModifiers(Modifier.PUBLIC);
|
||||
Builder methodBuilder = MethodSpec.methodBuilder("apply")
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
method.accept(methodBuilder);
|
||||
type.addMethod(methodBuilder.build());
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TEST_COMPILER.with(this.generationContext).compile(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import java.util.function.Supplier;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.assertj.core.api.ThrowingConsumer;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.GeneratedClass;
|
||||
@@ -44,6 +46,12 @@ import org.springframework.beans.testfixture.beans.factory.generator.InnerCompon
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.EnvironmentAwareComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.InnerComponentConfiguration.NoDependencyComponent;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.SimpleConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedConstructor;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalConstructor;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedForRemovalMemberConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.deprecation.DeprecatedMemberConfiguration;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolder;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.NumberHolderFactoryBean;
|
||||
import org.springframework.beans.testfixture.beans.factory.generator.factory.SampleFactory;
|
||||
@@ -57,6 +65,7 @@ import org.springframework.javapoet.ParameterizedTypeName;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
|
||||
/**
|
||||
* Tests for {@link InstanceSupplierCodeGenerator}.
|
||||
@@ -261,6 +270,108 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
.satisfies(hasMethodWithMode(ExecutableMode.INTROSPECT));
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SuppressWarnings("deprecation")
|
||||
class DeprecationTests {
|
||||
|
||||
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
|
||||
.withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror");
|
||||
|
||||
@Test
|
||||
@Disabled("Need to move to a separate method so that the warning can be suppressed")
|
||||
void generateWhenTargetClassIsDeprecated() {
|
||||
compileAndCheckWarnings(new RootBeanDefinition(DeprecatedBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetConstructorIsDeprecated() {
|
||||
compileAndCheckWarnings(new RootBeanDefinition(DeprecatedConstructor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodIsDeprecated() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(String.class)
|
||||
.setFactoryMethodOnBean("deprecatedString", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodParameterIsDeprecated() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(String.class)
|
||||
.setFactoryMethodOnBean("deprecatedParameter", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition());
|
||||
beanFactory.registerBeanDefinition("parameter", new RootBeanDefinition(DeprecatedBean.class));
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodReturnTypeIsDeprecated() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DeprecatedBean.class)
|
||||
.setFactoryMethodOnBean("deprecatedReturnType", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SuppressWarnings("removal")
|
||||
class DeprecationForRemovalTests {
|
||||
|
||||
private static final TestCompiler TEST_COMPILER = TestCompiler.forSystem()
|
||||
.withCompilerOptions("-Xlint:all", "-Xlint:-rawtypes", "-Werror");
|
||||
|
||||
@Test
|
||||
@Disabled("Need to move to a separate method so that the warning can be suppressed")
|
||||
void generateWhenTargetClassIsDeprecatedForRemoval() {
|
||||
compileAndCheckWarnings(new RootBeanDefinition(DeprecatedForRemovalBean.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetConstructorIsDeprecatedForRemoval() {
|
||||
compileAndCheckWarnings(new RootBeanDefinition(DeprecatedForRemovalConstructor.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodIsDeprecatedForRemoval() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(String.class)
|
||||
.setFactoryMethodOnBean("deprecatedString", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateWhenTargetFactoryMethodParameterIsDeprecatedForRemoval() {
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(String.class)
|
||||
.setFactoryMethodOnBean("deprecatedParameter", "config").getBeanDefinition();
|
||||
beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DeprecatedForRemovalMemberConfiguration.class).getBeanDefinition());
|
||||
beanFactory.registerBeanDefinition("parameter", new RootBeanDefinition(DeprecatedForRemovalBean.class));
|
||||
compileAndCheckWarnings(beanDefinition);
|
||||
}
|
||||
|
||||
private void compileAndCheckWarnings(BeanDefinition beanDefinition) {
|
||||
assertThatNoException().isThrownBy(() -> compile(TEST_COMPILER, beanDefinition,
|
||||
((instanceSupplier, compiled) -> {})));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ReflectionHints getReflectionHints() {
|
||||
return this.generationContext.getRuntimeHints().reflection();
|
||||
}
|
||||
@@ -285,6 +396,11 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
}
|
||||
|
||||
private void compile(BeanDefinition beanDefinition, BiConsumer<InstanceSupplier<?>, Compiled> result) {
|
||||
compile(TestCompiler.forSystem(), beanDefinition, result);
|
||||
}
|
||||
|
||||
private void compile(TestCompiler testCompiler, BeanDefinition beanDefinition,
|
||||
BiConsumer<InstanceSupplier<?>, Compiled> result) {
|
||||
|
||||
DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(this.beanFactory);
|
||||
freshBeanFactory.registerBeanDefinition("testBean", beanDefinition);
|
||||
@@ -306,8 +422,8 @@ class InstanceSupplierCodeGeneratorTests {
|
||||
.addStatement("return $L", generatedCode).build());
|
||||
});
|
||||
this.generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().with(this.generationContext).compile(compiled ->
|
||||
result.accept((InstanceSupplier<?>) compiled.getInstance(Supplier.class).get(), compiled));
|
||||
testCompiler.with(this.generationContext).compile(compiled -> result.accept(
|
||||
(InstanceSupplier<?>) compiled.getInstance(Supplier.class).get(), compiled));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user