Command for Data Query method CodeLens and CodeAction
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.reconcile;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class BasicCollector<T> implements ICollector<T> {
|
||||
|
||||
private final Collection<T> collection;
|
||||
|
||||
public BasicCollector(Collection<T> collection) {
|
||||
this.collection = collection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginCollecting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endCollecting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(T t) {
|
||||
collection.add(t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.reconcile;
|
||||
|
||||
public interface ICollector<T> {
|
||||
|
||||
void beginCollecting();
|
||||
void endCollecting();
|
||||
void accept(T t);
|
||||
|
||||
/**
|
||||
* Optional for both implementors and callers.
|
||||
* <p/>
|
||||
* This method optionally allows callers to do partial collection between the
|
||||
* start and end collecting, and can be called numerous times. The caller is
|
||||
* responsible to decide when and how often these checkpoints are invoked during
|
||||
* a collecting session.
|
||||
* <p/>
|
||||
* For implementors, this optional support handles cases where problems need to be processed in
|
||||
* intermediate phases between the start and end collecting stages, and if
|
||||
* implemented, should support multiple checkpoint invocations.
|
||||
*/
|
||||
default void checkPointCollecting() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2014, 2025 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
|
||||
@@ -10,27 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.languageserver.reconcile;
|
||||
|
||||
public interface IProblemCollector {
|
||||
|
||||
void beginCollecting();
|
||||
void endCollecting();
|
||||
void accept(ReconcileProblem problem);
|
||||
|
||||
/**
|
||||
* Optional for both implementors and callers.
|
||||
* <p/>
|
||||
* This method optionally allows callers to do partial collection between the
|
||||
* start and end collecting, and can be called numerous times. The caller is
|
||||
* responsible to decide when and how often these checkpoints are invoked during
|
||||
* a collecting session.
|
||||
* <p/>
|
||||
* For implementors, this optional support handles cases where problems need to be processed in
|
||||
* intermediate phases between the start and end collecting stages, and if
|
||||
* implemented, should support multiple checkpoint invocations.
|
||||
*/
|
||||
default void checkPointCollecting() {
|
||||
|
||||
}
|
||||
public interface IProblemCollector extends ICollector<ReconcileProblem> {
|
||||
|
||||
/**
|
||||
* Problem collector that simply ignores/discards anything passed to it.
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.openrewrite.ExecutionContext;
|
||||
import org.openrewrite.NlsRewrite.Description;
|
||||
import org.openrewrite.NlsRewrite.DisplayName;
|
||||
import org.openrewrite.Option;
|
||||
import org.openrewrite.Preconditions;
|
||||
import org.openrewrite.Recipe;
|
||||
import org.openrewrite.Tree;
|
||||
import org.openrewrite.TreeVisitor;
|
||||
import org.openrewrite.java.AddOrUpdateAnnotationAttribute;
|
||||
import org.openrewrite.java.JavaIsoVisitor;
|
||||
import org.openrewrite.java.MethodMatcher;
|
||||
import org.openrewrite.java.search.DeclaresMethod;
|
||||
import org.openrewrite.java.tree.J;
|
||||
import org.openrewrite.java.tree.J.Annotation;
|
||||
import org.openrewrite.java.tree.J.MethodDeclaration;
|
||||
import org.openrewrite.java.tree.JavaType;
|
||||
import org.openrewrite.java.tree.Space;
|
||||
import org.openrewrite.java.tree.TypeUtils;
|
||||
import org.openrewrite.marker.Markers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class AddAnnotationOverMethod extends Recipe {
|
||||
|
||||
public record Attribute(String name, String value) {}
|
||||
|
||||
@Override
|
||||
public @DisplayName String getDisplayName() {
|
||||
return "Add annotation over method";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Description String getDescription() {
|
||||
return "Add annotation over method.";
|
||||
}
|
||||
|
||||
@Option(description = "Method pattern", example = "com.example.Person setAge(int)")
|
||||
private String method;
|
||||
|
||||
@Option(description = "Annotation type")
|
||||
private String annotationType;
|
||||
|
||||
@Nullable
|
||||
@Option(description = "Annotation attributes", required = false)
|
||||
private List<Attribute> attributes;
|
||||
|
||||
@JsonCreator
|
||||
public AddAnnotationOverMethod(
|
||||
@JsonProperty("method") String method,
|
||||
@JsonProperty("annotationType") String annotationType,
|
||||
@JsonProperty("attributes") @Nullable List<Attribute> attributes) {
|
||||
this.method = method;
|
||||
this.annotationType = annotationType;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeVisitor<?, ExecutionContext> getVisitor() {
|
||||
final MethodMatcher matcher = new MethodMatcher(method);
|
||||
return Preconditions.check(new DeclaresMethod<>(matcher), new JavaIsoVisitor<>() {
|
||||
@Override
|
||||
public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) {
|
||||
MethodDeclaration m = super.visitMethodDeclaration(method, ctx);
|
||||
if (matcher.matches(m.getMethodType())) {
|
||||
Optional<Annotation> optAnnotation = m.getLeadingAnnotations().stream().filter(a -> TypeUtils.isOfClassType(a.getType(), annotationType)).findFirst();
|
||||
if (optAnnotation.isEmpty()) {
|
||||
List<J.Annotation> annotations = new ArrayList<>(m.getLeadingAnnotations());
|
||||
JavaType.ShallowClass at = JavaType.ShallowClass.build(annotationType);
|
||||
J.Annotation annotation = new J.Annotation(
|
||||
Tree.randomId(),
|
||||
Space.EMPTY,
|
||||
Markers.EMPTY,
|
||||
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), at.getClassName(), at, null),
|
||||
null);
|
||||
annotations.add(autoFormat(annotation, ctx));
|
||||
m = m.withLeadingAnnotations(annotations);
|
||||
m = autoFormat(m, m.getName(), ctx, getCursor().getParent());
|
||||
maybeAddImport(annotationType);
|
||||
optAnnotation = Optional.of(annotation);
|
||||
}
|
||||
if (attributes != null) {
|
||||
for (Attribute attr : attributes) {
|
||||
m = (MethodDeclaration) new AddOrUpdateAnnotationAttribute(annotationType, attr.name(),
|
||||
attr.value(), true, false).getVisitor().visit(m, ctx, getCursor().getParent());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return m;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.rewrite.java;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openrewrite.java.Assertions;
|
||||
import org.openrewrite.test.RewriteTest;
|
||||
|
||||
public class AddAnnotationOverMethodTest implements RewriteTest {
|
||||
|
||||
@Test
|
||||
void addAnnotationToMethod() {
|
||||
rewriteRun(
|
||||
spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected Text\"")))),
|
||||
Assertions.java(
|
||||
"""
|
||||
package demo;
|
||||
interface A {
|
||||
void foo();
|
||||
}
|
||||
""",
|
||||
"""
|
||||
package demo;
|
||||
interface A {
|
||||
@Deprecated("Expected Text")
|
||||
void foo();
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addAnnotationToMethodWithAnnotation() {
|
||||
rewriteRun(
|
||||
spec -> spec.recipe(new AddAnnotationOverMethod("demo.A foo()", "java.lang.Deprecated", List.of(new AddAnnotationOverMethod.Attribute("value", "\"Expected Text\"")))),
|
||||
Assertions.java(
|
||||
"""
|
||||
package demo;
|
||||
class A {
|
||||
@SuppressWarnings("null")
|
||||
void foo() {
|
||||
System.out.println("foo");
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
package demo;
|
||||
class A {
|
||||
@SuppressWarnings("null")
|
||||
@Deprecated("Expected Text")
|
||||
void foo() {
|
||||
System.out.println("foo");
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -17,6 +18,8 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex;
|
||||
import org.springframework.ide.vscode.boot.java.codeaction.JdtAstCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.codeaction.JdtCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronExpressionsInlayHintsProvider;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.cron.CronSemanticTokens;
|
||||
@@ -56,6 +59,7 @@ import org.springframework.ide.vscode.boot.java.spel.JdtSpelReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.spel.JdtSpelSemanticTokensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.spel.SpelReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@@ -191,4 +195,7 @@ public class JdtConfig {
|
||||
return new JdtCronReconciler(cronReconciler);
|
||||
}
|
||||
|
||||
@Bean JdtCodeActionHandler jdtCodeActionHandler(CompilationUnitCache cuCache, Collection<JdtAstCodeActionProvider> providers) {
|
||||
return new JdtCodeActionHandler(cuCache, providers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,13 @@ package org.springframework.ide.vscode.boot.app;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryAotMetadataService;
|
||||
import org.springframework.ide.vscode.boot.java.data.QueryMethodCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.ReconcileProblemCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.SpringBootUpgrade;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
@@ -35,13 +36,18 @@ public class RewriteConfig {
|
||||
}
|
||||
|
||||
@ConditionalOnBean(RewriteRecipeRepository.class)
|
||||
@Bean RewriteCodeActionHandler rewriteCodeActionHandler(CompilationUnitCache cuCache, BootJavaConfig config, JdtReconciler jdtReconciler, SimpleLanguageServer server) {
|
||||
return new RewriteCodeActionHandler(cuCache, config, jdtReconciler, server.getQuickfixRegistry(), server.getDiagnosticSeverityProvider());
|
||||
@Bean ReconcileProblemCodeActionProvider reconcileProblemCodeActionProvider(JdtReconciler reconciler, SimpleLanguageServer server) {
|
||||
return new ReconcileProblemCodeActionProvider(reconciler, server.getDiagnosticSeverityProvider());
|
||||
}
|
||||
|
||||
@ConditionalOnBean(RewriteRecipeRepository.class)
|
||||
@Bean SpringBootUpgrade springBootUpgrade(SimpleLanguageServer server, RewriteRecipeRepository recipeRepo, JavaProjectFinder projectFinder) {
|
||||
return new SpringBootUpgrade(server, recipeRepo, projectFinder);
|
||||
}
|
||||
|
||||
@ConditionalOnBean(RewriteRefactorings.class)
|
||||
@Bean QueryMethodCodeActionProvider queryMethodCodeActionProvider(DataRepositoryAotMetadataService dataRepoAotService, RewriteRefactorings refactorings) {
|
||||
return new QueryMethodCodeActionProvider(dataRepoAotService, refactorings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolP
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerCodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteHighlightProdivder;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.boot.java.spel.SpelSemanticTokens;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
|
||||
@@ -181,7 +182,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
spelSemanticTokens = appContext.getBean(SpelSemanticTokens.class);
|
||||
dataRepositoryAotMetadataService = appContext.getBean(DataRepositoryAotMetadataService.class);
|
||||
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens, dataRepositoryAotMetadataService);
|
||||
codeLensHandler = createCodeLensEngine(springIndex, projectFinder, server, spelSemanticTokens, dataRepositoryAotMetadataService, appContext.getBean(RewriteRefactorings.class));
|
||||
|
||||
highlightsEngine = createDocumentHighlightEngine(appContext);
|
||||
documents.onDocumentHighlight(highlightsEngine);
|
||||
@@ -316,12 +317,12 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
}
|
||||
|
||||
protected BootJavaCodeLensEngine createCodeLensEngine(SpringMetamodelIndex springIndex, JavaProjectFinder projectFinder, SimpleLanguageServer server,
|
||||
SpelSemanticTokens spelSemanticTokens, DataRepositoryAotMetadataService repositoryAotMetadataService) {
|
||||
SpelSemanticTokens spelSemanticTokens, DataRepositoryAotMetadataService repositoryAotMetadataService, RewriteRefactorings refactorings) {
|
||||
|
||||
Collection<CodeLensProvider> codeLensProvider = new ArrayList<>();
|
||||
codeLensProvider.add(new WebfluxHandlerCodeLensProvider(springIndex));
|
||||
codeLensProvider.add(new CopilotCodeLensProvider(projectFinder, server, spelSemanticTokens));
|
||||
codeLensProvider.add(new DataRepositoryAotMetadataCodeLensProvider(projectFinder, repositoryAotMetadataService));
|
||||
codeLensProvider.add(new DataRepositoryAotMetadataCodeLensProvider(projectFinder, repositoryAotMetadataService, refactorings));
|
||||
|
||||
return new BootJavaCodeLensEngine(this, codeLensProvider);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.codeaction;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ICollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public interface JdtAstCodeActionProvider {
|
||||
|
||||
boolean isApplicable(IJavaProject project);
|
||||
|
||||
ASTVisitor createVisitor(CancelChecker cancelToken, IJavaProject project, URI docURI, CompilationUnit cu, TextDocument doc, IRegion region, ICollector<CodeAction> collector);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.codeaction;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.CodeActionResolveSupportCapabilities;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.CompositeASTVisitor;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.BasicCollector;
|
||||
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.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class JdtCodeActionHandler implements JavaCodeActionHandler {
|
||||
|
||||
private static Logger log = LoggerFactory.getLogger(JdtCodeActionHandler.class);
|
||||
|
||||
final private CompilationUnitCache cuCache;
|
||||
final private Collection<JdtAstCodeActionProvider> providers;
|
||||
|
||||
public JdtCodeActionHandler(CompilationUnitCache cuCache, Collection<JdtAstCodeActionProvider> providers) {
|
||||
this.cuCache = cuCache;
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
protected static boolean isResolve(CodeActionCapabilities capabilities, String property) {
|
||||
if (capabilities != null) {
|
||||
CodeActionResolveSupportCapabilities resolveSupport = capabilities.getResolveSupport();
|
||||
if (resolveSupport != null) {
|
||||
List<String> properties = resolveSupport.getProperties();
|
||||
return properties != null && properties.contains(property);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private 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);
|
||||
} else {
|
||||
if (LspClient.currentClient() == Client.ECLIPSE) {
|
||||
// Eclipse would have no diagnostics in the context for QuickAssists refactoring. Diagnostics will be around for QuickFix only
|
||||
return context.getDiagnostics().isEmpty();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Either<Command, CodeAction>> handle(IJavaProject project, CancelChecker cancelToken,
|
||||
CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) {
|
||||
try {
|
||||
|
||||
URI uri = URI.create(doc.getUri());
|
||||
|
||||
if (isSupported(capabilities, context)) {
|
||||
|
||||
List<JdtAstCodeActionProvider> applicableProviders = providers.stream().filter(p -> p.isApplicable(project)).toList();
|
||||
if (!applicableProviders.isEmpty()) {
|
||||
List<CodeAction> codeActions = cuCache.withCompilationUnit(project, uri, cu -> {
|
||||
if (cu != null) {
|
||||
try {
|
||||
List<CodeAction> cas = new ArrayList<>();
|
||||
BasicCollector<CodeAction> codeActionsCollector = new BasicCollector<>(cas);
|
||||
|
||||
CompositeASTVisitor v = new CompositeASTVisitor();
|
||||
for (JdtAstCodeActionProvider p : applicableProviders) {
|
||||
v.add(p.createVisitor(cancelToken, project, uri, cu, doc, region, codeActionsCollector));
|
||||
}
|
||||
|
||||
codeActionsCollector.beginCollecting();
|
||||
|
||||
cu.accept(v);
|
||||
|
||||
codeActionsCollector.endCollecting();
|
||||
|
||||
return cas;
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
return codeActions.stream().map(ca -> Either.<Command, CodeAction>forRight(ca)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom - initial API and implementation
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IAnnotationBinding;
|
||||
@@ -25,8 +30,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.java.Annotations;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
|
||||
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.rewrite.config.RecipeScope;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.AddAnnotationOverMethod;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
@@ -39,10 +48,12 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid
|
||||
|
||||
private final DataRepositoryAotMetadataService repositoryMetadataService;
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final RewriteRefactorings refactorings;
|
||||
|
||||
public DataRepositoryAotMetadataCodeLensProvider(JavaProjectFinder projectFinder, DataRepositoryAotMetadataService repositoryMetadataService) {
|
||||
public DataRepositoryAotMetadataCodeLensProvider(JavaProjectFinder projectFinder, DataRepositoryAotMetadataService repositoryMetadataService, RewriteRefactorings refactorings) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.repositoryMetadataService = repositoryMetadataService;
|
||||
this.refactorings = refactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,59 +66,65 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List<CodeLens> resultAccumulator) {
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
IMethodBinding methodBinding = node.resolveBinding();
|
||||
|
||||
static boolean isDataQuaryNonAnnotatedMethodCandidate(IMethodBinding methodBinding) {
|
||||
if (methodBinding == null || methodBinding.getDeclaringClass() == null
|
||||
|| methodBinding.getMethodDeclaration() == null
|
||||
|| methodBinding.getDeclaringClass().getBinaryName() == null
|
||||
|| methodBinding.getMethodDeclaration().toString() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show CodeLens if annotated with `@Query` or `@NativeQuery`
|
||||
for (IAnnotationBinding a : methodBinding.getAnnotations()) {
|
||||
ITypeBinding t = a.getAnnotationType();
|
||||
if (t != null
|
||||
&& (Annotations.DATA_JPA_QUERY.equals(t.getQualifiedName()) || Annotations.DATA_JPA_NATIVE_QUERY.equals(t.getQualifiedName()))) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (methodBinding == null || methodBinding.getDeclaringClass() == null
|
||||
|| methodBinding.getMethodDeclaration() == null
|
||||
|| methodBinding.getDeclaringClass().getBinaryName() == null
|
||||
|| methodBinding.getMethodDeclaration().toString() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Optional<String> getDataQuery(DataRepositoryAotMetadataService repositoryMetadataService, IJavaProject project, IMethodBinding methodBinding) {
|
||||
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
|
||||
final IMethodBinding method = methodBinding.getMethodDeclaration();
|
||||
|
||||
DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass);
|
||||
|
||||
if (metadata != null) {
|
||||
return Optional.ofNullable(repositoryMetadataService.getQueryStatement(metadata, method));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
|
||||
protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List<CodeLens> resultAccumulator) {
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
IJavaProject project = projectFinder.find(document.getId()).get();
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass);
|
||||
|
||||
if (metadata == null) {
|
||||
return;
|
||||
IMethodBinding methodBinding = node.resolveBinding();
|
||||
|
||||
if (isDataQuaryNonAnnotatedMethodCandidate(methodBinding)) {
|
||||
cancelToken.checkCanceled();
|
||||
getDataQuery(repositoryMetadataService, project, methodBinding).map(queryStatement -> createCodeLens(node, document, queryStatement)).ifPresent(resultAccumulator::add);
|
||||
}
|
||||
|
||||
cancelToken.checkCanceled();
|
||||
|
||||
String queryStatement = repositoryMetadataService.getQueryStatement(metadata, method);
|
||||
if (queryStatement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CodeLens codeLens = createCodeLens(node, document, queryStatement);
|
||||
resultAccumulator.add(codeLens);
|
||||
}
|
||||
|
||||
private CodeLens createCodeLens(MethodDeclaration node, TextDocument document, String queryStatement) {
|
||||
try {
|
||||
IMethodBinding mb = node.resolveBinding();
|
||||
Command cmd = new Command();
|
||||
cmd.setTitle(queryStatement);
|
||||
if (mb != null) {
|
||||
cmd = refactorings.createFixCommand(queryStatement, createFixDescriptor(mb, document.getUri(), queryStatement));
|
||||
}
|
||||
|
||||
CodeLens codeLens = new CodeLens();
|
||||
codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength()));
|
||||
@@ -120,5 +137,16 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, String queryStatement) {
|
||||
return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Convert into `@Query`")
|
||||
.withRecipeScope(RecipeScope.FILE)
|
||||
.withParameters(Map.of("annotationType", Annotations.DATA_JPA_QUERY, "method",
|
||||
"%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(),
|
||||
Arrays.stream(mb.getParameterTypes()).map(pt -> pt.getQualifiedName())
|
||||
.collect(Collectors.joining(", "))),
|
||||
"attributes", List.of(new AddAnnotationOverMethod.Attribute("value",
|
||||
"\"%s\"".formatted(StringEscapeUtils.escapeJava(queryStatement))))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.IMethodBinding;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.java.codeaction.JdtAstCodeActionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
|
||||
import org.springframework.ide.vscode.commons.Version;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ICollector;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class QueryMethodCodeActionProvider implements JdtAstCodeActionProvider {
|
||||
|
||||
private static final String TITLE = "Convert into `@Query`";
|
||||
|
||||
private final DataRepositoryAotMetadataService repositoryMetadataService;
|
||||
private final RewriteRefactorings refactorings;
|
||||
|
||||
public QueryMethodCodeActionProvider(DataRepositoryAotMetadataService repositoryMetadataService, RewriteRefactorings refactorings) {
|
||||
this.repositoryMetadataService = repositoryMetadataService;
|
||||
this.refactorings = refactorings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-data-jpa");
|
||||
return version != null && version.getMajor() >= 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTVisitor createVisitor(CancelChecker cancelToken, IJavaProject project, URI docURI, CompilationUnit cu, TextDocument doc,
|
||||
IRegion region, ICollector<CodeAction> collector) {
|
||||
return new ASTVisitor() {
|
||||
|
||||
@Override
|
||||
public boolean visit(MethodDeclaration node) {
|
||||
cancelToken.checkCanceled();
|
||||
if (node.getStartPosition() <= region.getStart() && node.getStartPosition() + node.getLength() >= region.getEnd()) {
|
||||
int offset = node.getName().getStartPosition();
|
||||
int length = node.getName().getLength();
|
||||
if (offset <= region.getStart() && offset + length >= region.getEnd()) {
|
||||
IMethodBinding binding = node.resolveBinding();
|
||||
if (DataRepositoryAotMetadataCodeLensProvider.isDataQuaryNonAnnotatedMethodCandidate(binding)) {
|
||||
DataRepositoryAotMetadataCodeLensProvider.getDataQuery(repositoryMetadataService, project, binding)
|
||||
.map(query -> createCodeAction(binding, docURI, query)).ifPresent(collector::accept);
|
||||
}
|
||||
}
|
||||
return super.visit(node);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private CodeAction createCodeAction(IMethodBinding mb, URI docUri, String query) {
|
||||
CodeAction ca = new CodeAction();
|
||||
ca.setCommand(refactorings.createFixCommand(TITLE, DataRepositoryAotMetadataCodeLensProvider.createFixDescriptor(mb, docUri.toASCIIString(), query)));
|
||||
ca.setTitle(TITLE);
|
||||
ca.setKind(CodeActionKind.Refactor);
|
||||
return ca;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,8 +50,8 @@ public class JdtReconciler implements JavaReconciler {
|
||||
public static final String SPRING_CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
|
||||
|
||||
private final CompilationUnitCache compilationUnitCache;
|
||||
private final JdtAstReconciler[] reconcilers;
|
||||
private BootJavaConfig config;
|
||||
final JdtAstReconciler[] reconcilers;
|
||||
final BootJavaConfig config;
|
||||
|
||||
private final ConcurrentHashMap<String, List<JdtAstReconciler>> applicableReconcilersCache;
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.reconcilers;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTVisitor;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.springframework.ide.vscode.boot.java.codeaction.JdtAstCodeActionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.DiagnosticSeverityProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ICollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* Ignored (i.e. not shown) reconcile problems with QuickFixes are being shown as CodeActions(s) with this provider.
|
||||
* <b>Note:</b> Only Rewrite based QuickFixes are supported at the moment hence should be around only if Rewrite recipes are supported
|
||||
*/
|
||||
public class ReconcileProblemCodeActionProvider implements JdtAstCodeActionProvider {
|
||||
|
||||
private final JdtReconciler reconciler;
|
||||
private final DiagnosticSeverityProvider severityProvider;
|
||||
|
||||
|
||||
public ReconcileProblemCodeActionProvider(JdtReconciler reconciler, DiagnosticSeverityProvider severityProvider) {
|
||||
this.reconciler = reconciler;
|
||||
this.severityProvider = severityProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(IJavaProject project) {
|
||||
if (reconciler.config.isJavaSourceReconcileEnabled()) {
|
||||
return Arrays.stream(reconciler.reconcilers).anyMatch(r -> r.isApplicable(project));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTVisitor createVisitor(CancelChecker cancelToken, IJavaProject project, URI docURI, CompilationUnit cu, TextDocument doc,
|
||||
IRegion region, ICollector<CodeAction> collector) {
|
||||
IProblemCollector problemCollector = new IProblemCollector() {
|
||||
|
||||
@Override
|
||||
public void endCollecting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginCollecting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ReconcileProblem p) {
|
||||
if (p.getOffset() <= region.getOffset() && p.getOffset() + p.getLength() >= region.getOffset() + region.getLength() && severityProvider.getDiagnosticSeverity(p) == null) {
|
||||
for (QuickfixData<?> qf : p.getQuickfixes()) {
|
||||
if (qf.params instanceof FixDescriptor) {
|
||||
collector.accept(createCodeActionFromScope((FixDescriptor) qf.params));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
CompositeASTVisitor v = new CompositeASTVisitor();
|
||||
ReconcilingContext ctx = new ReconcilingContext(docURI.toASCIIString(), problemCollector, true, true, List.of());
|
||||
for (JdtAstReconciler r : reconciler.reconcilers) {
|
||||
if (r.isApplicable(project) && severityProvider.getDiagnosticSeverity(r.getProblemType()) == null) {
|
||||
v.add(r.createVisitor(project, docURI, cu, ctx));
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private CodeAction createCodeActionFromScope(FixDescriptor d) {
|
||||
CodeAction ca = new CodeAction();
|
||||
ca.setKind(CodeActionKind.Refactor);
|
||||
ca.setTitle(d.getLabel());
|
||||
ca.setData(d);
|
||||
return ca;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022, 2025 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;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeAction;
|
||||
import org.eclipse.lsp4j.CodeActionCapabilities;
|
||||
import org.eclipse.lsp4j.CodeActionContext;
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.CodeActionResolveSupportCapabilities;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.JavaCodeActionHandler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
|
||||
import org.springframework.ide.vscode.boot.java.reconcilers.ReconcilingContext;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
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.BasicProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.DiagnosticSeverityProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
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.rewrite.java.FixDescriptor;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
public class RewriteCodeActionHandler implements JavaCodeActionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RewriteCodeActionHandler.class);
|
||||
|
||||
final private CompilationUnitCache cuCache;
|
||||
final private JdtReconciler jdtReconciler;
|
||||
|
||||
private BootJavaConfig config;
|
||||
|
||||
private QuickfixRegistry quickfixRegistry;
|
||||
|
||||
private DiagnosticSeverityProvider severityProvider;
|
||||
|
||||
public RewriteCodeActionHandler(CompilationUnitCache cuCache, BootJavaConfig config, JdtReconciler jdtReconciler, QuickfixRegistry quickfixRegistry, DiagnosticSeverityProvider severityProvider) {
|
||||
this.cuCache = cuCache;
|
||||
this.config = config;
|
||||
this.jdtReconciler = jdtReconciler;
|
||||
this.quickfixRegistry = quickfixRegistry;
|
||||
this.severityProvider = severityProvider;
|
||||
}
|
||||
|
||||
protected static boolean isResolve(CodeActionCapabilities capabilities, String property) {
|
||||
if (capabilities != null) {
|
||||
CodeActionResolveSupportCapabilities resolveSupport = capabilities.getResolveSupport();
|
||||
if (resolveSupport != null) {
|
||||
List<String> properties = resolveSupport.getProperties();
|
||||
return properties != null && properties.contains(property);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private 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);
|
||||
} else {
|
||||
if (LspClient.currentClient() == Client.ECLIPSE) {
|
||||
// Eclipse would have no diagnostics in the context for QuickAssists refactoring. Diagnostics will be around for QuickFix only
|
||||
return context.getDiagnostics().isEmpty();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Either<Command, CodeAction>> handle(IJavaProject project, CancelChecker cancelToken,
|
||||
CodeActionCapabilities capabilities, CodeActionContext context, TextDocument doc, IRegion region) {
|
||||
// Short circuit here to avoid parsing the java source for nothing.
|
||||
if (!config.isJavaSourceReconcileEnabled()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
URI uri = URI.create(doc.getUri());
|
||||
final QuickfixType rewriteFixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX);
|
||||
|
||||
if (isSupported(capabilities, context) && rewriteFixType != null) {
|
||||
List<CodeAction> codeActions = cuCache.withCompilationUnit(project, uri, cu -> {
|
||||
|
||||
if (cu != null) {
|
||||
|
||||
try {
|
||||
List<CodeAction> cas = new ArrayList<>();
|
||||
List<ReconcileProblem> problems = new ArrayList<>();
|
||||
BasicProblemCollector problemsCollector = new BasicProblemCollector(problems);
|
||||
|
||||
ReconcilingContext reconcilingContext = new ReconcilingContext(doc.getUri(), problemsCollector, true, true, Collections.emptyList());
|
||||
jdtReconciler.reconcile(project, uri, cu, reconcilingContext);
|
||||
|
||||
for (ReconcileProblem p : problems) {
|
||||
if (p.getOffset() <= region.getOffset() && p.getOffset() + p.getLength() >= region.getOffset() + region.getLength() && severityProvider.getDiagnosticSeverity(p) == null) {
|
||||
for (QuickfixData<?> qf : p.getQuickfixes()) {
|
||||
if (qf.params instanceof FixDescriptor) {
|
||||
cas.add(createCodeActionFromScope((FixDescriptor) qf.params));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cas;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
});
|
||||
|
||||
return codeActions.stream().map(ca -> Either.<Command, CodeAction>forRight(ca)).collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private CodeAction createCodeActionFromScope(FixDescriptor d) {
|
||||
CodeAction ca = new CodeAction();
|
||||
ca.setKind(CodeActionKind.Refactor);
|
||||
ca.setTitle(d.getLabel());
|
||||
ca.setData(d);
|
||||
return ca;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022, 2024 VMware, Inc.
|
||||
* Copyright (c) 2022, 2025 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
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.rewrite;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
@@ -213,7 +214,13 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
|
||||
Field field = findField(recipe, entry.getKey());
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
field.set(recipe, entry.getValue());
|
||||
if (!field.getType().isAssignableFrom(entry.getValue().getClass())) {
|
||||
field.set(recipe, gson.fromJson(gson.toJsonTree(entry.getValue()), field.getType()));
|
||||
} else if (field.getGenericType() instanceof ParameterizedType pt) {
|
||||
field.set(recipe, gson.fromJson(gson.toJsonTree(entry.getValue()), pt));
|
||||
} else {
|
||||
field.set(recipe, entry.getValue());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("", e);;
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2025 Broadcom, 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:
|
||||
* Broadcom, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.data.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.TextDocumentEdit;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
|
||||
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
|
||||
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.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@BootLanguageServerTest
|
||||
@Import(SymbolProviderTestConf.class)
|
||||
public class QueryMethodCodeActionProviderTest {
|
||||
|
||||
@Autowired private BootLanguageServerHarness harness;
|
||||
@Autowired private JavaProjectFinder projectFinder;
|
||||
@Autowired private SpringSymbolIndex indexer;
|
||||
@Autowired private RewriteRefactorings refactorings;
|
||||
|
||||
private IJavaProject testProject;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
testProject = ProjectsHarness.INSTANCE.mavenProject("aot-generation");
|
||||
harness.useProject(testProject);
|
||||
harness.intialize(null);
|
||||
|
||||
// trigger project creation
|
||||
projectFinder.find(new TextDocumentIdentifier(testProject.getLocationUri().toASCIIString())).get();
|
||||
|
||||
CompletableFuture<Void> initProject = indexer.waitOperation();
|
||||
initProject.get(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertToQueryCodeAction() throws Exception {
|
||||
Path filePath = Paths.get(testProject.getLocationUri())
|
||||
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
|
||||
|
||||
List<CodeAction> codeActions = editor.getCodeActions("findUserByLastnameStartingWith", 1);
|
||||
assertEquals(1, codeActions.size());
|
||||
CodeAction ca = codeActions.get(0);
|
||||
assertEquals("Convert into `@Query`", ca.getLabel());
|
||||
Command cmd = ca.getCommand();
|
||||
assertEquals(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX, cmd.getArguments().get(0));
|
||||
WorkspaceEdit edit = refactorings.createEdit((JsonElement) cmd.getArguments().get(1)).get(1, TimeUnit.SECONDS);
|
||||
TextDocumentEdit docEdit = edit.getDocumentChanges().get(0).getLeft();
|
||||
assertEquals(
|
||||
"@Query(\"SELECT u FROM example.springdata.aot.User u WHERE u.lastname LIKE :lastname ESCAPE '\\\\' ORDER BY u.firstname asc\")",
|
||||
docEdit.getEdits().get(0).getNewText().trim());
|
||||
assertEquals(filePath.toUri().toASCIIString(), docEdit.getTextDocument().getUri());
|
||||
}
|
||||
|
||||
@Test
|
||||
void noConvertToQueryCodeAction() throws Exception {
|
||||
Path filePath = Paths.get(testProject.getLocationUri())
|
||||
.resolve("src/main/java/example/springdata/aot/UserRepository.java");
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8), filePath.toUri().toASCIIString());
|
||||
|
||||
List<CodeAction> codeActions = editor.getCodeActions("usersWithUsernamesStartingWith", 1);
|
||||
assertEquals(0, codeActions.size());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user