Show recipe parameters. Load recipes from descriptors.

This commit is contained in:
BoykoAlex
2022-06-15 23:31:09 -04:00
parent 45aa106b02
commit cd5d0d786a
6 changed files with 434 additions and 97 deletions

View File

@@ -0,0 +1,164 @@
/*******************************************************************************
* 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;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.openrewrite.Recipe;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.config.OptionDescriptor;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.config.RecipeIntrospectionException;
import org.openrewrite.internal.RecipeIntrospectionUtils;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
public class LoadUtils {
public static class DurationTypeConverter implements JsonSerializer<Duration>, JsonDeserializer<Duration> {
@Override
public JsonElement serialize(Duration src, Type srcType, JsonSerializationContext context) {
return new JsonPrimitive(src.toNanos());
}
@Override
public Duration deserialize(JsonElement json, Type type, JsonDeserializationContext context)
throws JsonParseException {
return Duration.ofNanos(json.getAsLong());
}
}
@SuppressWarnings("unchecked")
public static Recipe createRecipe(RecipeDescriptor d) {
try {
Class<? extends Recipe> recipeClazz = (Class<? extends Recipe>) Class.forName(d.getName());
Recipe recipe = constructRecipe(recipeClazz, d.getOptions());
return recipe;
} catch (ClassNotFoundException e) {
DeclarativeRecipe recipe = new DeclarativeRecipe(d.getName(), d.getDisplayName(), d.getDescription(), d.getTags(), d.getEstimatedEffortPerOccurrence(), d.getSource());
for (RecipeDescriptor subDescriptor : d.getRecipeList()) {
recipe.doNext(createRecipe(subDescriptor));
}
return recipe;
}
}
public static Recipe constructRecipe(Class<?> recipeClass, List<OptionDescriptor> options) {
Constructor<?> primaryConstructor = RecipeIntrospectionUtils.getZeroArgsConstructor(recipeClass);
if (primaryConstructor == null) {
primaryConstructor = RecipeIntrospectionUtils.getPrimaryConstructor(recipeClass);
}
Object[] constructorArgs = new Object[primaryConstructor.getParameterCount()];
List<OptionDescriptor> remainingOptions = new ArrayList<>(options);
for (int i = 0; i < primaryConstructor.getParameters().length; i++) {
java.lang.reflect.Parameter param = primaryConstructor.getParameters()[i];
int j = 0;
for (; j < remainingOptions.size() && !param.getName().equals(remainingOptions.get(j).getName()); j++) {
// nothing
}
if (j < remainingOptions.size()) {
// found in option descriptors
OptionDescriptor optionDescriptor = remainingOptions.remove(j);
constructorArgs[i] = getOptionValueForType(param.getType(), optionDescriptor);
} else {
// param not found in option descriptors
if (param.getType().isPrimitive()) {
constructorArgs[i] = getPrimitiveDefault(param.getType());
} else {
constructorArgs[i] = null;
}
}
}
primaryConstructor.setAccessible(true);
try {
Recipe recipe = (Recipe) primaryConstructor.newInstance(constructorArgs);
for (OptionDescriptor option : remainingOptions) {
if (option.getValue() != null) {
Field f = recipe.getClass().getDeclaredField(option.getName());
f.setAccessible(true);
f.set(recipe, getOptionValueForType(f.getType(), option));
}
}
return recipe;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException | SecurityException e) {
// Should never happen
throw new RecipeIntrospectionException("Unable to call primary constructor for Recipe " + recipeClass, e);
}
}
private static Object getOptionValueForType(Class<?> type, OptionDescriptor option) {
if (option.getValue() instanceof Collection && type.isArray()) {
Collection<?> arrayValue = (Collection<?>) option.getValue();
Object[] valueToSet = (Object[]) Array.newInstance(type.getComponentType(), arrayValue.size());
int k = 0;
for (Object v : arrayValue) {
valueToSet[k] = getOptionValue(type.getComponentType().getSimpleName(), v);
}
return valueToSet;
} else {
return getOptionValue(option.getType(), option.getValue());
}
}
private static Object getOptionValue(String type, Object v) {
switch (type) {
case "int":
return ((Number) v).intValue();
case "long":
return ((Number) v).longValue();
case "short":
return ((Number) v).shortValue();
case "float":
return ((Number) v).floatValue();
case "double":
return ((Number) v).doubleValue();
default:
return v;
}
}
private static Object getPrimitiveDefault(Class<?> t) {
if (t.equals(byte.class)) {
return (byte) 0;
} else if (t.equals(short.class)) {
return (short) 0;
} else if (t.equals(int.class)) {
return 0;
} else if (t.equals(long.class)) {
return 0L;
} else if (t.equals(float.class)) {
return 0.0f;
} else if (t.equals(double.class)) {
return 0.0d;
} else if (t.equals(char.class)) {
return '\u0000';
} else if (t.equals(boolean.class)) {
return false;
} else {
throw new RecipeIntrospectionException(t.getCanonicalName() + " is not a supported primitive type");
}
}
}

View File

@@ -9,7 +9,7 @@ recipeList:
# Upgrade 3.0.x from 2.x
- org.openrewrite.java.spring.boot3.MavenPomUpgrade
- org.openrewrite.java.spring.boot3.data.UpgradeSpringData_3_0
# - org.openrewrite.java.spring.boot3.SpringBootProperties_3_0
- org.openrewrite.java.spring.boot3.Micrometer_3_0
---
########################################################################################################################
# SpringBoot 3_0 Maven Pom
@@ -31,5 +31,17 @@ recipeList:
key: 'java.version'
newValue: 17
addIfMissing: true
---
########################################################################################################################
# Spring Data 3.0 io.micrometer.core.instrument.binder -> io.micrometer.binder
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.spring.boot3.Micrometer_3_0
displayName: Micrometer compatible with Boot 3.x
description: Switch to Micrometer compatible with Boot 3.x
recipeList:
- org.openrewrite.java.ChangePackage:
oldPackageName: io.micrometer.core.instrument.binder
newPackageName: io.micrometer.binder
recursive: true

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openrewrite.Recipe;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.config.Environment;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.maven.UpgradeDependencyVersion;
public class LoadUtilsTest {
private static Environment env;
@BeforeClass
public static void setupAll() {
env = Environment.builder().scanRuntimeClasspath().build();
}
@Test
public void createRecipeTest() throws Exception {
RecipeDescriptor recipeDescriptor = env.listRecipeDescriptors().stream().filter(d -> "org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0".equals(d.getName())).findFirst().orElse(null);
assertNotNull(recipeDescriptor);
Recipe r = LoadUtils.createRecipe(recipeDescriptor);
assertTrue(r instanceof DeclarativeRecipe);
assertEquals("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0", r.getName());
assertEquals("Upgrade to Spring Boot 3.0 from prior 2.x version.", r.getDescription());
assertEquals("Upgrade to Spring Boot 3.0 from 2.x", r.getDisplayName());
assertEquals(3, r.getRecipeList().size());
Recipe pomRecipe = r.getRecipeList().get(0);
assertTrue(pomRecipe instanceof DeclarativeRecipe);
assertEquals("org.openrewrite.java.spring.boot3.MavenPomUpgrade", pomRecipe.getName());
assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from prior 2.x version.", pomRecipe.getDescription());
assertEquals("Upgrade Maven Pom to Spring Boot 3.0 from 2.x", pomRecipe.getDisplayName());
assertEquals(3, pomRecipe.getRecipeList().size());
r = pomRecipe.getRecipeList().get(0);
assertTrue(r instanceof UpgradeDependencyVersion);
UpgradeDependencyVersion upgradeDependencyRecipe = (UpgradeDependencyVersion) r;
assertEquals("org.openrewrite.maven.UpgradeDependencyVersion", upgradeDependencyRecipe.getName());
assertEquals("Upgrade Maven dependency version", upgradeDependencyRecipe.getDisplayName());
assertEquals(0, upgradeDependencyRecipe.getRecipeList().size());
assertTrue(upgradeDependencyRecipe.getNewVersion().startsWith("3.0."));
assertEquals("org.springframework.boot", upgradeDependencyRecipe.getGroupId());
assertEquals("*", upgradeDependencyRecipe.getArtifactId());
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.stream.Collectors;
import org.openrewrite.config.Environment;
import org.openrewrite.config.OptionDescriptor;
import org.openrewrite.config.RecipeDescriptor;
public class RecipesDescriptionGenerator {
public static void main(String[] args) throws IOException {
String s = Environment.builder().scanRuntimeClasspath().build().listRecipeDescriptors().stream()
.filter(d -> RewriteRecipeRepository.TOP_LEVEL_RECIPES.contains(d.getName()))
.map(d -> convertToMarkdown(d, 1))
.collect(Collectors.joining("\n\n"));
Path path = Paths.get("recipes.md");
path = path.toFile().getCanonicalFile().toPath();
System.out.println("Saving to file: " + path);
Files.write(path, s.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
}
private static String convertToMarkdown(RecipeDescriptor d, int level) {
StringBuilder sb = new StringBuilder();
if (!"org.openrewrite.yaml.ChangePropertyKey".equals(d.getName())) {
for (int i = 0; i < level; i++) {
sb.append('#');
}
sb.append(' ');
sb.append(d.getDisplayName());
sb.append(" (");
sb.append(d.getName());
sb.append(")\n");
sb.append(d.getDescription());
sb.append("\n\n");
for (OptionDescriptor option : d.getOptions()) {
sb.append("- **");
sb.append(option.getDisplayName());
sb.append("**: ");
sb.append(": `");
sb.append(option.getValue());
sb.append("`\n");
}
sb.append("\n");
sb.append(d.getRecipeList().stream().map(cd -> convertToMarkdown(cd, level + 1)).collect(Collectors.joining("\n\n")));
}
return sb.toString();
}
}

View File

@@ -13,8 +13,8 @@ package org.springframework.ide.vscode.boot.java.rewrite;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -39,8 +39,8 @@ import org.openrewrite.Recipe;
import org.openrewrite.Result;
import org.openrewrite.SourceFile;
import org.openrewrite.Validated;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.config.Environment;
import org.openrewrite.config.RecipeDescriptor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.maven.MavenParser;
import org.slf4j.Logger;
@@ -50,6 +50,8 @@ 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.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.maven.MavenProjectParser;
@@ -57,6 +59,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
public class RewriteRecipeRepository {
@@ -75,7 +78,7 @@ public class RewriteRecipeRepository {
final public CompletableFuture<Void> loaded;
private static final Set<String> TOP_LEVEL_RECIPES = Set.of(
static final Set<String> TOP_LEVEL_RECIPES = Set.of(
"org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration",
"org.openrewrite.java.spring.boot2.SpringBoot2BestPractices",
"org.openrewrite.java.spring.boot2.SpringBoot1To2Migration",
@@ -85,6 +88,10 @@ public class RewriteRecipeRepository {
"org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0"
);
private static Gson serializationGson = new GsonBuilder()
.registerTypeAdapter(Duration.class, new DurationTypeConverter())
.create();
public RewriteRecipeRepository(SimpleLanguageServer server, JavaProjectFinder projectFinder) {
this.server = server;
this.projectFinder = projectFinder;
@@ -95,6 +102,7 @@ public class RewriteRecipeRepository {
private void loadRecipes() {
try {
server.getProgressService().progressEvent(RECIPES_LOADING_PROGRESS, "Loading Rewrite Recipes...");
log.info("Loading Rewrite Recipes...");
for (Recipe r : Environment.builder().scanRuntimeClasspath().build().listRecipes()) {
if (r.getName() != null) {
@@ -119,6 +127,7 @@ public class RewriteRecipeRepository {
log.info("Done loading Rewrite Recipes");
server.doOnInitialized(() -> registerCommands());
} catch (Throwable t) {
server.getProgressService().progressEvent(RECIPES_LOADING_PROGRESS, null);
log.error("", t);
}
}
@@ -127,46 +136,39 @@ public class RewriteRecipeRepository {
return Optional.ofNullable(recipes.get(name));
}
private static JsonElement recipeToJson(Recipe r) {
JsonElement jsonElement = serializationGson.toJsonTree(r.getDescriptor());
return jsonElement;
}
private void registerCommands() {
log.info("Registering commands for rewrite recipes...");
Builder<Object> listBuilder = ImmutableList.builder();
server.onCommand("sts/rewrite/list", params -> {
JsonElement uri = (JsonElement) params.getArguments().get(0);
return CompletableFuture.completedFuture(uri == null ? Collections.emptyList() : listProjectRefactoringCommands(uri.getAsString()));
return CompletableFuture.completedFuture(uri == null ? Collections.emptyList() : listProjectRefactoringRecipes(uri.getAsString()).stream().map(RewriteRecipeRepository::recipeToJson).collect(Collectors.toList()));
});
listBuilder.add("sts/rewrite/list");
server.onCommand("sts/rewrite/execute", params -> {
String uri = ((JsonElement) params.getArguments().get(0)).getAsString();
JsonElement recipesJson = ((JsonElement) params.getArguments().get(1));
RecipeDescriptor[] recipeDescriptors = new Gson().fromJson(recipesJson, RecipeDescriptor[].class);
List<Recipe> convertedRecipes = Arrays.stream(recipeDescriptors)
.filter(rd -> rd.selected)
.filter(rd -> recipes.containsKey(rd.id))
.map(rd -> convert(recipes.get(rd.id), rd))
.collect(Collectors.toList());
if (convertedRecipes.isEmpty()) {
RecipeDescriptor d = serializationGson.fromJson(recipesJson, RecipeDescriptor.class);
Recipe aggregateRecipe = LoadUtils.createRecipe(d);
if (aggregateRecipe.getRecipeList().isEmpty()) {
throw new RuntimeException("Not recipes to execute!");
} else if (convertedRecipes.size() == 1) {
Recipe r = convertedRecipes.get(0);
} else if (aggregateRecipe.getRecipeList().size() == 1) {
Recipe r = aggregateRecipe.getRecipeList().get(0);
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? r.getName() : params.getWorkDoneToken().getLeft();
return apply(r, uri, progressToken);
} else {
Recipe multiRecipe = new Recipe() {
@Override
public String getDisplayName() {
return convertedRecipes.size() + " recipes";
}
};
for (Recipe r : convertedRecipes) {
multiRecipe.doNext(r);
}
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? multiRecipe.getName() : params.getWorkDoneToken().getLeft();
return apply(multiRecipe, uri, progressToken);
String progressToken = params.getWorkDoneToken() == null || params.getWorkDoneToken().getLeft() == null ? aggregateRecipe.getName() : params.getWorkDoneToken().getLeft();
return apply(aggregateRecipe, uri, progressToken);
}
});
listBuilder.add("sts/rewrite/execute");
@@ -248,13 +250,13 @@ public class RewriteRecipeRepository {
return ORDocUtils.createWorkspaceEdit(absoluteProjectDir, server.getTextDocumentService(), results);
}
private List<RecipeDescriptor> listProjectRefactoringCommands(String uri) {
private List<Recipe> listProjectRefactoringRecipes(String uri) {
if (uri != null) {
Optional<IJavaProject> projectOpt = projectFinder.find(new TextDocumentIdentifier(uri));
if (projectOpt.isPresent()) {
List<RecipeDescriptor> commandDescriptors = new ArrayList<>(globalCommandRecipes.size());
List<Recipe> commandDescriptors = new ArrayList<>(globalCommandRecipes.size());
for (Recipe r : globalCommandRecipes) {
commandDescriptors.add(new RecipeDescriptor(r));
commandDescriptors.add(r);
}
return commandDescriptors;
}
@@ -287,47 +289,47 @@ public class RewriteRecipeRepository {
}
}
private static Recipe convert(Recipe r, RecipeDescriptor d) {
try {
if (d.selected) {
if (d.children != null && !d.children.isEmpty()) {
Recipe recipe = r instanceof DeclarativeRecipe ? new DeclarativeRecipe(r.getName(), r.getDisplayName(), r.getDescription(), r.getTags(), r.getEstimatedEffortPerOccurrence(), null)
: r.getClass().getDeclaredConstructor().newInstance();
int i = 0;
for (Recipe sr : r.getRecipeList()) {
Recipe convertedSubRecipe = convert(sr, d.children.get(i++));
if (convertedSubRecipe != null) {
recipe.doNext(convertedSubRecipe);
}
}
return recipe;
} else {
return r;
}
}
} catch (Exception e) {
log.error("", e);
}
return null;
}
// private static Recipe convert(Recipe r, RecipeDescriptor d) {
// try {
// if (d.selected) {
// if (d.children != null && !d.children.isEmpty()) {
// Recipe recipe = r instanceof DeclarativeRecipe ? new DeclarativeRecipe(r.getName(), r.getDisplayName(), r.getDescription(), r.getTags(), r.getEstimatedEffortPerOccurrence(), null)
// : r.getClass().getDeclaredConstructor().newInstance();
// int i = 0;
// for (Recipe sr : r.getRecipeList()) {
// Recipe convertedSubRecipe = convert(sr, d.children.get(i++));
// if (convertedSubRecipe != null) {
// recipe.doNext(convertedSubRecipe);
// }
// }
// return recipe;
// } else {
// return r;
// }
// }
// } catch (Exception e) {
// log.error("", e);
// }
// return null;
// }
@SuppressWarnings("unused")
private static class RecipeDescriptor {
String id;
String label;
String detail;
List<RecipeDescriptor> children;
boolean selected;
RecipeDescriptor(Recipe r) {
this.id = r.getName();
this.label = r.getDisplayName();
this.detail = r.getDescription();
List<Recipe> subRecipes = r.getRecipeList();
if (r instanceof DeclarativeRecipe && !subRecipes.isEmpty() && (subRecipes.size() > 1 || subRecipes.get(0) instanceof DeclarativeRecipe)) {
this.children = r.getRecipeList().stream().map(sr -> new RecipeDescriptor(sr)).collect(Collectors.toList());
}
}
}
// @SuppressWarnings("unused")
// private static class RecipeDescriptor {
// String id;
// String label;
// String detail;
// List<RecipeDescriptor> children;
// boolean selected;
//
// RecipeDescriptor(Recipe r) {
// this.id = r.getName();
// this.label = r.getDisplayName();
// this.detail = r.getDescription();
// List<Recipe> subRecipes = r.getRecipeList();
// if (r instanceof DeclarativeRecipe && !subRecipes.isEmpty() && (subRecipes.size() > 1 || subRecipes.get(0) instanceof DeclarativeRecipe)) {
// this.children = r.getRecipeList().stream().map(sr -> new RecipeDescriptor(sr)).collect(Collectors.toList());
// }
// }
// }
}

View File

@@ -3,18 +3,34 @@ import { LanguageClient } from "vscode-languageclient/node";
import * as VSCode from 'vscode';
import * as path from "path";
interface RewriteCommandInfo {
id: string;
label: string;
detail?: string;
children: RewriteCommandInfo[];
selected: boolean;
interface RecipeDescriptor {
name: string;
displayName: string;
description: string;
tags: string[];
estimatedEffortPerOccurrence: number;
options: OptionDescriptor[];
languages: string[];
recipeList: RecipeDescriptor[];
source: string;
}
interface OptionDescriptor {
name: string;
type: string;
displayName: string;
description: string;
example: string;
valid: string[] | undefined;
required: boolean;
value: any;
}
interface RecipeQuickPickItem extends VSCode.QuickPickItem{
id: string;
children: RecipeQuickPickItem[];
readonly id: string;
selected: boolean;
children: RecipeQuickPickItem[],
readonly recipeDescriptor: RecipeDescriptor;
}
function getWorkspaceFolderName(file: VSCode.Uri): string {
@@ -73,34 +89,47 @@ async function liveHoverConnectHandler(uri: VSCode.Uri) {
if (!uri) {
uri = await getTargetPomXml();
}
const cmds: RewriteCommandInfo[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true));
const cmds: RecipeDescriptor[] = await VSCode.commands.executeCommand('sts/rewrite/list', uri.toString(true));
const choices = cmds.map(convertToQuickPickItem);
await showCurrentPathQuickPick(choices, []);
const recipesInfo = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
if (recipesInfo.length) {
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), recipesInfo);
const recipeDescriptors = choices.filter(i => i.selected).map(convertToRecipeDescriptor);
if (recipeDescriptors.length) {
const aggregateRecipeDescriptor = recipeDescriptors.length === 1 ? recipeDescriptors[0] : {
name: `${recipeDescriptors.length} recipes`,
displayName: `${recipeDescriptors.length} recipes`,
description: recipeDescriptors.map(d => d.description).join('\n'),
tags: [...new Set<string>(recipeDescriptors.flatMap(d => d.tags))],
languages: [...new Set<string>(recipeDescriptors.flatMap(d => d.languages))],
options: [],
recipeList: recipeDescriptors,
estimatedEffortPerOccurrence: recipeDescriptors.filter(d => d.estimatedEffortPerOccurrence).map(d => d.estimatedEffortPerOccurrence).reduce((p, c) => p + c, 0)
};
if (aggregateRecipeDescriptor.estimatedEffortPerOccurrence === 0) {
delete aggregateRecipeDescriptor.estimatedEffortPerOccurrence;
}
VSCode.commands.executeCommand('sts/rewrite/execute', uri.toString(true), aggregateRecipeDescriptor);
} else {
VSCode.window.showErrorMessage('No Recipes were selected!');
}
}
function convertToRecipeDescriptor(i: RecipeQuickPickItem): RewriteCommandInfo {
function convertToRecipeDescriptor(i: RecipeQuickPickItem): RecipeDescriptor {
return {
id: i.id,
label: i.label,
selected: i.selected,
children: i.children.map(convertToRecipeDescriptor)
...i.recipeDescriptor,
recipeList: i.children.filter(c => c.selected).map(convertToRecipeDescriptor)
};
}
function convertToQuickPickItem(i: RewriteCommandInfo): RecipeQuickPickItem {
function convertToQuickPickItem(i: RecipeDescriptor): RecipeQuickPickItem {
return {
id: i.id,
label: i.label,
detail: i.detail,
selected: i.selected,
children: i.children ? i.children.map(convertToQuickPickItem) : [],
buttons: i.children && i.children.length ? [ SUB_RECIPES_BUTTON ] : undefined,
id: i.name,
label: i.displayName,
detail: i.options.filter(o => !!o.value).map(o => `${o.name}: ${JSON.stringify(o.value)}`).join('\n\n'),
description: i.description,
selected: false,
children: i.recipeList ? i.recipeList.map(convertToQuickPickItem) : [],
buttons: i.recipeList && i.recipeList.length ? [ SUB_RECIPES_BUTTON ] : undefined,
recipeDescriptor: i
};
}
@@ -112,7 +141,7 @@ function showCurrentPathQuickPick(items: RecipeQuickPickItem[], itemsPath: Recip
parent = currentItems.find(i => i === p);
currentItems = parent.children;
});
const quickPick = VSCode.window.createQuickPick<RewriteCommandInfo & VSCode.QuickPickItem>();
const quickPick = VSCode.window.createQuickPick<RecipeQuickPickItem>();
quickPick.items = currentItems;
quickPick.title = 'Select Recipes';
quickPick.canSelectMany = true;