JUnit SpringExtension code action and quick fix

This commit is contained in:
BoykoAlex
2022-05-09 15:37:07 -04:00
parent 8102c19f8a
commit 36f7198082
10 changed files with 264 additions and 12 deletions

View File

@@ -24,6 +24,7 @@ import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNo
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.ConvertAutowiredField;
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMapping;
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappings;
import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction;
import org.springframework.ide.vscode.boot.java.rewrite.quickfix.AutowiredConstructorQuickFixHandler;
import org.springframework.ide.vscode.boot.java.rewrite.quickfix.BeanMethodNoPublicQuickFixHandler;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -73,6 +74,14 @@ public class RewriteConfig implements InitializingBean {
return new BeanMethodsNoPublicCodeAction(server, projectFinder, rewriteRefactorings, orCuCache);
}
@ConditionalOnClass({org.openrewrite.java.spring.boot2.UnnecessarySpringExtension.class})
@Bean
UnnecessarySpringExtensionCodeAction unnecessarySpringExtension(SimpleLanguageServer server, JavaProjectFinder projectFinder,
RewriteRefactorings rewriteRefactorings, RewriteRecipeRepository recipesRepo,
ORCompilationUnitCache orCuCache) {
return new UnnecessarySpringExtensionCodeAction(server, projectFinder, rewriteRefactorings, orCuCache);
}
@Override
public void afterPropertiesSet() throws Exception {
QuickfixRegistry registry = server.getQuickfixRegistry();

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* Copyright (c) 2020, 2022 Pivotal, 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
@@ -24,9 +24,11 @@ public enum SpringJavaProblemType implements ProblemType {
JAVA_SPEL_EXPRESSION_SYNTAX(ERROR, "SpEL parser raised a ParseException", "SpEL Expression Syntax"),
JAVA_AUTOWIRED_CONSTRUCTOR(WARNING, "Unnecessary @Autowired over the only constructor", "Unnecessary @Autowired"),
JAVA_AUTOWIRED_CONSTRUCTOR(WARNING, "Unnecessary `@Autowired` over the only constructor", "Unnecessary `@Autowired`"),
JAVA_PUBLIC_BEAN_METHOD(HINT, "Public modifier on @Bean method. They no longer have to be public visibility to be usable by Spring.", "public @Bean method");
JAVA_PUBLIC_BEAN_METHOD(HINT, "Public modifier on `@Bean` method. They no longer have to be public visibility to be usable by Spring.", "public `@Bean` method"),
JAVA_TEST_SPRING_EXTENSION(WARNING, "`@SpringBootTest` and all test slice annotations already applies `@SpringExtension` as of Spring Boot 2.1.0.", "Unnecessary `@SpringExtension`");
private final ProblemSeverity defaultSeverity;
private String description;

View File

@@ -24,6 +24,7 @@ import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationParamRecon
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredConstructorReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.boot.java.value.Constants;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -92,7 +93,8 @@ public class BootJavaReconcileEngine implements IReconcileEngine {
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
new AutowiredConstructorReconciler(quickfixRegistry),
new BeanMethodNotPublicReconciler(quickfixRegistry)
new BeanMethodNotPublicReconciler(quickfixRegistry),
new UnnecessarySpringExtensionReconciler()
};
}

View File

@@ -0,0 +1,121 @@
/*******************************************************************************
* 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.Arrays;
import java.util.List;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.springframework.ide.vscode.boot.java.SpringJavaProblemType;
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.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class UnnecessarySpringExtensionReconciler implements AnnotationReconciler {
private static final List<String> SPRING_BOOT_TEST_ANNOTATIONS = Arrays.asList(
"org.springframework.boot.test.context.SpringBootTest",
"org.springframework.boot.test.autoconfigure.jdbc.JdbcTest",
"org.springframework.boot.test.autoconfigure.web.client.RestClientTest",
"org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest",
"org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest",
"org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest",
"org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest",
"org.springframework.boot.test.autoconfigure.jooq.JooqTest",
"org.springframework.boot.test.autoconfigure.json.JsonTest",
"org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest",
"org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest",
"org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest",
"org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest",
"org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest",
"org.springframework.boot.test.autoconfigure.data.r2dbc.DataR2dbcTest",
"org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest"
);
private static final String EXTEND_WITH_ANNOTATION = "org.junit.jupiter.api.extension.ExtendWith";
private static final String SPRING_EXTENSION_ANNOTATION = "org.springframework.test.context.junit.jupiter.SpringExtension";
@Override
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding,
IProblemCollector problemCollector) {
if (isUnnecessarySpringExtensionAnnotation(project, node, typeBinding)) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(
SpringJavaProblemType.JAVA_TEST_SPRING_EXTENSION, "Unnecessary @SpringExtension",
node.getStartPosition(), node.getLength());
problemCollector.accept(problem);
}
}
public static boolean isUnnecessarySpringExtensionAnnotation(IJavaProject project, Annotation node, ITypeBinding typeBinding) {
if (EXTEND_WITH_ANNOTATION.equals(typeBinding.getQualifiedName())) {
Version v = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
// Since Boot 2.1
if ((v.getMajor() == 2 && v.getMinor() >= 1) || v.getMajor() > 2) {
return hasSpringTestSiblingAnnotation(node) && hasSpringExtensionAnnotationParameter(node);
}
}
return false;
}
private static boolean hasSpringTestSiblingAnnotation(Annotation node) {
if (node.getParent() instanceof TypeDeclaration) {
TypeDeclaration typeDecl = ((TypeDeclaration) node.getParent());
for (Object m : typeDecl.modifiers()) {
if (m instanceof Annotation) {
IAnnotationBinding annotationBinding = ((Annotation)m).resolveAnnotationBinding();
if (annotationBinding != null) {
ITypeBinding annotationType = annotationBinding.getAnnotationType();
if (annotationType != null && SPRING_BOOT_TEST_ANNOTATIONS.contains(annotationBinding.getAnnotationType().getQualifiedName())) {
return true;
}
}
}
}
}
return false;
}
@SuppressWarnings("unchecked")
private static boolean hasSpringExtensionAnnotationParameter(Annotation node) {
if (node instanceof SingleMemberAnnotation) {
return isSpringExtensionExpression(((SingleMemberAnnotation) node).getValue());
} else if (node instanceof NormalAnnotation) {
List<MemberValuePair> params = ((NormalAnnotation) node).values();
for (MemberValuePair param : params) {
if ("value".equals(param.getName().getIdentifier())) {
return isSpringExtensionExpression(param.getValue());
}
}
}
return false;
}
private static boolean isSpringExtensionExpression(Expression o) {
if (o instanceof TypeLiteral) {
ITypeBinding binding = ((TypeLiteral) o).getType().resolveBinding();
return binding != null && SPRING_EXTENSION_ANNOTATION.equals(binding.getQualifiedName());
}
return false;
}
}

View File

@@ -61,9 +61,9 @@ public abstract class AbstractRewriteJavaCodeAction implements JavaCodeAction {
rewriteRefactorings.addRefactoring(codeActionId, this::perform);
}
protected CodeAction createCodeAction(String title, List<?> arguments) {
protected CodeAction createCodeAction(String kind, String title, List<?> arguments) {
CodeAction ca = new CodeAction();
ca.setKind(CodeActionKind.Refactor);
ca.setKind(kind);
ca.setTitle(title);
ca.setData(new RewriteRefactorings.Data(codeActionId, arguments));
return ca;

View File

@@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
@@ -65,8 +66,8 @@ public class BeanMethodsNoPublicCodeAction extends AbstractRewriteJavaCodeAction
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
if (version.getMajor() >= 2) {
return List.of(
Either.forRight(createCodeAction(REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE, List.of(doc.getUri(), false))),
Either.forRight(createCodeAction(REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT, List.of(doc.getUri(), true)))
Either.forRight(createCodeAction(CodeActionKind.Refactor, REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_FILE, List.of(doc.getUri(), false))),
Either.forRight(createCodeAction(CodeActionKind.Refactor, REMOVE_PUBLIC_FROM_BEAN_METHODS_IN_PROJECT, List.of(doc.getUri(), true)))
);
}
}

View File

@@ -24,6 +24,7 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
@@ -88,7 +89,7 @@ public class ConvertAutowiredField extends AbstractRewriteJavaCodeAction {
if (autowired.isPresent()) {
return List.of(Either.forRight(createCodeAction("Convert into Constructor Parameter",
return List.of(Either.forRight(createCodeAction(CodeActionKind.Refactor, "Convert into Constructor Parameter",
List.of(doc.getId().getUri(),
((TypeDeclaration) fd.getParent()).resolveBinding().getQualifiedName(),
((VariableDeclarationFragment) fd.fragments().get(0)).getName().getIdentifier()))));

View File

@@ -22,6 +22,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
@@ -92,7 +93,7 @@ public class NoRequestMapping extends NoRequestMappings {
sb.append(')');
methodMatcher = sb.toString();
}
return createCodeAction("Replace single @RequestMapping with @GetMapping etc.", List.of(
return createCodeAction(CodeActionKind.Refactor, "Replace single @RequestMapping with @GetMapping etc.", List.of(
doc.getId().getUri(),
methodMatcher
));

View File

@@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
@@ -74,8 +75,8 @@ public class NoRequestMappings extends AbstractRewriteJavaCodeAction {
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project,
CompilationUnit cu, ASTNode node) {
return findAppropriateMethodDeclaration(node).map(m -> List.of(
Either.<Command, CodeAction>forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))),
Either.<Command, CodeAction>forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true)))
Either.<Command, CodeAction>forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))),
Either.<Command, CodeAction>forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true)))
)).orElse(Collections.emptyList());
}

View File

@@ -0,0 +1,114 @@
/*******************************************************************************
* 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.rewrite.codeaction;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionCapabilities;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.openrewrite.java.spring.boot2.UnnecessarySpringExtension;
import org.springframework.ide.vscode.boot.java.reconcilers.UnnecessarySpringExtensionReconciler;
import org.springframework.ide.vscode.boot.java.rewrite.ORCompilationUnitCache;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient.Client;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
public class UnnecessarySpringExtensionCodeAction extends AbstractRewriteJavaCodeAction {
private static final String CODE_ACTION_ID = "RemoveUnnecessarySpringExtension";
public UnnecessarySpringExtensionCodeAction(SimpleLanguageServer server, JavaProjectFinder projectFinder,
RewriteRefactorings rewriteRefactorings, ORCompilationUnitCache orCuCache) {
super(server, projectFinder, rewriteRefactorings, orCuCache, CODE_ACTION_ID);
}
protected boolean isSupported(CodeActionCapabilities capabilities, CodeActionContext context) {
// Default case is anything non-quick fix related.
if (isResolve(capabilities, "edit")) {
if (context.getOnly() != null) {
return context.getOnly().contains(CodeActionKind.Refactor) || context.getOnly().contains(CodeActionKind.QuickFix);
} else {
return true;
}
}
return false;
}
@Override
protected List<Either<Command, CodeAction>> provideCodeActions(CodeActionContext context, TextDocument doc,
IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) {
for (; node != null && !(node instanceof Annotation); node = node.getParent()) {
// nothing
}
if (node instanceof Annotation) {
Annotation a = (Annotation) node;
ITypeBinding binding = a.resolveTypeBinding();
if (binding != null && UnnecessarySpringExtensionReconciler.isUnnecessarySpringExtensionAnnotation(project,
a, binding)) {
Builder<Either<Command, CodeAction>> listBuilder = ImmutableList.builder();
if (context.getOnly() == null) {
if (LspClient.currentClient() == Client.ECLIPSE) {
if (context.getDiagnostics().isEmpty()) {
listBuilder.add(createRefactoringForProject(doc.getUri()));
} else {
listBuilder.add(createQuickfixForFile(doc.getUri()));
}
} else {
listBuilder.add(createQuickfixForFile(doc.getUri()));
listBuilder.add(createRefactoringForProject(doc.getUri()));
}
} else {
if (context.getOnly().contains(CodeActionKind.QuickFix)) {
listBuilder.add(createQuickfixForFile(doc.getUri()));
}
if (context.getOnly().contains(CodeActionKind.Refactor)) {
listBuilder.add(createRefactoringForProject(doc.getUri()));
}
}
return listBuilder.build();
}
}
return null;
}
private Either<Command, CodeAction> createQuickfixForFile(String docUri) {
return Either.forRight(createCodeAction(CodeActionKind.QuickFix,
"Remove unnecessary @SpringExtension in file", List.of(docUri, false)));
}
private Either<Command, CodeAction> createRefactoringForProject(String docUri) {
return Either.forRight(createCodeAction(CodeActionKind.Refactor,
"Remove unnecessary @SpringExtension in project", List.of(docUri, true)));
}
@Override
public WorkspaceEdit perform(List<?> args) {
return perform(args, () -> new UnnecessarySpringExtension());
}
}