diff --git a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java index 5e593c2e0b6..0f9554efc91 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/Environment.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/Environment.java @@ -15,6 +15,7 @@ */ package org.openrewrite.config; +import org.apache.commons.lang3.StringUtils; import org.openrewrite.Contributor; import org.openrewrite.Recipe; import org.openrewrite.RecipeException; @@ -28,7 +29,10 @@ import java.util.*; import static java.util.Collections.emptyList; +import static java.util.Comparator.comparingInt; +import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; public class Environment { private final Collection resourceLoaders; @@ -134,24 +138,27 @@ public Collection listRecipeDescriptors() { } public Recipe activateRecipes(Iterable activeRecipes) { - List allRecipes = listRecipes(); + Map recipesByName = listRecipes().stream().collect(toMap(Recipe::getName, identity())); List recipesNotFound = new ArrayList<>(); List activatedRecipes = new ArrayList<>(); for (String activeRecipe : activeRecipes) { - boolean foundRecipe = false; - for (Recipe recipe : allRecipes) { - if (activeRecipe.equals(recipe.getName())) { - activatedRecipes.add(recipe); - foundRecipe = true; - break; - } - } - if (!foundRecipe) { + Recipe recipe = recipesByName.get(activeRecipe); + if (recipe == null) { recipesNotFound.add(activeRecipe); + } else { + activatedRecipes.add(recipe); } } if (!recipesNotFound.isEmpty()) { - throw new RecipeException("Recipes not found: " + String.join(", ", recipesNotFound)); + List suggestions = recipesNotFound.stream() + .map(r -> recipesByName.keySet().stream() + .min(comparingInt(a -> StringUtils.getLevenshteinDistance(a, r))) + .orElse(r)) + .collect(toList()); + String message = String.format("Recipes not found: %s\nDid you mean: %s", + String.join(", ", recipesNotFound), + String.join(", ", suggestions)); + throw new RecipeException(message); } return new CompositeRecipe(activatedRecipes); } diff --git a/rewrite-test/src/test/java/org/openrewrite/config/EnvironmentTest.java b/rewrite-test/src/test/java/org/openrewrite/config/EnvironmentTest.java index 09ad982d476..cfebb015b1c 100644 --- a/rewrite-test/src/test/java/org/openrewrite/config/EnvironmentTest.java +++ b/rewrite-test/src/test/java/org/openrewrite/config/EnvironmentTest.java @@ -30,6 +30,7 @@ import java.util.Properties; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.openrewrite.test.SourceSpecs.text; class EnvironmentTest implements RewriteTest { @@ -100,6 +101,41 @@ void listRecipes() { assertThat(changes).hasSize(1); } + @Test + void activeRecipeNotFoundSuggestions() { + var env = Environment.builder() + .load( + new YamlResourceLoader( + //language=yml + new ByteArrayInputStream( + """ + type: specs.openrewrite.org/v1beta/recipe + name: test.ChangeTextToHello + displayName: Change text to hello + recipeList: + - org.openrewrite.text.ChangeText: + toText: Hello + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.ChangeTextToHelloWorld + displayName: Change text to hello world + recipeList: + - org.openrewrite.text.ChangeText: + toText: Hello + """.getBytes() + ), + URI.create("rewrite.yml"), + new Properties() + ) + ) + .build(); + + assertThatExceptionOfType(RecipeException.class) + .isThrownBy(() -> env.activateRecipes("foo.ChangeTextToHelloWorld")) + .withMessageContaining("foo.ChangeTextToHelloWorld") + .withMessageContaining("test.ChangeTextToHelloWorld"); + } + @Test void recipeWithoutRequiredConfiguration() { var env = Environment.builder()