No Autowired over constructor: account for SpringBootTest inherited

This commit is contained in:
aboyko
2023-03-02 21:45:40 -05:00
parent 52110d55e9
commit 9cf2d7ea89
3 changed files with 52 additions and 285 deletions

View File

@@ -1,123 +0,0 @@
/*******************************************************************************
* Copyright (c) 2022 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class AutowiredConstructorReconciler implements AnnotationReconciler {
public static final String REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR = "RemoveUnnecessaryConstructorAutowired";
private QuickfixRegistry quickfixRegistry;
public AutowiredConstructorReconciler(QuickfixRegistry quickfixRegistry) {
this.quickfixRegistry = quickfixRegistry;
}
@Override
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) {
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
if (version.getMajor() >= 2) {
getSingleAutowiredConstructorDeclaringType(node, typeBinding).ifPresent(type -> {
ReconcileProblemImpl problem = new ReconcileProblemImpl(Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR, "Unnecesary @Autowired", node.getStartPosition(), node.getLength());
QuickfixType quickfixType = quickfixRegistry.getQuickfixType(AutowiredConstructorReconciler.REMOVE_UNNECESSARY_AUTOWIRED_FROM_CONSTRUCTOR);
if (quickfixType != null) {
problem.addQuickfix(new QuickfixData<>(
quickfixType,
List.of(doc.getUri(), type.getQualifiedName()),
"Remove unnecessary @Autowired"
));
}
problemCollector.accept(problem);
});
}
}
static Optional<ITypeBinding> getSingleAutowiredConstructorDeclaringType(Annotation a, ITypeBinding type) {
if (type != null && Annotations.AUTOWIRED.equals(type.getQualifiedName())) {
if (a.getParent() instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) a.getParent();
IMethodBinding methodBinding = method.resolveBinding();
if (methodBinding != null && methodBinding.isConstructor()) {
ITypeBinding declaringType = methodBinding.getDeclaringClass();
if (declaringType != null && !isBootTestClass(declaringType) && isOnlyOneConstructor(declaringType)) {
return Optional.of(declaringType);
}
}
}
}
return Optional.empty();
}
private static boolean isOnlyOneConstructor(ITypeBinding t) {
int numberOfConstructors = 0;
if (!t.isInterface()) {
for (IMethodBinding m : t.getDeclaredMethods()) {
if (m.isConstructor()) {
numberOfConstructors++;
if (numberOfConstructors > 1) {
break;
}
}
}
}
return numberOfConstructors == 1;
}
private static boolean isBootTestClass(ITypeBinding typeBinding) {
for (IAnnotationBinding annotation : typeBinding.getAnnotations()) {
boolean found = AnnotationHierarchies.findTransitiveSuperAnnotationBindings(annotation).anyMatch(a -> {
if (a.getAnnotationType() != null) {
if ("org.springframework.test.context.BootstrapWith".equals(a.getAnnotationType().getQualifiedName())) {
for (IMemberValuePairBinding pair : a.getAllMemberValuePairs()) {
if (pair.getValue() instanceof ITypeBinding) {
ITypeBinding type = (ITypeBinding) pair.getValue();
if ("value".equals(pair.getName())
&& "org.springframework.boot.test.context.SpringBootTestContextBootstrapper".equals(type.getQualifiedName())) {
return true;
}
}
}
}
}
return false;
});
if (found) {
return true;
}
}
return false;
}
}

View File

@@ -1,113 +0,0 @@
/*******************************************************************************
* Copyright (c) 2022 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class BeanMethodNotPublicReconciler implements AnnotationReconciler {
private static final Logger log = LoggerFactory.getLogger(BeanMethodNotPublicReconciler.class);
public static final String REMOVE_PUBLIC_FROM_BEAN_METHOD = "RemovePublicFromBeanMethod";
private QuickfixRegistry quickfixRegistry;
public BeanMethodNotPublicReconciler(QuickfixRegistry quickfixRegistry) {
this.quickfixRegistry = quickfixRegistry;
}
@Override
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding,
IProblemCollector problemCollector) {
if (Annotations.BEAN.equals(typeBinding.getQualifiedName()) && node.getParent() instanceof MethodDeclaration) {
MethodDeclaration m = (MethodDeclaration) node.getParent();
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
if (version.getMajor() >= 2) {
IMethodBinding methodBinding = m.resolveBinding();
if (isNotOverridingPublicMethod(methodBinding)) {
ReconcileProblemImpl problem = ((List<?>)m.modifiers()).stream()
.filter(Modifier.class::isInstance)
.map(Modifier.class::cast)
.filter(modifier -> modifier.isPublic())
.findFirst()
.map(modifier -> new ReconcileProblemImpl(
Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "public @Bean method",
modifier.getStartPosition(), modifier.getLength()))
.orElse(new ReconcileProblemImpl(
Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, "public @Bean method",
m.getName().getStartPosition(), m.getName().getLength()));
QuickfixType quickfixType = quickfixRegistry.getQuickfixType(REMOVE_PUBLIC_FROM_BEAN_METHOD);
if (quickfixType != null) {
problem.addQuickfix(new QuickfixData<>(
quickfixType,
createParameters(doc, methodBinding),
"Remove public from @Bean method"
));
}
problemCollector.accept(problem);
}
}
}
}
private static final boolean isOverriding(IMethodBinding binding) {
try {
Field f = binding.getClass().getDeclaredField("binding");
f.setAccessible(true);
org.eclipse.jdt.internal.compiler.lookup.MethodBinding value = (org.eclipse.jdt.internal.compiler.lookup.MethodBinding) f.get(binding);
return value.isOverriding();
} catch (Exception e) {
log.error("", e);
}
return false;
}
public static final boolean isNotOverridingPublicMethod(IMethodBinding methodBinding) {
return !isOverriding(methodBinding) && (methodBinding.getModifiers() & Modifier.PUBLIC) != 0;
}
public static final List<String> createParameters(IDocument doc, IMethodBinding methodBinding) {
StringBuilder methodPattern = new StringBuilder(methodBinding.getDeclaringClass().getQualifiedName());
methodPattern.append(' ');
methodPattern.append(methodBinding.getName());
methodPattern.append('(');
methodPattern.append(Arrays.stream(methodBinding.getParameterTypes()).map(p -> p.getErasure().getQualifiedName()).collect(Collectors.joining(",")));
methodPattern.append(')');
return List.of(doc.getUri(), methodPattern.toString());
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 VMware, Inc.
* Copyright (c) 2022, 2023 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -18,6 +18,7 @@ import org.openrewrite.ExecutionContext;
import org.openrewrite.SourceFile;
import org.openrewrite.Tree;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.ClassDeclaration;
@@ -32,64 +33,67 @@ import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies;
import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
import org.springframework.ide.vscode.commons.rewrite.java.JavaMarkerVisitor;
public class NoAutowiredOnConstructorProblem implements RecipeCodeActionDescriptor {
private static final AnnotationMatcher BOOT_TEST_ANNOTATION_MATCHER = new AnnotationMatcher(
"@org.springframework.boot.test.context.SpringBootTest", true);
private static final String ID = "org.openrewrite.java.spring.NoAutowiredOnConstructor";
private static final String LABEL = "Remove Unnecessary @Autowired";
@Override
public JavaVisitor<ExecutionContext> getMarkerVisitor(ApplicationContext applicationContext) {
return new JavaMarkerVisitor<ExecutionContext>() {
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, context);
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext context) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, context);
int constructorCount = 0;
for (Statement s : cd.getBody().getStatements()) {
if (isConstructor(s)) {
constructorCount++;
if (constructorCount > 1) {
return cd;
}
}
}
FullyQualified type = TypeUtils.asFullyQualified(classDecl.getType());
if (type != null && isApplicableType(type)) {
return cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), s -> {
if (!isConstructor(s)) {
return s;
}
MethodDeclaration constructor = (MethodDeclaration) s;
String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri()
.toASCIIString();
FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId(), getId())
.withFix(new FixDescriptor(ID, List.of(uri), LABEL).withRecipeScope(RecipeScope.NODE)
.withRangeScope(getCursor().firstEnclosing(ClassDeclaration.class).getMarkers()
.findFirst(Range.class).get()));
constructor = constructor
.withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> {
if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) {
a = a.withMarkers(a.getMarkers().add(fixAssistMarker));
}
return a;
}));
return constructor;
})));
}
return cd;
}
int constructorCount = 0;
for(Statement s : cd.getBody().getStatements()) {
if(isConstructor(s)) {
constructorCount++;
if(constructorCount > 1) {
return cd;
}
}
}
FullyQualified type = TypeUtils.asFullyQualified(classDecl.getType());
if (type != null && isApplicableType(type)) {
return cd.withBody(cd.getBody().withStatements(
ListUtils.map(cd.getBody().getStatements(), s -> {
if(!isConstructor(s)) {
return s;
}
MethodDeclaration constructor = (MethodDeclaration) s;
String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri().toASCIIString();
FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId(), getId())
.withFix(
new FixDescriptor(ID, List.of(uri), LABEL)
.withRecipeScope(RecipeScope.NODE)
.withRangeScope(getCursor().firstEnclosing(ClassDeclaration.class).getMarkers().findFirst(Range.class).get())
);
constructor = constructor.withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> {
if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) {
a = a.withMarkers(a.getMarkers().add(fixAssistMarker));
}
return a;
}));
return constructor;
})
));
}
return cd;
}
private boolean isApplicableType(FullyQualified type) {
return !AnnotationHierarchies
.getTransitiveSuperAnnotations(type, fq -> fq.getFullyQualifiedName().startsWith("java."))
.contains("org.springframework.boot.test.context.SpringBootTest");
for (FullyQualified annotationType : type.getAnnotations()) {
if (BOOT_TEST_ANNOTATION_MATCHER.matchesAnnotationOrMetaAnnotation(annotationType)) {
return false;
}
}
FullyQualified superType = type.getSupertype();
return superType == null ? true : isApplicableType(superType);
}
};
@@ -104,10 +108,9 @@ public class NoAutowiredOnConstructorProblem implements RecipeCodeActionDescript
public Boot2JavaProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR;
}
private static boolean isConstructor(Statement s) {
return s instanceof J.MethodDeclaration && ((J.MethodDeclaration)s).isConstructor();
}
private static boolean isConstructor(Statement s) {
return s instanceof J.MethodDeclaration && ((J.MethodDeclaration) s).isConstructor();
}
}