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