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 d74c89682..12e079f49 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/plugin.xml @@ -196,7 +196,7 @@ category="org.springframework.tooling.boot.ls.preferences" class="org.springframework.tooling.boot.ls.RewritePreferencePage" id="org.springframework.tooling.boot.ls.rewrite" - name="Rewrite"> + name="OpenRewrite"> + class="org.springframework.tooling.boot.ls.commands.RewriteRefactoringsHandler$RefactorBootProject" + commandId="org.springframework.tooling.boot.ls.rewrite.refactor"> + + + id="org.springframework.tooling.boot.ls.rewrite.refactor" + name="Refactor Spring Boot Project..."> + + @@ -452,7 +462,7 @@ requiresUIAccess="false"/> + name="Spring - OpenRewrite Recipes"> @@ -522,6 +532,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , JsonDeserializer { @Override public JsonElement serialize(Duration src, Type srcType, JsonSerializationContext context) { @@ -73,6 +79,16 @@ public class RewriteRefactoringsHandler extends AbstractHandler { private static final String REWRITE_REFACTORINGS_LIST = "sts/rewrite/list"; private static final String REWRITE_REFACTORINGS_EXEC = "sts/rewrite/execute"; + + private RecipeFilter recipeFilter; + + public RewriteRefactoringsHandler() { + this(RecipeFilter.ALL); + } + + public RewriteRefactoringsHandler(RecipeFilter recipeFilter) { + this.recipeFilter = recipeFilter; + } @Override public Object execute(ExecutionEvent event) throws ExecutionException { @@ -96,7 +112,7 @@ public class RewriteRefactoringsHandler extends AbstractHandler { final String uri = project.getLocationURI().toString(); ExecuteCommandParams commandParams = new ExecuteCommandParams(); commandParams.setCommand(REWRITE_REFACTORINGS_LIST); - commandParams.setArguments(List.of(uri)); + commandParams.setArguments(List.of(uri, recipeFilter.toString())); try { List allRewriteRecipesJson = new ArrayList<>(); @@ -152,5 +168,16 @@ public class RewriteRefactoringsHandler extends AbstractHandler { } return null; } - + + public static class UpgradeBootVersion extends RewriteRefactoringsHandler { + public UpgradeBootVersion() { + super(RecipeFilter.BOOT_UPGRADE); + } + } + + public static class RefactorBootProject extends RewriteRefactoringsHandler { + public RefactorBootProject() { + super(RecipeFilter.NON_BOOT_UPGRADE); + } + } } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java index 698dbc39f..39bac745d 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/prefs/StringListEditor.java @@ -29,6 +29,9 @@ public class StringListEditor extends ListEditor { } public static String[] decode(String value) { + if (value.isEmpty()) { + return new String[0]; + } return value.split(DELIMITER); } 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 a41ecbe8f..c96add9f5 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 @@ -31,6 +31,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -78,6 +79,26 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; public class RewriteRecipeRepository implements ApplicationContextAware { + + enum RecipeFilter { + ALL, + BOOT_UPGRADE, + NON_BOOT_UPGRADE + } + + private static final Pattern P1 = Pattern.compile("(Upgrade|Migrate)SpringBoot_\\d+_\\d+"); + + private static final Map> RECIPE_LIST_FILTERS = new HashMap<>(); + static { + RECIPE_LIST_FILTERS.put(RecipeFilter.ALL, r -> true); + RECIPE_LIST_FILTERS.put(RecipeFilter.BOOT_UPGRADE, r -> { + String n = lastTokenAfterDot(r.getName()); + return P1.matcher(n).matches(); + }); + RECIPE_LIST_FILTERS.put(RecipeFilter.NON_BOOT_UPGRADE, r -> { + return RECIPE_LIST_FILTERS.get(RecipeFilter.BOOT_UPGRADE).negate().test(r); + }); + } private static final String CMD_REWRITE_RELOAD = "sts/rewrite/reload"; private static final String CMD_REWRITE_EXECUTE = "sts/rewrite/execute"; @@ -123,7 +144,7 @@ public class RewriteRecipeRepository implements ApplicationContextAware { config.addListener(l -> { Set recipeFilterFromConfig = config.getRecipesFilters(); boolean firstTimeConfig = recipeFilters == UNINITIALIZED_SET && scanDirs == UNINITIALIZED_SET && scanFiles == UNINITIALIZED_SET; - if (recipeFilters == UNINITIALIZED_SET || recipeFilters.equals(recipeFilterFromConfig)) { + if (recipeFilters == UNINITIALIZED_SET || !recipeFilters.equals(recipeFilterFromConfig)) { recipeFilters = recipeFilterFromConfig; } if (scanDirs == UNINITIALIZED_SET || !scanDirs.equals(config.getRecipeDirectories()) @@ -186,23 +207,27 @@ public class RewriteRecipeRepository implements ApplicationContextAware { } private boolean isAcceptableGlobalCommandRecipe(Recipe r) { - for (String filter : recipeFilters) { - if (!filter.isBlank()) { - // Check if wild-card character present - if (filter.indexOf('*') < 0) { - // No wild-card - direct equality - if (filter.equals(r.getName())) { - return isRecipeValid(r); - } - } else { - // Wild-card present - convert to regular expression - if (Pattern.matches(filter.replaceAll("\\*", "\\.*"), r.getName())) { - return isRecipeValid(r); + if (recipeFilters.isEmpty()) { + return isRecipeValid(r); + } else { + for (String filter : recipeFilters) { + if (!filter.isBlank()) { + // Check if wild-card character present + if (filter.indexOf('*') < 0) { + // No wild-card - direct equality + if (filter.equals(r.getName())) { + return isRecipeValid(r); + } + } else { + // Wild-card present - convert to regular expression + if (Pattern.matches(filter.replaceAll("\\*", "\\.*"), r.getName())) { + return isRecipeValid(r); + } } } } + return false; } - return false; } private static boolean isRecipeValid(Recipe r) { @@ -298,11 +323,15 @@ public class RewriteRecipeRepository implements ApplicationContextAware { private void registerCommands() { server.onCommand(CMD_REWRITE_LIST, params -> { JsonElement uri = (JsonElement) params.getArguments().get(0); + RecipeFilter f = params.getArguments().size() > 1 ? RecipeFilter.valueOf(((JsonElement) params.getArguments().get(1)).getAsString()) : RecipeFilter.ALL; return loaded.thenApply(v -> { if (uri == null) { return Collections.emptyList(); } else { - return listProjectRefactoringRecipes(uri.getAsString()).stream().map(RewriteRecipeRepository::recipeToJson).collect(Collectors.toList()); + return listProjectRefactoringRecipes(uri.getAsString()).stream() + .filter(RECIPE_LIST_FILTERS.get(f)) + .map(RewriteRecipeRepository::recipeToJson) + .collect(Collectors.toList()); } }); }); @@ -443,6 +472,14 @@ public class RewriteRecipeRepository implements ApplicationContextAware { this.applicationContext = applicationContext; } + private static String lastTokenAfterDot(String s) { + int idx = s.lastIndexOf('.'); + if (idx >= 0 && idx < s.length() - 1) { + return s.substring(idx + 1); + } + return s; + } + // 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/reconcile/AutowiredFieldIntoConstructorParameterCodeAction.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AutowiredFieldIntoConstructorParameterCodeAction.java index 28ddd6c1e..d95a532ef 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AutowiredFieldIntoConstructorParameterCodeAction.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/rewrite/reconcile/AutowiredFieldIntoConstructorParameterCodeAction.java @@ -87,7 +87,7 @@ public class AutowiredFieldIntoConstructorParameterCodeAction implements RecipeC ); if (constructors.size() == 0) { m = m.withMarkers(m.getMarkers().add(marker)); - } else if (constructors.size() == 1 && !AutowiredFieldIntoConstructorParameterVisitor.isConstructorInitializingField(constructors.get(0), fieldName)) { + } else if (constructors.size() == 1 && AutowiredFieldIntoConstructorParameterVisitor.isNotConstructorInitializingField(constructors.get(0), fieldName)) { m = m.withMarkers(m.getMarkers().add(marker)); } else { List autowiredConstructors = constructors.stream().filter(constr -> constr.getLeadingAnnotations().stream() @@ -98,7 +98,7 @@ public class AutowiredFieldIntoConstructorParameterCodeAction implements RecipeC ) .limit(2) .collect(Collectors.toList()); - if (autowiredConstructors.size() == 1 && !AutowiredFieldIntoConstructorParameterVisitor.isConstructorInitializingField(autowiredConstructors.get(0), fieldName)) { + if (autowiredConstructors.size() == 1 && AutowiredFieldIntoConstructorParameterVisitor.isNotConstructorInitializingField(autowiredConstructors.get(0), fieldName)) { m = m.withMarkers(m.getMarkers().add(marker)); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java index 5e7027868..b03f89881 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/ProjectVersionDiagnosticProvider.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.validation.generations; import java.io.File; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.eclipse.lsp4j.Diagnostic; @@ -43,7 +44,8 @@ public class ProjectVersionDiagnosticProvider { Version javaProjectVersion = SpringProjectUtil.getSpringBootVersion(javaProject); if (javaProjectVersion == null) { - throw new Exception("Unable to resolve version for project: " + javaProject.getLocationUri().toString()); + log.warn("Unable to resolve version for project: " + javaProject.getLocationUri().toString()); + return new DiagnosticResult(buildFileUri, Collections.emptyList()); } List diagnostics = new ArrayList(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/VersionValidators.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/VersionValidators.java index 90e708845..2eddd8999 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/VersionValidators.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/VersionValidators.java @@ -199,7 +199,7 @@ public class VersionValidators { CodeAction ca = new CodeAction(); ca.setKind(CodeActionKind.QuickFix); - ca.setTitle("Upgrade to Version " + latest.toString()); + ca.setTitle("Upgrade to Spring Boot " + latest.toString() + " (Maven dependency version changes only)"); String commandId = SpringBootUpgrade.CMD_UPGRADE_SPRING_BOOT; ca.setCommand(new Command("Upgrade to Version " + latest.toString(), commandId, ImmutableList.of(javaProject.getLocationUri().toString(), latest.toString()))); @@ -230,7 +230,7 @@ public class VersionValidators { CodeAction ca = new CodeAction(); ca.setKind(CodeActionKind.QuickFix); - ca.setTitle("Upgrade to Version " + latest.toString()); + ca.setTitle("Upgrade to Spring Boot " + latest.toString() + " (executes the full project conversion recipe from OpenRewrite)"); String commandId = SpringBootUpgrade.CMD_UPGRADE_SPRING_BOOT; ca.setCommand(new Command("Upgrade to Version " + latest.toString(), commandId, ImmutableList.of(javaProject.getLocationUri().toString(), latest.toString()))); @@ -261,7 +261,7 @@ public class VersionValidators { CodeAction ca = new CodeAction(); ca.setKind(CodeActionKind.QuickFix); - ca.setTitle("Upgrade to Version " + latest.toString()); + ca.setTitle("Upgrade to Spring Boot " + latest.toString() + " (executes the full project conversion recipe from OpenRewrite)"); String commandId = SpringBootUpgrade.CMD_UPGRADE_SPRING_BOOT; ca.setCommand(new Command("Upgrade to Version " + latest.toString(), commandId, ImmutableList.of(javaProject.getLocationUri().toString(), latest.toString()))); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/preferences/VersionValidationProblemType.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/preferences/VersionValidationProblemType.java index e41602acd..060a8d0ce 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/preferences/VersionValidationProblemType.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/preferences/VersionValidationProblemType.java @@ -12,6 +12,9 @@ package org.springframework.ide.vscode.boot.validation.generations.preferences; import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.HINT; import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.ERROR; +import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.INFO; +import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.WARNING; +import static org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity.IGNORE; import org.springframework.ide.vscode.boot.common.SpringProblemCategories; import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemCategory; @@ -28,11 +31,11 @@ public enum VersionValidationProblemType implements ProblemType { // // SUPPORTED_COMMERCIAL_VERSION(HINT, "Supported Commercial Version", "Supported Commercial Version"), - UPDATE_LATEST_MAJOR_VERSION(HINT, "Update to Latest Major Version", "Update to Latest Major Version"), + UPDATE_LATEST_MAJOR_VERSION(IGNORE, "Update to Latest Major Version", "Update to Latest Major Version"), - UPDATE_LATEST_MINOR_VERSION(HINT, "Update to Latest Minor Version", "Update to Latest Minor Version"), + UPDATE_LATEST_MINOR_VERSION(INFO, "Update to Latest Minor Version", "Update to Latest Minor Version"), - UPDATE_LATEST_PATCH_VERSION(HINT, "Update to Latest Patch Version", "Update to Latest Patch Version"); + UPDATE_LATEST_PATCH_VERSION(WARNING, "Update to Latest Patch Version", "Update to Latest Patch Version"); private final ProblemSeverity defaultSeverity; private String description; diff --git a/headless-services/spring-boot-language-server/src/main/resources/problem-types.json b/headless-services/spring-boot-language-server/src/main/resources/problem-types.json index 3f9e5af4e..3721a5894 100644 --- a/headless-services/spring-boot-language-server/src/main/resources/problem-types.json +++ b/headless-services/spring-boot-language-server/src/main/resources/problem-types.json @@ -307,19 +307,19 @@ "code": "UPDATE_LATEST_MAJOR_VERSION", "label": "Update to Latest Major Version", "description": "Update to Latest Major Version", - "defaultSeverity": "HINT" + "defaultSeverity": "IGNORE" }, { "code": "UPDATE_LATEST_MINOR_VERSION", "label": "Update to Latest Minor Version", "description": "Update to Latest Minor Version", - "defaultSeverity": "HINT" + "defaultSeverity": "INFO" }, { "code": "UPDATE_LATEST_PATCH_VERSION", "label": "Update to Latest Patch Version", "description": "Update to Latest Patch Version", - "defaultSeverity": "HINT" + "defaultSeverity": "WARNING" } ] } diff --git a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts index df3b3fd64..8dc3ac208 100644 --- a/vscode-extensions/vscode-spring-boot/lib/rewrite.ts +++ b/vscode-extensions/vscode-spring-boot/lib/rewrite.ts @@ -3,6 +3,9 @@ import { LanguageClient } from "vscode-languageclient/node"; import * as VSCode from 'vscode'; import * as path from "path"; +const BOOT_UPGRADE = 'BOOT_UPGRADE'; +const OTHER_REFACTORINGS = 'NON_BOOT_UPGRADE'; + interface RecipeDescriptor { name: string; displayName: string; @@ -85,11 +88,11 @@ const SUB_RECIPES_BUTTON: VSCode.QuickInputButton = { tooltip: 'Sub-Recipes' } -async function liveHoverConnectHandler(uri: VSCode.Uri) { +async function showRefactorings(uri: VSCode.Uri, filter: string) { if (!uri) { uri = await getTargetPomXml(); } - const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true)).then((cmds: RecipeDescriptor[]) => cmds.map(convertToQuickPickItem)), []); + const choices = await showCurrentPathQuickPick(VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true), filter).then((cmds: RecipeDescriptor[]) => cmds.map(convertToQuickPickItem)), []); const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor); if (recipeDescriptors.length) { const aggregateRecipeDescriptor = recipeDescriptors.length === 1 ? recipeDescriptors[0] : { @@ -214,9 +217,16 @@ export function activate( context: VSCode.ExtensionContext ) { context.subscriptions.push( - VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list', params => { + VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list.boot-upgrades', param => { if (client.isRunning()) { - return liveHoverConnectHandler(params[0]); + return showRefactorings(param, BOOT_UPGRADE); + } else { + VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects"); + } + }), + VSCode.commands.registerCommand('vscode-spring-boot.rewrite.list.refactorings', param => { + if (client.isRunning()) { + return showRefactorings(param, OTHER_REFACTORINGS); } else { VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects"); } diff --git a/vscode-extensions/vscode-spring-boot/package.json b/vscode-extensions/vscode-spring-boot/package.json index 7717b4f31..40d0f04dd 100644 --- a/vscode-extensions/vscode-spring-boot/package.json +++ b/vscode-extensions/vscode-spring-boot/package.json @@ -32,7 +32,8 @@ "onLanguage:xml", "onLanguage:spring-factories", "onDebugResolve:java", - "onCommand:vscode-spring-boot.rewrite.list", + "onCommand:vscode-spring-boot.rewrite.list.refactorings", + "onCommand:vscode-spring-boot.rewrite.list.boot-upgrades", "onCommand:vscode-spring-boot.ls.start", "workspaceContains:pom.xml", "workspaceContains:*/pom.xml", @@ -94,14 +95,24 @@ "editor/context": [ { "when": "resourceFilename == pom.xml", - "command": "vscode-spring-boot.rewrite.list", + "command": "vscode-spring-boot.rewrite.list.refactorings", + "group": "SpringBoot" + }, + { + "when": "resourceFilename == pom.xml", + "command": "vscode-spring-boot.rewrite.list.boot-upgrades", "group": "SpringBoot" } ], "explorer/context": [ { "when": "resourceFilename == pom.xml && config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list", + "command": "vscode-spring-boot.rewrite.list.refactorings", + "group": "SpringBoot" + }, + { + "when": "resourceFilename == pom.xml && config.boot-java.rewrite.refactorings.on == true", + "command": "vscode-spring-boot.rewrite.list.boot-upgrades", "group": "SpringBoot" } ] @@ -114,13 +125,19 @@ }, { "enablement": "config.boot-java.rewrite.refactorings.on == true", - "command": "vscode-spring-boot.rewrite.list", + "command": "vscode-spring-boot.rewrite.list.boot-upgrades", "category": "Spring Boot", - "title": "Rewrite Refactorings..." + "title": "Upgrade Spring Boot Version..." + }, + { + "enablement": "config.boot-java.rewrite.refactorings.on == true", + "command": "vscode-spring-boot.rewrite.list.refactorings", + "category": "Spring Boot", + "title": "Refactor Spring Boot Project..." }, { "command": "vscode-spring-boot.rewrite.reload", - "title": "Reload Rewrite Recipes, Code Actions, Problem and Quick Fix Descriptors", + "title": "Reload OpenRewrite Recipes, Code Actions, Problem and Quick Fix Descriptors", "category": "Spring Boot" } ], @@ -209,7 +226,7 @@ }, { "id": "rewrite", - "title": "Rewrite", + "title": "OpenRewrite", "order": 400, "properties": { "boot-java.rewrite.refactorings.on": { @@ -220,7 +237,7 @@ "boot-java.rewrite.reconcile": { "type": "boolean", "default": false, - "description": "Experimental reconciling for Java source based on Rewrite project" + "description": "Reconciling Java Sources" }, "boot-java.rewrite.recipe-filters": { "type": "array", @@ -816,7 +833,7 @@ }, "spring-boot.ls.problem.version-validation.UPDATE_LATEST_MAJOR_VERSION": { "type": "string", - "default": "HINT", + "default": "IGNORE", "description": "Update to Latest Major Version", "enum": [ "IGNORE", @@ -828,7 +845,7 @@ }, "spring-boot.ls.problem.version-validation.UPDATE_LATEST_MINOR_VERSION": { "type": "string", - "default": "HINT", + "default": "INFO", "description": "Update to Latest Minor Version", "enum": [ "IGNORE", @@ -840,7 +857,7 @@ }, "spring-boot.ls.problem.version-validation.UPDATE_LATEST_PATCH_VERSION": { "type": "string", - "default": "HINT", + "default": "WARNING", "description": "Update to Latest Patch Version", "enum": [ "IGNORE",