Runtime load recipes yml/jar. Offscreen doc quick fix.

This commit is contained in:
aboyko
2022-09-16 18:52:48 -04:00
parent 01008b3375
commit 1a708a44e8
46 changed files with 1145 additions and 148 deletions

View File

@@ -131,6 +131,12 @@
id="org.springframework.tooling.boot.ls.preferences"
name="Spring Boot Language Server">
</page>
<page
category="org.springframework.tooling.boot.ls.preferences"
class="org.springframework.tooling.boot.ls.RewritePreferencePage"
id="org.springframework.tooling.boot.ls.rewrite"
name="Rewrite">
</page>
</extension>
@@ -338,9 +344,13 @@
<extension
point="org.eclipse.ui.quickAccess">
<computer
class="org.springframework.tooling.boot.ls.commands.LiveProcessCommandsQuickAccessProvider"
class="org.springframework.tooling.boot.ls.commands.RewriteCommandsQuickAccessProvider"
name="Spring - Live Process Information"
requiresUIAccess="false"/>
<computer
class="org.springframework.tooling.boot.ls.QuickAccessComputer1"
name="Spring - Rewrite Recipes">
</computer>
</extension>
<extension
point="org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.Contributor">

View File

@@ -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));
}
}

View File

@@ -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";
}

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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<LanguageServer> 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;
}
}

View File

@@ -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<Duration>, JsonDeserializer<Duration> {

View File

@@ -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;

View File

@@ -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<String> fileFilters;
public FileListEditor(String name, String labelText,
String dirChooserLabelText, Composite parent, List<String> 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<String> getValuesFromPreference(String rawValue) {
StringTokenizer st = new StringTokenizer(rawValue, File.pathSeparator
+ "\n\r");//$NON-NLS-1$
ArrayList<String> l = new ArrayList<>();
while (st.hasMoreElements()) {
l.add((String)st.nextElement());
}
return l;
}
}

View File

@@ -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<QuickfixData<?>> 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);
}

View File

@@ -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<Either<Command, CodeAction>> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TrackedDocument doc, CodeActionParams params) {
private List<Either<Command, CodeAction>> computeCodeActions(CancelChecker cancelToken, CodeActionCapabilities capabilities, TextDocument doc, CodeActionParams params) {
Builder<Either<Command,CodeAction>> 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.<Command, CodeAction>forRight(command))
.forEach(listBuilder::add);
params.getContext().getDiagnostics().forEach(d -> {
if (d.getData() != null) {
Type type = new TypeToken<List<CodeAction>>(){}.getType();
List<CodeAction> 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());
}

View File

@@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>qq</groupId>
<artifactId>commons-rewrite-test</artifactId>
<parent>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-parent</artifactId>
<version>1.39.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-rewrite</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -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<ExecutionContext> 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);
}
}

View File

@@ -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<?, ExecutionContext> 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;
}
};
}
}

View File

@@ -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";
}
}

View File

@@ -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<RecipeCodeActionDescriptor> getCodeActionDescriptors() {
return Collections.emptyList();
}
@Override
public List<RecipeSpringJavaProblemDescriptor> getProblemDescriptors() {
return List.of(new HelloMethodRenameProblemDescriptor());
}
}

View File

@@ -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

View File

@@ -22,7 +22,18 @@
<artifactId>commons-language-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-java</artifactId>
<version>${project.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.classgraph/classgraph -->
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.149</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-properties</artifactId>

View File

@@ -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<RecipeCodeActionDescriptor> getCodeActionDescriptors();
public abstract List<RecipeSpringJavaProblemDescriptor> getProblemDescriptors();
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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<Recipe> recipes = new ArrayList<>();
private final List<NamedStyles> styles = new ArrayList<>();
private final List<RecipeDescriptor> recipeDescriptors = new ArrayList<>();
private final List<CategoryDescriptor> categoryDescriptors = new ArrayList<>();
private final List<RecipeExample> recipeExamples = new ArrayList<>();
private final List<CodeActionRepository> 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<YamlResourceLoader> 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<Recipe> listRecipes() {
return recipes;
}
@Override
public Collection<RecipeDescriptor> listRecipeDescriptors() {
return recipeDescriptors;
}
@Override
public Collection<CategoryDescriptor> listCategoryDescriptors() {
return categoryDescriptors;
}
@Override
public Collection<NamedStyles> listStyles() {
return styles;
}
@Override
public Collection<RecipeExample> listRecipeExamples() {
return recipeExamples;
}
public List<CodeActionRepository> listCodeActionDescriptorsRepositories() {
return codeActionRepos;
}
}

