No Autowired over constructor: account for SpringBootTest inherited
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user