diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml index 7f174019f..cba608063 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -131,6 +131,12 @@ id="org.springframework.tooling.boot.ls.preferences" name="Spring Boot Language Server"> + + @@ -338,9 +344,13 @@ + + diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java index 32584f1d6..bd30ff71f 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/BootJavaPreferencesPage.java @@ -96,7 +96,6 @@ public class BootJavaPreferencesPage extends FieldEditorPreferencePage implement addField(new BooleanFieldEditor(Constants.PREF_VALIDATION_SPEL_EXPRESSIONS, "SpEL Expression Syntax Validation", fieldEditorParent)); - addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent)); } } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java index 5019d61b4..4174ddb3f 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/Constants.java @@ -33,4 +33,7 @@ public class Constants { public static final String PREF_REWRITE_RECONCILE = "boot-java.rewrite.reconcile"; + public static final String PREF_REWRITE_RECIPES_SCAN_FILES = "boot-java.rewrite.scan-files"; + + public static final String PREF_REWRITE_RECIPES_SCAN_DIRS = "boot-java.rewrite.scan-directories"; } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java index 242911bc2..2564aa151 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/DelegatingStreamConnectionProvider.java @@ -32,9 +32,9 @@ import org.eclipse.lsp4j.jsonrpc.messages.Message; import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage; import org.eclipse.lsp4j.services.LanguageServer; import org.springframework.tooling.boot.ls.prefs.CategoryProblemsSeverityPrefsPage; +import org.springframework.tooling.boot.ls.prefs.FileListEditor; import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData; import org.springframework.tooling.boot.ls.prefs.ProblemCategoryData.CategoryToggleData; -import org.springframework.tooling.ls.eclipse.commons.LanguageServerCommonsActivator; import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder; import org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.RemoteAppData; import org.springsource.ide.eclipse.commons.livexp.core.ValueListener; @@ -187,7 +187,12 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi bootJavaObj.put("change-detection", bootChangeDetection); bootJavaObj.put("validation", validation); bootJavaObj.put("remote-apps", getAllRemoteApps()); - bootJavaObj.put("rewrite", Map.of("reconcile", preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE))); + + bootJavaObj.put("rewrite", Map.of( + "reconcile", preferenceStore.getBoolean(Constants.PREF_REWRITE_RECONCILE), + "scan-directories", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS)), + "scan-files", FileListEditor.getValuesFromPreference(preferenceStore.getString(Constants.PREF_REWRITE_RECIPES_SCAN_FILES)) + )); settings.put("boot-java", bootJavaObj); putValidationPreferences(settings); diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java new file mode 100644 index 000000000..06708f8cc --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/RewritePreferencePage.java @@ -0,0 +1,33 @@ +package org.springframework.tooling.boot.ls; + +import java.util.List; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.PathEditor; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; +import org.springframework.tooling.boot.ls.prefs.FileListEditor; + +public class RewritePreferencePage extends FieldEditorPreferencePage implements IWorkbenchPreferencePage { + + @Override + public void init(IWorkbench workbench) { + setPreferenceStore(BootLanguageServerPlugin.getDefault().getPreferenceStore()); + } + + @Override + protected void createFieldEditors() { + Composite fieldEditorParent = getFieldEditorParent(); + + addField(new BooleanFieldEditor(Constants.PREF_REWRITE_RECONCILE, "Experimental reconciling for Java source based on Rewrite project", fieldEditorParent)); + + addField(new FileListEditor(Constants.PREF_REWRITE_RECIPES_SCAN_FILES, "JAR and YAML files to scan for Recipes", "Select JARs and YAML files:", fieldEditorParent, List.of("jar", "yml", "yaml"))); + + addField(new PathEditor(Constants.PREF_REWRITE_RECIPES_SCAN_DIRS, "Directories to scan for Recipes", "Select directory to scan for Recipes", fieldEditorParent)); + + } + + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java new file mode 100644 index 000000000..24c4429fb --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteCommandsQuickAccessProvider.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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.tooling.boot.ls.commands; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.services.LanguageServer; +import org.eclipse.ui.quickaccess.IQuickAccessComputer; +import org.eclipse.ui.quickaccess.IQuickAccessComputerExtension; +import org.eclipse.ui.quickaccess.QuickAccessElement; +import org.springframework.tooling.boot.ls.BootLanguageServerPlugin; + +@SuppressWarnings("restriction") +public class RewriteCommandsQuickAccessProvider implements IQuickAccessComputer, IQuickAccessComputerExtension { + + private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload"; + + @Override + public QuickAccessElement[] computeElements(String query, IProgressMonitor monitor) { + return new QuickAccessElement[] { + new QuickAccessElement() { + + @Override + public String getLabel() { + return "Reload Rewrite Recipes"; + } + + @Override + public ImageDescriptor getImageDescriptor() { + return null; + } + + @Override + public String getId() { + return CMD_REWRITE_RELOAD; + } + + @Override + public void execute() { + List usedLanguageServers = LanguageServiceAccessor.getActiveLanguageServers(serverCapabilities -> true); + + if (usedLanguageServers.isEmpty()) { + return; + } + + ExecuteCommandParams commandParams = new ExecuteCommandParams(); + commandParams.setCommand(CMD_REWRITE_RELOAD); + + commandParams.setArguments(Collections.emptyList()); + + try { + CompletableFuture.allOf(usedLanguageServers.stream().map(ls -> + ls.getWorkspaceService().executeCommand(commandParams)).toArray(CompletableFuture[]::new)).get(2, TimeUnit.SECONDS); + } + catch (Exception e) { + BootLanguageServerPlugin.getDefault().getLog().error("Failed to reload Rewrite Recipes!", e); + } + } + } + }; + } + + @Override + public QuickAccessElement[] computeElements() { + return new QuickAccessElement[0]; + } + + @Override + public void resetState() { + } + + @Override + public boolean needsRefresh() { + return false; + } + +} diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java index 6e999f3d4..21d509b17 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/RewriteRefactoringsHandler.java @@ -48,6 +48,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +@SuppressWarnings("restriction") public class RewriteRefactoringsHandler extends AbstractHandler { private static class DurationTypeConverter implements JsonSerializer, JsonDeserializer { diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java index 9a7a25e2b..8719e76ec 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/SelectRecipesDialog.java @@ -45,6 +45,7 @@ import org.osgi.framework.FrameworkUtil; import org.springframework.tooling.boot.ls.commands.RecipeDescriptor.OptionDescriptor; import org.springframework.tooling.boot.ls.commands.RecipeTreeModel.CheckedState; +@SuppressWarnings("restriction") public class SelectRecipesDialog extends StatusDialog { private static final int MARGIN = 5; diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java new file mode 100644 index 000000000..429473f76 --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/FileListEditor.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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.tooling.boot.ls.prefs; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.eclipse.jface.preference.PathEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; + +public class FileListEditor extends PathEditor { + + private String lastPath; + + private String dirChooserLabelText; + + private List fileFilters; + + public FileListEditor(String name, String labelText, + String dirChooserLabelText, Composite parent, List fileFilters) { + super(name, labelText, dirChooserLabelText, parent); + this.dirChooserLabelText = dirChooserLabelText; + this.fileFilters = fileFilters; + } + + @Override + protected String getNewInputObject() { + + FileDialog dialog = new FileDialog(getShell(), SWT.SHEET); + dialog.setFilterExtensions(fileFilters.toArray(String[]::new)); + if (dirChooserLabelText != null) { + dialog.setText(dirChooserLabelText); + } + if (lastPath != null) { + if (new File(lastPath).exists()) { + dialog.setFilterPath(lastPath); + } + } + String file = dialog.open(); + if (file != null) { + String parentFolder = new File(file).getParent(); + if (parentFolder == null) { + return null; + } + lastPath = parentFolder; + } + return file; + } + + public static List getValuesFromPreference(String rawValue) { + StringTokenizer st = new StringTokenizer(rawValue, File.pathSeparator + + "\n\r");//$NON-NLS-1$ + ArrayList l = new ArrayList<>(); + while (st.hasMoreElements()) { + l.add((String)st.nextElement()); + } + return l; + } + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index cca405180..fb20bc573 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -31,13 +31,16 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.eclipse.lsp4j.ApplyWorkspaceEditParams; import org.eclipse.lsp4j.ApplyWorkspaceEditResponse; import org.eclipse.lsp4j.ClientCapabilities; +import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionKind; import org.eclipse.lsp4j.CodeActionOptions; import org.eclipse.lsp4j.CodeLensOptions; +import org.eclipse.lsp4j.Command; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.ExecuteCommandOptions; @@ -94,6 +97,7 @@ import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.text.TextDocument; +import com.google.common.collect.ImmutableList; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -720,10 +724,21 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC d.setSeverity(severity); d.setSource(getServer().EXTENSION_ID); List> fixes = problem.getQuickfixes(); + // Copy original diagnsotic without the data field to avoid stackoverflow is hashCode() method call + Diagnostic refDiagnostic = new Diagnostic(d.getRange(), d.getMessage(), d.getSeverity(), d.getSource()); if (CollectionUtil.hasElements(fixes)) { - for (QuickfixData fix : fixes) { - quickfixes.add(new Quickfix<>(CODE_ACTION_COMMAND_ID, d, fix)); - } + d.setData(fixes.stream().map(fix -> { + CodeAction ca = new CodeAction(); + ca.setKind(CodeActionKind.QuickFix); + ca.setTitle(fix.title); + ca.setDiagnostics(List.of(refDiagnostic)); + ca.setCommand(new Command( + fix.title, + CODE_ACTION_COMMAND_ID, + ImmutableList.of(fix.type.getId(), fix.params) + )); + return ca; + }).collect(Collectors.toList())); } diagnostics.add(d); } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java index 6b06023de..4e639ecb9 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleTextDocumentService.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.util; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -81,11 +82,15 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.LazyTextDocument; import org.springframework.ide.vscode.commons.util.text.Region; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; public class SimpleTextDocumentService implements TextDocumentService, DocumentEventListenerManager { @@ -430,22 +435,26 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE } } - private List> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TrackedDocument doc, CodeActionParams params) { + private List> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, CodeActionParams params) { Builder> listBuilder = ImmutableList.builder(); CodeActionContext context = params.getContext(); if (!context.getDiagnostics().isEmpty() || (context.getOnly() != null && context.getOnly().contains(CodeActionKind.QuickFix))) { - doc.getQuickfixes().stream() - .filter((fix) -> fix.appliesTo(params.getRange(), context)) - .map(f -> f.getCodeAction(params.getContext())) - .map(command -> Either.forRight(command)) - .forEach(listBuilder::add); + params.getContext().getDiagnostics().forEach(d -> { + if (d.getData() != null) { + Type type = new TypeToken>(){}.getType(); + List codeActions = new GsonBuilder().create().fromJson((JsonElement)d.getData(), type); + for (CodeAction ca : codeActions) { + listBuilder.add(Either.forRight(ca)); + } + } + }); } if (codeActionHandler != null) { try { - int start = doc.getDocument().toOffset(params.getRange().getStart()); - int end = doc.getDocument().toOffset(params.getRange().getEnd()); - listBuilder.addAll(codeActionHandler.handle(cancelToken, capabilities, context, doc.getDocument(), new Region(start, end - start))); + int start = doc.toOffset(params.getRange().getStart()); + int end = doc.toOffset(params.getRange().getEnd()); + listBuilder.addAll(codeActionHandler.handle(cancelToken, capabilities, context, doc, new Region(start, end - start))); } catch (Exception e) { log.error("Failed to compute quick refactorings", e); } @@ -469,14 +478,22 @@ public class SimpleTextDocumentService implements TextDocumentService, DocumentE // this doesn't happen async, because it accesses the internal documents structure // and therefore needs to be executed as part of the main LSP message queue - TrackedDocument doc = documents.get(params.getTextDocument().getUri()); + String uri = params.getTextDocument().getUri(); + TrackedDocument trackedDoc = documents.get(uri); + TextDocument doc = trackedDoc == null ? null : trackedDoc.getDocument(); + + if (doc == null) { + if (uri.endsWith(".java")) { + doc = new LazyTextDocument(uri, LanguageId.JAVA); + } + } if (doc != null) { - + final TextDocument d = doc; return server.getClientCapabilities() .thenApply(SimpleTextDocumentService::getCodeActionCapabilities) .thenComposeAsync(capabilities -> - CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> computeCodeActions(cancelToken, capabilities, doc, params))); + CompletableFutures.computeAsync(messageWorkerThreadPool, cancelToken -> computeCodeActions(cancelToken, capabilities, d, params))); } else { return CompletableFuture.completedFuture(ImmutableList.of()); } diff --git a/headless-services/commons/commons-rewrite-test/pom.xml b/headless-services/commons/commons-rewrite-test/pom.xml new file mode 100644 index 000000000..8ffb8483e --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + qq + commons-rewrite-test + + + org.springframework.ide.vscode + commons-parent + 1.39.0-SNAPSHOT + ../pom.xml + + + + + org.springframework.ide.vscode + commons-rewrite + ${project.version} + + + + + \ No newline at end of file diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java new file mode 100644 index 000000000..4df00128c --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameProblemDescriptor.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.rewrite.test; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.marker.Range; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTypes; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; +import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; + +public class HelloMethodRenameProblemDescriptor implements RecipeSpringJavaProblemDescriptor { + + @Override + public String getRecipeId() { + return "org.springframework.rewrite.test.HelloMethodRenameRecipe"; + } + + @Override + public String getLabel(RecipeScope s) { + return RecipeCodeActionDescriptor.buildLabel("Switch hello method into bye", s); + } + + @Override + public RecipeScope[] getScopes() { + return RecipeScope.values(); + } + + @Override + public JavaVisitor getMarkerVisitor() { + return new JavaIsoVisitor<>() { + + @Override + public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) { + MethodDeclaration m = super.visitMethodDeclaration(method, p); + if ("hello".equals(method.getSimpleName())) { + FixAssistMarker marker = new FixAssistMarker(Tree.randomId()).withRecipeId(getRecipeId()).withScope(m.getMarkers().findFirst(Range.class).get()); + m = m.withName(m.getName().withMarkers(m.getName().getMarkers().add(marker))); + } + return m; + } + + }; + } + + @Override + public boolean isApplicable(IJavaProject project) { + return true; + } + + @Override + public ProblemType getProblemType() { + return ProblemTypes.create("Hello Method!", ProblemSeverity.ERROR, ProblemCategory.NO_CATEGORY); + } + +} diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java new file mode 100644 index 000000000..e7f5d9822 --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/HelloMethodRenameRecipe.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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.rewrite.test; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J.MethodDeclaration; +import org.openrewrite.java.tree.J.MethodInvocation; + +public class HelloMethodRenameRecipe extends Recipe { + + @Override + public String getDisplayName() { + return "Rename hello method into bye"; + } + + @Override + protected TreeVisitor getVisitor() { + return new JavaIsoVisitor<>() { + + @Override + public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext p) { + MethodDeclaration m = super.visitMethodDeclaration(method, p); + if ("hello".equals(m.getSimpleName())) { + m = m.withName(m.getName().withSimpleName("bye")); + } + return m; + } + + @Override + public MethodInvocation visitMethodInvocation(MethodInvocation method, ExecutionContext p) { + MethodInvocation m = super.visitMethodInvocation(method, p); + if ("hello".equals(m.getSimpleName())) { + m = m.withName(m.getName().withSimpleName("bye")); + } + return m; + } + + }; + } + + + +} diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java new file mode 100644 index 000000000..e8cef39a6 --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/OssPackageRecipe.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * 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.rewrite.test; + +import org.openrewrite.Recipe; +import org.openrewrite.java.ChangePackage; + +public class OssPackageRecipe extends Recipe { + + public OssPackageRecipe() { + doNext(new ChangePackage("com.example", "org.example", true)); + } + + @Override + public String getDisplayName() { + return "Test recipe class coming from the Jar"; + } + +} diff --git a/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java new file mode 100644 index 000000000..b2779e573 --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/src/main/java/org/springframework/rewrite/test/StsTestCodeActionRepo.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * 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.rewrite.test; + +import java.util.Collections; +import java.util.List; + +import org.springframework.ide.vscode.commons.rewrite.config.CodeActionRepository; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; + +public class StsTestCodeActionRepo extends CodeActionRepository { + + @Override + public List getCodeActionDescriptors() { + return Collections.emptyList(); + } + + @Override + public List getProblemDescriptors() { + return List.of(new HelloMethodRenameProblemDescriptor()); + } + +} diff --git a/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml b/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml new file mode 100644 index 000000000..980802e9c --- /dev/null +++ b/headless-services/commons/commons-rewrite-test/src/main/resources/META-INF/rewrite/test-recipes.yml @@ -0,0 +1,11 @@ +######################################################################################################################## +# Spring Data 3.0 io.micrometer.core.instrument.binder -> io.micrometer.binder +type: specs.openrewrite.org/v1beta/recipe +name: rewrite.test.jar.oss-package +displayName: Example with OSS packages from YAML +description: Switches app to OSS app +recipeList: + - org.openrewrite.java.ChangePackage: + oldPackageName: com.example + newPackageName: org.example + recursive: true diff --git a/headless-services/commons/commons-rewrite/pom.xml b/headless-services/commons/commons-rewrite/pom.xml index 6149ea10c..803b727cc 100644 --- a/headless-services/commons/commons-rewrite/pom.xml +++ b/headless-services/commons/commons-rewrite/pom.xml @@ -22,7 +22,18 @@ commons-language-server ${project.version} + + org.springframework.ide.vscode + commons-java + ${project.version} + + + + io.github.classgraph + classgraph + 4.8.149 + org.openrewrite rewrite-properties diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java new file mode 100644 index 000000000..81e4ea325 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/CodeActionRepository.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * 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.commons.rewrite.config; + +import java.util.List; + +public abstract class CodeActionRepository { + + public abstract List getCodeActionDescriptors(); + + public abstract List getProblemDescriptors(); + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java similarity index 94% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java index 856076292..0aa97f4d1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeCodeActionDescriptor.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeCodeActionDescriptor.java @@ -8,7 +8,7 @@ * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite; +package org.springframework.ide.vscode.commons.rewrite.config; import org.openrewrite.ExecutionContext; import org.openrewrite.java.JavaVisitor; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java similarity index 90% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java index 9c3530365..0c0835a53 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RecipeScope.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeScope.java @@ -8,7 +8,7 @@ * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite; +package org.springframework.ide.vscode.commons.rewrite.config; public enum RecipeScope { NODE, diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java similarity index 82% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java rename to headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java index 57075c8cb..6d9801639 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/RecipeSpringJavaProblemDescriptor.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/RecipeSpringJavaProblemDescriptor.java @@ -8,9 +8,8 @@ * Contributors: * VMware, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.rewrite.reconcile; +package org.springframework.ide.vscode.commons.rewrite.config; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; public interface RecipeSpringJavaProblemDescriptor extends RecipeCodeActionDescriptor { diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java new file mode 100644 index 000000000..d60b0f402 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsClasspathScanningLoader.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * 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.commons.rewrite.config; + +import static org.openrewrite.internal.RecipeIntrospectionUtils.constructRecipe; +import static org.openrewrite.internal.RecipeIntrospectionUtils.recipeDescriptorFromRecipe; + +import java.lang.reflect.Constructor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Properties; + +import javax.annotation.Nullable; + +import org.openrewrite.Recipe; +import org.openrewrite.config.CategoryDescriptor; +import org.openrewrite.config.DeclarativeRecipe; +import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.config.RecipeExample; +import org.openrewrite.config.ResourceLoader; +import org.openrewrite.config.YamlResourceLoader; +import org.openrewrite.internal.RecipeIntrospectionUtils; +import org.openrewrite.style.NamedStyles; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; + +public class StsClasspathScanningLoader implements ResourceLoader, StsResourceLoader { + + private final List recipes = new ArrayList<>(); + private final List styles = new ArrayList<>(); + + private final List recipeDescriptors = new ArrayList<>(); + private final List categoryDescriptors = new ArrayList<>(); + private final List recipeExamples = new ArrayList<>(); + + private final List codeActionRepos = new ArrayList<>(); + + public StsClasspathScanningLoader(Path p, Properties properties, ClassLoader classLoader) { + if (Files.isDirectory(p)) { + String dir = p.toString(); + + scanClasses(new ClassGraph() + .acceptPaths(dir) + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader), classLoader); + + scanYaml(new ClassGraph() + .acceptPaths(dir) + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader) + .acceptPaths("META-INF/rewrite"), properties, classLoader); + + } else { + String jarName = p.toFile().getName(); + + scanClasses(new ClassGraph() + .acceptJars(jarName) + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader), classLoader); + + scanYaml(new ClassGraph() + .acceptJars(jarName) + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader) + .acceptPaths("META-INF/rewrite"), properties, classLoader); + } + + } + + public StsClasspathScanningLoader(Properties properties, String[] acceptPackages) { + scanClasses(new ClassGraph().acceptPackages(acceptPackages), getClass().getClassLoader()); + scanYaml(new ClassGraph().acceptPaths("META-INF/rewrite"), properties, null); + } + + /** + * Construct a ClasspathScanningLoader scans the provided classload for recipes + * + * @param properties Yaml placeholder properties + * @param classLoader Limit scan to classes loadable by this classloader + */ + public StsClasspathScanningLoader(Properties properties, ClassLoader classLoader) { + scanClasses(new ClassGraph() + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader), classLoader); + + scanYaml(new ClassGraph() + .ignoreParentClassLoaders() + .overrideClassLoaders(classLoader) + .acceptPaths("META-INF/rewrite"), properties, classLoader); + } + + /** + * This must be called _after_ scanClasses or the descriptors of declarative recipes will be missing any + * non-declarative recipes they depend on that would be discovered by scanClasses + */ + private void scanYaml(ClassGraph classGraph, Properties properties, @Nullable ClassLoader classLoader) { + try (ScanResult scanResult = classGraph.enableMemoryMapping().scan()) { + List yamlResourceLoaders = new ArrayList<>(); + + scanResult.getResourcesWithExtension("yml").forEachInputStreamIgnoringIOException((res, input) -> { + yamlResourceLoaders.add(new YamlResourceLoader(input, res.getURI(), properties, classLoader)); + }); + // Extract in two passes so that the full list of recipes from all sources are known when computing recipe descriptors + // Otherwise recipes which include recipes from other sources in their recipeList will have incomplete descriptors + for(YamlResourceLoader resourceLoader : yamlResourceLoaders) { + recipes.addAll(resourceLoader.listRecipes()); + categoryDescriptors.addAll(resourceLoader.listCategoryDescriptors()); + styles.addAll(resourceLoader.listStyles()); + recipeExamples.addAll(resourceLoader.listRecipeExamples()); + } + for(YamlResourceLoader resourceLoader : yamlResourceLoaders) { + recipeDescriptors.addAll(resourceLoader.listRecipeDescriptors(recipes)); + } + } + } + + private void scanClasses(ClassGraph classGraph, ClassLoader classLoader) { + try (ScanResult result = classGraph + .ignoreClassVisibility() + .overrideClassLoaders(classLoader) + .scan()) { + + for (ClassInfo classInfo : result.getSubclasses(Recipe.class.getName())) { + Class recipeClass = classInfo.loadClass(); + if (recipeClass.getName().equals(DeclarativeRecipe.class.getName()) || recipeClass.getEnclosingClass() != null) { + continue; + } + try { + Recipe recipe = constructRecipe(recipeClass); + recipeDescriptors.add(recipeDescriptorFromRecipe(recipe)); + recipes.add(recipe); + } catch (Exception e) { +// logger.warn("Unable to configure {}", recipeClass.getName(), e); + } + } + for (ClassInfo classInfo : result.getSubclasses(NamedStyles.class.getName())) { + Class styleClass = classInfo.loadClass(); + try { + Constructor constructor = RecipeIntrospectionUtils.getZeroArgsConstructor(styleClass); + if(constructor != null) { + constructor.setAccessible(true); + styles.add((NamedStyles) constructor.newInstance()); + } + } catch (Exception e) { +// logger.warn("Unable to configure {}", styleClass.getName(), e); + } + } + + for (ClassInfo classInfo : result.getSubclasses(CodeActionRepository.class.getName())) { + Class codeActionRepoClass = classInfo.loadClass(); + Constructor primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(codeActionRepoClass); + if (primaryConstructor == null) { + //TODO: error!!! + } else { + try { + CodeActionRepository repo = (CodeActionRepository) primaryConstructor.newInstance(); + codeActionRepos.add(repo); + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + } + } + + @Override + public Collection listRecipes() { + return recipes; + } + + @Override + public Collection listRecipeDescriptors() { + return recipeDescriptors; + } + + @Override + public Collection listCategoryDescriptors() { + return categoryDescriptors; + } + + @Override + public Collection listStyles() { + return styles; + } + + @Override + public Collection listRecipeExamples() { + return recipeExamples; + } + + public List listCodeActionDescriptorsRepositories() { + return codeActionRepos; + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java new file mode 100644 index 000000000..40f55a572 --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsEnvironment.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * 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.commons.rewrite.config; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.openrewrite.config.Environment; +import org.openrewrite.config.ResourceLoader; + +public class StsEnvironment extends Environment { + + final private Supplier> codeActionRepos; + + public StsEnvironment(Collection resourceLoaders) { + super(resourceLoaders); + codeActionRepos = () -> resourceLoaders.stream().filter(StsResourceLoader.class::isInstance).map(StsResourceLoader.class::cast).flatMap(l -> l.listCodeActionDescriptorsRepositories().stream()); + } + + public static class Builder extends Environment.Builder { + + final private Properties props; + + public Builder(Properties properties) { + super(properties); + this.props = properties; + } + + @Override + public org.openrewrite.config.Environment.Builder scanRuntimeClasspath(String... acceptPackages) { + return load(new StsClasspathScanningLoader(props, acceptPackages)); + } + + @Override + public org.openrewrite.config.Environment.Builder scanClassLoader(ClassLoader classLoader) { + return load(new StsClasspathScanningLoader(props, classLoader)); + } + + @Override + public org.openrewrite.config.Environment.Builder scanJar(Path jar, ClassLoader classLoader) { + return load(new StsClasspathScanningLoader(jar, props, classLoader)); + } + + public org.openrewrite.config.Environment.Builder scanPath(Path dir, ClassLoader classLoader) { + return load(new StsClasspathScanningLoader(dir, props, classLoader)); + } + + @SuppressWarnings("unchecked") + public StsEnvironment build() { + try { + Field f = Environment.Builder.class.getDeclaredField("resourceLoaders"); + f.setAccessible(true); + return new StsEnvironment((Collection) f.get(this)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + + } + + public List listCodeActionDescriptors() { + return codeActionRepos.get().flatMap(r -> r.getCodeActionDescriptors().stream()).collect(Collectors.toList()); + } + + public List listProblemDescriptors() { + return codeActionRepos.get().flatMap(r -> r.getProblemDescriptors().stream()).collect(Collectors.toList()); + } + + public static Builder builder() { + return new Builder(new Properties()); + } + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java new file mode 100644 index 000000000..135aaad1e --- /dev/null +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/config/StsResourceLoader.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * 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.commons.rewrite.config; + +import java.util.List; + +import org.openrewrite.config.ResourceLoader; + +public interface StsResourceLoader extends ResourceLoader { + + List listCodeActionDescriptorsRepositories(); + + +} diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java index d98a5e7c9..0c1eca018 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/FixAssistMarker.java @@ -6,12 +6,13 @@ import java.util.Objects; import java.util.UUID; import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Range; public class FixAssistMarker implements Marker { private UUID id; - private UUID scope; + private Range scope; private String recipeId; @@ -34,12 +35,12 @@ public class FixAssistMarker implements Marker { return this; } - public FixAssistMarker withScope(UUID scope) { + public FixAssistMarker withScope(Range scope) { this.scope = scope; return this; } - public UUID getScope() { + public Range getScope() { return scope; } diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java index fe3960d34..b38b6b089 100644 --- a/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java +++ b/headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/ORAstUtils.java @@ -329,15 +329,18 @@ public class ORAstUtils { @Override public J visit(Tree tree, ExecutionContext ctx) { - J t = super.visit(tree, ctx); - if (condition.test(t)) { - makeVisitorNonTopLevel(visitor); - t = visitor.visit(t, ctx, getCursor()); - for (TreeVisitor v : getAfterVisitors(visitor)) { - doAfterVisit(v); - } + if (tree instanceof J) { + J t = (J) tree; + if (condition.test(t)) { + makeVisitorNonTopLevel(visitor); + t = visitor.visit(t, ctx, getCursor()); + for (TreeVisitor v : getAfterVisitors(visitor)) { + doAfterVisit(v); + } + return t; + } } - return t; + return super.visit(tree, ctx); } }; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java index 310a2fb6d..fd562fcf3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaConfig.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.app; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.Consumer; import org.slf4j.Logger; @@ -162,7 +163,15 @@ public class BootJavaConfig implements InitializingBean { public void afterPropertiesSet() throws Exception { workspace.onDidChangeConfiguraton(this::handleConfigurationChange); } + + public Set getRecipeDirectories() { + return settings.getStringSet("boot-java", "rewrite", "scan-directories"); + } + public Set getRecipeFiles() { + return settings.getStringSet("boot-java", "rewrite", "scan-files"); + } + public Settings getRawSettings() { return settings; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index b08190e80..ba1d3a087 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -317,7 +317,7 @@ public class BootLanguageServerBootApp { }; } - @Bean RewriteRecipeRepository rewriteRecipesRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) { - return new RewriteRecipeRepository(server, projectFinder); + @Bean RewriteRecipeRepository rewriteRecipesRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) { + return new RewriteRecipeRepository(server, projectFinder, config); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index b92803e06..d06dc3760 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -32,6 +32,7 @@ import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider; +import org.springframework.ide.vscode.boot.java.rewrite.RewriteRecipeRepository; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.utils.SymbolCache; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; @@ -87,6 +88,7 @@ public class BootLanguageServerInitializer implements InitializingBean { @Autowired SpringProjectsValidations springProjectsValidations; @Autowired private JavaProjectFinder projectFinder; @Autowired private LanguageServerProperties configProps; + @Autowired(required = false) private RewriteRecipeRepository recipesRepo; @Qualifier("adHocProperties") @Autowired ProjectBasedPropertyIndexProvider adHocProperties; @@ -170,21 +172,27 @@ public class BootLanguageServerInitializer implements InitializingBean { components.getCodeActionProvider().ifPresent(documents::onCodeAction); - config.addListener(evt -> { - components.getReconcileEngine().ifPresent(reconciler -> { - log.info("A configuration changed, triggering reconcile on all open documents"); - for (TextDocument doc : server.getTextDocumentService().getAll()) { - server.validateWith(doc.getId(), reconciler); - } - params.projectFinder.all().forEach(p -> validateProject(p, reconciler)); - }); - }); + config.addListener(evt -> reconcile()); + + if (recipesRepo != null) { + recipesRepo.onRecipesLoaded(v -> reconcile()); + } addSpringProjectsVersionValidation(params); server.getWorkspaceService().getFileObserver().onFilesChanged(FILES_TO_WATCH_GLOB, this::handleFiles); server.getWorkspaceService().getFileObserver().onFilesCreated(FILES_TO_WATCH_GLOB, this::handleFiles); } + + private void reconcile() { + components.getReconcileEngine().ifPresent(reconciler -> { + log.info("A configuration changed, triggering reconcile on all open documents"); + for (TextDocument doc : server.getTextDocumentService().getAll()) { + server.validateWith(doc.getId(), reconciler); + } + params.projectFinder.all().forEach(p -> validateProject(p, reconciler)); + }); + } public CompositeLanguageServerComponents getComponents() { Assert.notNull(components, "Not yet initialized, can't get components yet."); 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 e18b83c21..797bf85e8 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 @@ -27,9 +27,6 @@ public class RewriteConfig implements InitializingBean { @Autowired private SimpleLanguageServer server; - @Autowired - private RewriteRecipeRepository recipeRepo; - @Autowired private RewriteRefactorings rewriteRefactorings; @@ -41,15 +38,7 @@ public class RewriteConfig implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { QuickfixRegistry registry = server.getQuickfixRegistry(); - - recipeRepo.loaded.thenAccept(v -> - recipeRepo.getProblemRecipeDescriptors().forEach(d -> { - if (recipeRepo.getRecipe(d.getRecipeId()).isPresent()) { - registry.register(d.getRecipeId(), rewriteRefactorings); - } - }) - ); - + registry.register(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX, rewriteRefactorings); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java new file mode 100644 index 000000000..87c982141 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/BootCodeActionRepository.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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; + +import java.util.List; + +import org.springframework.ide.vscode.boot.java.rewrite.codeaction.AutowiredFieldIntoConstructorParameterCodeAction; +import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction; +import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction; +import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction; +import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem; +import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem; +import org.springframework.ide.vscode.boot.java.rewrite.reconcile.PreciseBeanTypeProblem; +import org.springframework.ide.vscode.boot.java.rewrite.reconcile.UnnecessarySpringExtensionProblem; +import org.springframework.ide.vscode.commons.rewrite.config.CodeActionRepository; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; + +public class BootCodeActionRepository extends CodeActionRepository { + + @Override + public List getCodeActionDescriptors() { + return List.of( + new AutowiredFieldIntoConstructorParameterCodeAction(), + new BeanMethodsNotPublicCodeAction(), + new NoRequestMappingAnnotationCodeAction(), + new UnnecessarySpringExtensionCodeAction() + ); + } + + @Override + public List getProblemDescriptors() { + return List.of( + new BeanMethodNotPublicProblem(), + new NoAutowiredOnConstructorProblem(), + new UnnecessarySpringExtensionProblem(), + new PreciseBeanTypeProblem() + ); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java index d3a879269..5bfa293c3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteCodeActionHandler.java @@ -37,10 +37,12 @@ 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.rewrite.reconcile.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.java.IJavaProject; 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.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.util.text.IRegion; @@ -99,9 +101,6 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler { try { - // Wait for recipe repo to load if not loaded - should be loaded by the time we get here. - recipeRepo.loaded.get(); - List descriptors = recipeRepo.getCodeActionRecipeDescriptors().stream() // If Recipe not present - don't show quick assist as it won't be handled without the Rewrite recipe present .filter(d -> recipeRepo.getRecipe(d.getRecipeId()).isPresent()) @@ -177,7 +176,7 @@ public class RewriteCodeActionHandler implements JavaCodeActionHandler { m.getRecipeId(), doc.getUri(), s, - m.getScope() == null ? null : m.getScope().toString(), + m.getScope() == null ? null : m.getScope(), m.getParameters() == null ? Collections.emptyMap() : m.getParameters() )); return ca; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java index 280b75ab1..657e083bb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRecipeRepository.java @@ -11,6 +11,10 @@ package org.springframework.ide.vscode.boot.java.rewrite; import java.io.File; +import java.io.FileInputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; @@ -20,9 +24,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,31 +46,27 @@ import org.openrewrite.Result; import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; import org.openrewrite.Validated; -import org.openrewrite.config.Environment; import org.openrewrite.config.RecipeDescriptor; +import org.openrewrite.config.YamlResourceLoader; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.java.JavaParser; import org.openrewrite.java.tree.J.CompilationUnit; import org.openrewrite.maven.MavenParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.rewrite.codeaction.AutowiredFieldIntoConstructorParameterCodeAction; -import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction; -import org.springframework.ide.vscode.boot.java.rewrite.codeaction.NoRequestMappingAnnotationCodeAction; -import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.BeanMethodNotPublicProblem; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.PreciseBeanTypeProblem; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.NoAutowiredOnConstructorProblem; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.UnnecessarySpringExtensionProblem; +import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IClasspathUtil; 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.ListenerList; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.rewrite.LoadUtils; import org.springframework.ide.vscode.commons.rewrite.LoadUtils.DurationTypeConverter; import org.springframework.ide.vscode.commons.rewrite.ORDocUtils; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.StsEnvironment; import org.springframework.ide.vscode.commons.rewrite.maven.MavenProjectParser; import com.google.common.collect.ImmutableList; @@ -76,6 +78,9 @@ import com.google.gson.JsonElement; public class RewriteRecipeRepository { + private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload"; + private static final String CMD_REWRITE_EXECUTE = "sts/rewrite/execute"; + private static final String CMD_REWRITE_LIST = "sts/rewrite/list"; private static final Logger log = LoggerFactory.getLogger(RewriteRecipeRepository.class); private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand"; @@ -84,11 +89,21 @@ public class RewriteRecipeRepository { final private SimpleLanguageServer server; final private Map recipes; + final private List globalCommandRecipes; final private JavaProjectFinder projectFinder; - final public CompletableFuture loaded; + final private List codeActionDescriptors; + + final private List javaProblemDescriptors; + + final private ListenerList loadListeners; + + private CompletableFuture loaded; + + private Set scanFiles = Collections.emptySet(); + private Set scanDirs = Collections.emptySet(); static final Set TOP_LEVEL_RECIPES = Set.of( "org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration", @@ -104,40 +119,62 @@ public class RewriteRecipeRepository { .registerTypeAdapter(Duration.class, new DurationTypeConverter()) .create(); - private List codeActionDescriptors = List.of( - new AutowiredFieldIntoConstructorParameterCodeAction(), - new BeanMethodsNotPublicCodeAction(), - new NoRequestMappingAnnotationCodeAction(), - new UnnecessarySpringExtensionCodeAction() - ); - - private List javaProblemDescriptors = List.of( - new BeanMethodNotPublicProblem(), - new NoAutowiredOnConstructorProblem(), - new UnnecessarySpringExtensionProblem(), - new PreciseBeanTypeProblem() - ); - - public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) { + public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder, BootJavaConfig config) { this.server = server; this.projectFinder = projectFinder; this.recipes = new HashMap<>(); this.globalCommandRecipes = new ArrayList<>(); - this.loaded = CompletableFuture.runAsync(this::loadRecipes); + this.codeActionDescriptors = new ArrayList<>(); + this.javaProblemDescriptors = new ArrayList<>(); + this.loadListeners = new ListenerList<>(); + + server.doOnInitialized(() -> { + this.scanDirs = config.getRecipeDirectories(); + this.scanFiles = config.getRecipeFiles(); + load().thenAccept(v -> registerCommands()); + config.addListener(l -> { + if (!scanDirs.equals(config.getRecipeDirectories()) + || !scanFiles.equals(config.getRecipeFiles())) { + // Eclipse client sends one event for init and the other config changed event due to remote app value expr listener. + // Therefore it is best to store the scanDirs here right after it is received, not during scan process or anything else done async + scanDirs = config.getRecipeDirectories(); + scanFiles = config.getRecipeFiles(); + load(); + } + }); + }); } - private void loadRecipes() { + public CompletableFuture load() { + return this.loaded = CompletableFuture.runAsync(() -> { + clearRecipes(); + loadRecipes(); + loadListeners.fire(null); + }); + } + + private synchronized void clearRecipes() { + recipes.clear(); + globalCommandRecipes.clear(); + codeActionDescriptors.clear(); + javaProblemDescriptors.clear(); + } + + private synchronized void loadRecipes() { try { server.getProgressService().progressBegin(RECIPES_LOADING_PROGRESS, "Loading Rewrite Recipes", null); log.info("Loading Rewrite Recipes..."); - for (Recipe r : Environment.builder().scanRuntimeClasspath().build().listRecipes()) { + StsEnvironment env = createRewriteEnvironment(); + for (Recipe r : env.listRecipes()) { if (r.getName() != null) { if (recipes.containsKey(r.getName())) { log.error("Duplicate ids: '" + r.getName() + "'"); } recipes.put(r.getName(), r); - if (TOP_LEVEL_RECIPES.contains(r.getName())) { + if (TOP_LEVEL_RECIPES.contains(r.getName()) || r.getName().startsWith("rewrite.test.") + || r.getName().startsWith("org.rewrite.java.security") + || r.getName().startsWith("org.springframework.rewrite.test")) { Validated validation = Validated.invalid(null, null, null); try { validation = r.validate(); @@ -150,14 +187,47 @@ public class RewriteRecipeRepository { } } } + javaProblemDescriptors.addAll(env.listProblemDescriptors()); + codeActionDescriptors.addAll(env.listCodeActionDescriptors()); log.info("Done loading Rewrite Recipes"); - server.doOnInitialized(() -> registerCommands()); } catch (Throwable t) { - server.getProgressService().progressDone(RECIPES_LOADING_PROGRESS); log.error("", t); + } finally { + server.getProgressService().progressDone(RECIPES_LOADING_PROGRESS); } } + private StsEnvironment createRewriteEnvironment() { + StsEnvironment.Builder builder = (StsEnvironment.Builder) StsEnvironment.builder().scanRuntimeClasspath(); + for (String p : scanFiles) { + try { + Path f = Path.of(p); + String pathStr = f.toString(); + if (pathStr.endsWith(".jar")) { + URLClassLoader classLoader = new URLClassLoader(new URL[] { f.toUri().toURL() }, + getClass().getClassLoader()); + builder.scanJar(f, classLoader); + } else if (pathStr.endsWith(".yml") || pathStr.endsWith(".yaml")) { + builder.load(new YamlResourceLoader(new FileInputStream(f.toFile()), f.toUri(), new Properties())); + } + } catch (Exception e) { + log.error("Skipping folder " + p, e); + } + } + for (String p : scanDirs) { + try { + Path d = Path.of(p); + if (Files.isDirectory(d)) { + URLClassLoader classLoader = new URLClassLoader(new URL[] { d.toUri().toURL()}, getClass().getClassLoader()); + builder.scanPath(d, classLoader); + } + } catch (Exception e) { + log.error("Skipping folder " + p, e); + } + } + return (StsEnvironment) builder.build(); + } + public Optional getRecipe(String name) { return Optional.ofNullable(recipes.get(name)); } @@ -223,7 +293,7 @@ public class RewriteRecipeRepository { Builder listBuilder = ImmutableList.builder(); - server.onCommand("sts/rewrite/list", params -> { + server.onCommand(CMD_REWRITE_LIST, params -> { JsonElement uri = (JsonElement) params.getArguments().get(0); return loaded.thenApply(v -> { if (uri == null) { @@ -233,9 +303,9 @@ public class RewriteRecipeRepository { } }); }); - listBuilder.add("sts/rewrite/list"); + listBuilder.add(CMD_REWRITE_LIST); - server.onCommand("sts/rewrite/execute", params -> { + server.onCommand(CMD_REWRITE_EXECUTE, params -> { String uri = ((JsonElement) params.getArguments().get(0)).getAsString(); JsonElement recipesJson = ((JsonElement) params.getArguments().get(1)); @@ -254,7 +324,10 @@ public class RewriteRecipeRepository { return apply(aggregateRecipe, uri, progressToken); } }); - listBuilder.add("sts/rewrite/execute"); + listBuilder.add(CMD_REWRITE_EXECUTE); + + server.onCommand(CMD_REWRITE_RELOAD, params -> load().thenApply((v) -> "executed")); + listBuilder.add(CMD_REWRITE_RELOAD); for (Recipe r : globalCommandRecipes) { listBuilder.add(createGlobalCommand(r)); @@ -271,7 +344,6 @@ public class RewriteRecipeRepository { server.getClient().registerCapability(params).thenAccept((v) -> { server.onShutdown(() -> server.getClient().unregisterCapability(new UnregistrationParams(List.of(new Unregistration(registrationId, WORKSPACE_EXECUTE_COMMAND))))); log.info("Done registering commands for rewrite recipes"); - server.getProgressService().progressDone(RECIPES_LOADING_PROGRESS); }); } @@ -370,6 +442,10 @@ public class RewriteRecipeRepository { } } + public void onRecipesLoaded(Consumer l) { + loadListeners.add(l); + } + // private static Recipe convert(Recipe r, RecipeDescriptor d) { // try { // if (d.selected) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java index 55cf30f91..47899f0f5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteReconciler.java @@ -14,7 +14,6 @@ import java.io.ByteArrayInputStream; import java.net.URI; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -39,7 +38,6 @@ import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.java.reconcilers.JavaReconciler; import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings.Data; -import org.springframework.ide.vscode.boot.java.rewrite.reconcile.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; @@ -49,6 +47,8 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemC import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -103,24 +103,26 @@ public class RewriteReconciler implements JavaReconciler { if (range != null) { RecipeSpringJavaProblemDescriptor recipeFixDescriptor = recipeRepo.getProblemRecipeDescriptor(m.getRecipeId()); if (recipeFixDescriptor != null && recipeFixDescriptor.getScopes() != null && recipeRepo.getRecipe(recipeFixDescriptor.getRecipeId()).isPresent()) { - return Arrays.stream(recipeFixDescriptor.getScopes()).map(s -> createProblemFromScope(doc, recipeFixDescriptor, s, m, range)).collect(Collectors.toList()); + return List.of(createProblemFromScope(doc, recipeFixDescriptor, m, range)); } } } return Collections.emptyList(); } - private ReconcileProblemImpl createProblemFromScope(IDocument doc, RecipeSpringJavaProblemDescriptor recipeFixDescriptor, RecipeScope s, + private ReconcileProblemImpl createProblemFromScope(IDocument doc, RecipeSpringJavaProblemDescriptor recipeFixDescriptor, FixAssistMarker m, Range range) { ProblemType problemType = recipeFixDescriptor.getProblemType(); ReconcileProblemImpl problem = new ReconcileProblemImpl(problemType, problemType.getLabel(), range.getStart().getOffset(), range.getEnd().getOffset() - range.getStart().getOffset()); - QuickfixType quickfixType = quickfixRegistry.getQuickfixType(m.getRecipeId()); - if (quickfixType != null) { - problem.addQuickfix(new QuickfixData<>( - quickfixType, - new Data(m.getRecipeId(), doc.getUri(), s, m.getScope().toString(), m.getParameters()), - recipeFixDescriptor.getLabel(s) - )); + QuickfixType quickfixType = quickfixRegistry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX); + if (quickfixType != null && m.getRecipeId() != null) { + for (RecipeScope s : recipeFixDescriptor.getScopes()) { + problem.addQuickfix(new QuickfixData<>( + quickfixType, + new Data(m.getRecipeId(), doc.getUri(), s, m.getScope(), m.getParameters()), + recipeFixDescriptor.getLabel(s) + )); + } } return problem; } @@ -159,10 +161,6 @@ public class RewriteReconciler implements JavaReconciler { private List getProblemRecipeDescriptors(IJavaProject project) throws InterruptedException, ExecutionException { - // Wait for recipe repo to load if not loaded - should be loaded by the time we - // get here. - recipeRepo.loaded.get(); - return recipeRepo.getProblemRecipeDescriptors().stream().filter(d -> d.getProblemType() != null).filter(d -> { switch (config.getProblemApplicability(d.getProblemType())) { case ON: diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java index 0ac09d359..6bd20f55f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/RewriteRefactorings.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.UUID; import java.util.stream.Collectors; import org.eclipse.lsp4j.CodeAction; @@ -31,6 +30,7 @@ import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.marker.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.java.IJavaProject; @@ -40,6 +40,7 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHa import org.springframework.ide.vscode.commons.languageserver.util.CodeActionResolver; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.rewrite.ORDocUtils; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.ORAstUtils; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -50,6 +51,8 @@ import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler { + + public static final String REWRITE_RECIPE_QUICKFIX = "org.openrewrite.rewrite"; private static final Logger log = LoggerFactory.getLogger(RewriteRefactorings.class); @@ -163,11 +166,18 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler } } if (d.recipeScope == RecipeScope.NODE) { - UUID astNodeId = UUID.fromString(d.scope); - if (astNodeId == null) { + if (d.scope == null) { throw new IllegalArgumentException("Missing scope AST node!"); } else { - r = ORAstUtils.nodeRecipe(r, j -> j != null && astNodeId.equals(j.getId())); + r = ORAstUtils.nodeRecipe(r, j -> { + if (j != null) { + Range range = j.getMarkers().findFirst(Range.class).orElse(null); + if (range != null) { + return d.scope.getStart().getOffset() <= range.getStart().getOffset() && range.getEnd().getOffset() <= d.scope.getEnd().getOffset(); + } + } + return false; + }); } } return r; @@ -177,9 +187,9 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler public String id; public String docUri; public RecipeScope recipeScope; - public String scope; + public Range scope; public Map params; - public Data(String id, String docUri, RecipeScope recipeScope, String scope, Map params) { + public Data(String id, String docUri, RecipeScope recipeScope, Range scope, Map params) { this.id = id; this.docUri = docUri; this.recipeScope = recipeScope; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java index 0b0ce4574..e5bb1f2d1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/AutowiredFieldIntoConstructorParameterCodeAction.java @@ -24,9 +24,10 @@ import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.J.VariableDeclarations; import org.openrewrite.java.tree.JavaType.FullyQualified; import org.openrewrite.java.tree.TypeUtils; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; @@ -68,7 +69,7 @@ public class AutowiredFieldIntoConstructorParameterCodeAction implements RecipeC if (fqType != null && isApplicableType(fqType)) { m = m.withMarkers(m.getMarkers().add(new FixAssistMarker(Tree.randomId()) .withRecipeId(getRecipeId()) - .withScope(classDeclaration.getId()) + .withScope(classDeclaration.getMarkers().findFirst(Range.class).get()) .withParameters(Map.of("classFqName", fqType.getFullyQualifiedName(), "fieldName", multiVariable.getVariables().get(0).getSimpleName())))); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java index 7f9b9fe57..dc9f895ab 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/BeanMethodsNotPublicCodeAction.java @@ -18,10 +18,11 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; public class BeanMethodsNotPublicCodeAction implements RecipeCodeActionDescriptor { @@ -60,7 +61,7 @@ public class BeanMethodsNotPublicCodeAction implements RecipeCodeActionDescripto // mark public modifier FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId()) .withRecipeId(ID) - .withScope(m.getId()); + .withScope(m.getMarkers().findFirst(Range.class).get()); m = m.withModifiers(ListUtils.map(m.getModifiers(), modifier -> { if (modifier.getType() == J.Modifier.Type.Public) { return modifier.withMarkers(modifier.getMarkers().add(fixAssistMarker)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java index b7320079b..96dd60d89 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/codeaction/NoRequestMappingAnnotationCodeAction.java @@ -18,9 +18,10 @@ import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; public class NoRequestMappingAnnotationCodeAction implements RecipeCodeActionDescriptor { @@ -53,7 +54,7 @@ public class NoRequestMappingAnnotationCodeAction implements RecipeCodeActionDes if (REQUEST_MAPPING_ANNOTATION_MATCHER.matches(a) && getCursor().getParentOrThrow().getValue() instanceof J.MethodDeclaration) { FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId()) .withRecipeId(getRecipeId()) - .withScope(a.getId()); + .withScope(a.getMarkers().findFirst(Range.class).get()); a = a.withMarkers(a.getMarkers().add(fixAssistMarker)); } return a; 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 index 09bae1583..bc744550a 100644 --- 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 @@ -14,7 +14,6 @@ import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.spri import java.util.Arrays; import java.util.List; -import java.util.UUID; import org.openrewrite.ExecutionContext; import org.openrewrite.Tree; @@ -22,12 +21,13 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.JavaType.FullyQualified; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; +import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; public class UnnecessarySpringExtensionCodeAction implements RecipeCodeActionDescriptor { @@ -80,10 +80,10 @@ public class UnnecessarySpringExtensionCodeAction implements RecipeCodeActionDes FullyQualified fq = TypeUtils.asFullyQualified(a.getType()); return fq != null && SPRING_BOOT_TEST_ANNOTATIONS.contains(fq.getFullyQualifiedName()); })) { - UUID id = c.getId(); + Range range = c.getMarkers().findFirst(Range.class).get(); c = c.withLeadingAnnotations(ListUtils.map(c.getLeadingAnnotations(), a -> { if (SPRING_EXTENSION_ANNOTATIN_MATCHER.matches(a)) { - return a.withMarkers(a.getMarkers().add(new FixAssistMarker(Tree.randomId()).withRecipeId(ID).withScope(id))); + return a.withMarkers(a.getMarkers().add(new FixAssistMarker(Tree.randomId()).withRecipeId(ID).withScope(range))); } return a; })); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java index 82ec1eca2..2a6996c39 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/BeanMethodNotPublicProblem.java @@ -11,8 +11,9 @@ package org.springframework.ide.vscode.boot.java.rewrite.reconcile; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; import org.springframework.ide.vscode.boot.java.rewrite.codeaction.BeanMethodsNotPublicCodeAction; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; public class BeanMethodNotPublicProblem extends BeanMethodsNotPublicCodeAction implements RecipeSpringJavaProblemDescriptor { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java index 7ae897079..b299ce1b0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/NoAutowiredOnConstructorProblem.java @@ -21,12 +21,14 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.ClassDeclaration; import org.openrewrite.java.tree.J.MethodDeclaration; import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.marker.Range; import org.openrewrite.java.tree.Statement; import org.openrewrite.java.tree.TypeUtils; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.AnnotationHierarchies; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; @@ -76,7 +78,7 @@ public class NoAutowiredOnConstructorProblem implements RecipeSpringJavaProblemD MethodDeclaration constructor = (MethodDeclaration) s; FixAssistMarker fixAssistMarker = new FixAssistMarker(Tree.randomId()) .withRecipeId(ID) - .withScope(getCursor().firstEnclosing(ClassDeclaration.class).getId()); + .withScope(getCursor().firstEnclosing(ClassDeclaration.class).getMarkers().findFirst(Range.class).get()); constructor = constructor.withLeadingAnnotations(ListUtils.map(constructor.getLeadingAnnotations(), a -> { if (TypeUtils.isOfClassType(a.getType(), Annotations.AUTOWIRED)) { a = a.withMarkers(a.getMarkers().add(fixAssistMarker)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/PreciseBeanTypeProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/PreciseBeanTypeProblem.java index 848c7a31a..435a816e4 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/PreciseBeanTypeProblem.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/PreciseBeanTypeProblem.java @@ -19,12 +19,14 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J.Return; +import org.openrewrite.marker.Range; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.TypeUtils; import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeCodeActionDescriptor; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeCodeActionDescriptor; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.FixAssistMarker; public class PreciseBeanTypeProblem implements RecipeSpringJavaProblemDescriptor { @@ -60,7 +62,7 @@ public class PreciseBeanTypeProblem implements RecipeSpringJavaProblemDescriptor if ((o instanceof JavaType.FullyQualified && m.getReturnTypeExpression().getType() instanceof JavaType.FullyQualified) || (o instanceof JavaType.Array && m.getReturnTypeExpression().getType() instanceof JavaType.Array)) { m = m.withReturnTypeExpression(m.getReturnTypeExpression().withMarkers(m.getReturnTypeExpression().getMarkers().add( - new FixAssistMarker(Tree.randomId()).withScope(m.getId()).withRecipeId(getRecipeId())))); + new FixAssistMarker(Tree.randomId()).withScope(m.getMarkers().findFirst(Range.class).get()).withRecipeId(getRecipeId())))); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java index 48c1c7717..8dbab5d74 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/UnnecessarySpringExtensionProblem.java @@ -11,8 +11,9 @@ package org.springframework.ide.vscode.boot.java.rewrite.reconcile; import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType; -import org.springframework.ide.vscode.boot.java.rewrite.RecipeScope; import org.springframework.ide.vscode.boot.java.rewrite.codeaction.UnnecessarySpringExtensionCodeAction; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; +import org.springframework.ide.vscode.commons.rewrite.config.RecipeSpringJavaProblemDescriptor; public class UnnecessarySpringExtensionProblem extends UnnecessarySpringExtensionCodeAction implements RecipeSpringJavaProblemDescriptor { diff --git a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts index 87279e8c7..7aaa594c0 100644 --- a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts +++ b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts @@ -211,6 +211,7 @@ export function activate( context: VSCode.ExtensionContext ) { context.subscriptions.push( - VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list', liveHoverConnectHandler) + VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list', liveHoverConnectHandler), + VSCode.commands.registerCommand('vscode-spring-boot.rewrite.reload', () => VSCode.commands.executeCommand('sts/rewrite/reload')) ); } \ No newline at end of file diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index c94193c8d..2e1eedb82 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -93,6 +93,11 @@ "command": "vscode-spring-boot.rewrite.list", "category": "Spring Boot", "title": "Rewrite Refactorings..." + }, + { + "command": "vscode-spring-boot.rewrite.reload", + "title": "Reload Rewrite Recipes, Code Actions, Problem and Quick Fix Descriptors", + "category": "Spring Boot" } ], "configuration": [ @@ -106,11 +111,6 @@ "default": false, "description": "Experimental support for Rewrite recipes refactoring the whole maven projects via commands" }, - "boot-java.rewrite.reconcile": { - "type": "boolean", - "default": false, - "description": "Experimental reconciling for Java source based on Rewrite project" - }, "boot-java.live-information.automatic-connection.on": { "type": "boolean", "default": true, @@ -188,6 +188,34 @@ } } }, + { + "id": "rewrite", + "title": "Rewrite", + "order": 400, + "properties": { + "boot-java.rewrite.reconcile": { + "type": "boolean", + "default": false, + "description": "Experimental reconciling for Java source based on Rewrite project" + }, + "boot-java.rewrite.scan-files": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "JAR and YAML files to scan for recipes, code actions, problem and quick fix descriptors" + }, + "boot-java.rewrite.scan-dirs": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "Java project output folders to scan for recipes" + } + } + }, { "id": "ls", "title": "Language Server",