View File

@@ -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<Stream<CodeActionRepository>> codeActionRepos;
public StsEnvironment(Collection<? extends ResourceLoader> 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<ResourceLoader>) f.get(this));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
public List<RecipeCodeActionDescriptor> listCodeActionDescriptors() {
return codeActionRepos.get().flatMap(r -> r.getCodeActionDescriptors().stream()).collect(Collectors.toList());
}
public List<RecipeSpringJavaProblemDescriptor> listProblemDescriptors() {
return codeActionRepos.get().flatMap(r -> r.getProblemDescriptors().stream()).collect(Collectors.toList());
}
public static Builder builder() {
return new Builder(new Properties());
}
}

View File

@@ -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<CodeActionRepository> listCodeActionDescriptorsRepositories();
}

View File

@@ -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;
}

View File

@@ -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<J, ExecutionContext> 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<J, ExecutionContext> v : getAfterVisitors(visitor)) {
doAfterVisit(v);
}
return t;
}
}
return t;
return super.visit(tree, ctx);
}
};

View File

@@ -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<String> getRecipeDirectories() {
return settings.getStringSet("boot-java", "rewrite", "scan-directories");
}
public Set<String> getRecipeFiles() {
return settings.getStringSet("boot-java", "rewrite", "scan-files");
}
public Settings getRawSettings() {
return settings;
}

View File

@@ -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);
}
}

View File

@@ -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.");

View File

@@ -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);
}
}

View File

@@ -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<RecipeCodeActionDescriptor> getCodeActionDescriptors() {
return List.of(
new AutowiredFieldIntoConstructorParameterCodeAction(),
new BeanMethodsNotPublicCodeAction(),
new NoRequestMappingAnnotationCodeAction(),
new UnnecessarySpringExtensionCodeAction()
);
}
@Override
public List<RecipeSpringJavaProblemDescriptor> getProblemDescriptors() {
return List.of(
new BeanMethodNotPublicProblem(),
new NoAutowiredOnConstructorProblem(),
new UnnecessarySpringExtensionProblem(),
new PreciseBeanTypeProblem()
);
}
}

View File

@@ -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<RecipeCodeActionDescriptor> 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;

View File

@@ -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<String, Recipe> recipes;
final private List<Recipe> globalCommandRecipes;
final private JavaProjectFinder projectFinder;
final public CompletableFuture<Void> loaded;
final private List<RecipeCodeActionDescriptor> codeActionDescriptors;
final private List<RecipeSpringJavaProblemDescriptor> javaProblemDescriptors;
final private ListenerList<Void> loadListeners;
private CompletableFuture<Void> loaded;
private Set<String> scanFiles = Collections.emptySet();
private Set<String> scanDirs = Collections.emptySet();
static final Set<String> 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<RecipeCodeActionDescriptor> codeActionDescriptors = List.of(
new AutowiredFieldIntoConstructorParameterCodeAction(),
new BeanMethodsNotPublicCodeAction(),
new NoRequestMappingAnnotationCodeAction(),
new UnnecessarySpringExtensionCodeAction()
);
private List<RecipeSpringJavaProblemDescriptor> 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<Void> 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<Recipe> getRecipe(String name) {
return Optional.ofNullable(recipes.get(name));
}
@@ -223,7 +293,7 @@ public class RewriteRecipeRepository {
Builder<Object> 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<Void> l) {
loadListeners.add(l);
}
// private static Recipe convert(Recipe r, RecipeDescriptor d) {
// try {
// if (d.selected) {

View File

@@ -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<RecipeSpringJavaProblemDescriptor> 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:

View File

@@ -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<String, Object> params;
public Data(String id, String docUri, RecipeScope recipeScope, String scope, Map<String, Object> params) {
public Data(String id, String docUri, RecipeScope recipeScope, Range scope, Map<String, Object> params) {
this.id = id;
this.docUri = docUri;
this.recipeScope = recipeScope;

View File

@@ -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()))));
}
}

View File

@@ -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));

View File

@@ -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;

View File

@@ -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;
}));

View File

@@ -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 {

View File

@@ -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));

View File

@@ -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()))));
}
}
}

View File

@@ -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 {

View File

@@ -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'))
);
}

View File

@@ -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",