diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java index 2365f5b0a..306e2babc 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/RewriteConfig.java @@ -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(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java index 54e0a6a34..d1732e413 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/SpringJavaProblemType.java @@ -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; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java index e4b9e82bc..4bd5962ae 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java @@ -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() }; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java new file mode 100644 index 000000000..8102d9671 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/UnnecessarySpringExtensionReconciler.java @@ -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 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 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; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java index 8349d4425..ae04958fe 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AbstractRewriteJavaCodeAction.java @@ -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; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java index 025231385..13f3dc350 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNoPublicCodeAction.java @@ -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))) ); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java index 849e9e759..d364871b0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/ConvertAutowiredField.java @@ -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())))); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java index 17181e674..0464f3aab 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMapping.java @@ -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 )); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java index 3ea17ca2f..d6171ebe3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappings.java @@ -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> provideCodeActions(CodeActionContext context, TextDocument doc, IRegion region, IJavaProject project, CompilationUnit cu, ASTNode node) { return findAppropriateMethodDeclaration(node).map(m -> List.of( - Either.forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))), - Either.forRight(createCodeAction("Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true))) + Either.forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in file", List.of(doc.getId().getUri(), false))), + Either.forRight(createCodeAction(CodeActionKind.Refactor, "Replace @RequestMapping with @GetMapping etc. in project", List.of(doc.getId().getUri(), true))) )).orElse(Collections.emptyList()); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java new file mode 100644 index 000000000..789231c3b --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/UnnecessarySpringExtensionCodeAction.java @@ -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> 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> 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 createQuickfixForFile(String docUri) { + return Either.forRight(createCodeAction(CodeActionKind.QuickFix, + "Remove unnecessary @SpringExtension in file", List.of(docUri, false))); + } + + private Either 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()); + } + +}