spring-projects / spring-rewrite-commons

Apache License 2.0
9 stars 11 forks source link

Parse Recipe results from OpenRewrite Maven plugin output #74

Open fabapp2 opened 7 months ago

fabapp2 commented 7 months ago

What needs to be done

The console output of OpenRewrite's Maven plugins should be parsed and made available.

The PluginInvoker implementation from #73 provides access to the output of the build.

PluginInvocationResult result = RewritePlugin.run()...
String output = result.capturedOutput();

The MAven plugin prints information about the recipes that were applied. This is an example for the output of OR's Maven plugin:

[INFO] Running recipe(s)...
[WARNING] Changes have been made to complete/pom.xml by:
[WARNING]     org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1
[WARNING]         org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING]             org.openrewrite.java.spring.boot2.UpgradeSpringBoot_2_7
[WARNING]                 org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=2.7.x}
[WARNING]             org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]                 org.openrewrite.java.migrate.Java8toJava11
[WARNING]                     org.openrewrite.java.migrate.JavaVersion11
[WARNING]                         org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING]                 org.openrewrite.java.migrate.JavaVersion17
[WARNING]                     org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]             org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.0.x, retainVersions=[org.thymeleaf:thymeleaf-spring5, org.thymeleaf.extras:thymeleaf-extras-springsecurity5]}
[WARNING]         org.openrewrite.maven.UpgradeParentVersion: {groupId=org.springframework.boot, artifactId=spring-boot-starter-parent, newVersion=3.1.x}
[WARNING] Changes have been made to complete/src/main/java/com/example/accessingdatarest/Person.java by:
[WARNING]     org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_1
[WARNING]         org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
[WARNING]             org.openrewrite.java.migrate.UpgradeToJava17
[WARNING]                 org.openrewrite.java.migrate.Java8toJava11
[WARNING]                     org.openrewrite.java.migrate.JavaVersion11
[WARNING]                         org.openrewrite.java.migrate.UpgradeJavaVersion: {version=11}
[WARNING]                 org.openrewrite.java.migrate.JavaVersion17
[WARNING]                     org.openrewrite.java.migrate.UpgradeJavaVersion: {version=17}
[WARNING]             org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta
[WARNING]                 org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence
[WARNING]                     org.openrewrite.java.ChangePackage: {oldPackageName=javax.persistence, newPackageName=jakarta.persistence, recursive=true}
[WARNING] Please review and commit the results.

A new component RewritePluginOutputParser should parse the output of OpenRewrite Maven plugin and provide an API to access the extracted information:

PluginInvocationResult result = RewritePlugin.run()...
String out = result.capturedOutput();
parser = new RewritePluginOutputParser();
RecipeRunDetails details = parser.parseOutput(out);

List<Path> affected = details.getAffectedFiles();
// AppliedRecipe can have itself a list of AppliedRecipe and parameters
List<AppliedRecipe> appliedRecipes = details.getAppliedRecipes(); 
// ...

What's next

The return types can be made specific to the executed plugin goals that provide an API to the build information extracted from the plugin output. E.g. PluginInvocationResult result = RewritePlugin.run()... would become

RewritePluginRunResult result = RewritePlugin.run()...
appliedRecipes = result.getRecipesApplied();
affectedFiles = result.getAffectedFiles();
bsmahi commented 7 months ago

@fabapp2 I will work on this, please assign it to me.

Thanks, Mahi

bsmahi commented 7 months ago

@fabapp2 I need to create RewritePluginOutputParser in separately in spring-rewrite-commons-plugin-invoker-maven and spring-rewrite-commons-plugin-invoker-gradle or spring-rewrite-commons-plugin-invoker-polyglot?

Could you please confirm?

Thanks, Mahi

fabapp2 commented 7 months ago

@fabapp2 I need to create RewritePluginOutputParser in separately in spring-rewrite-commons-plugin-invoker-maven and spring-rewrite-commons-plugin-invoker-gradle or spring-rewrite-commons-plugin-invoker-polyglot?

Could you please confirm?

Thanks, Mahi

Hi @bsmahi That's correct. I don't see the need for a PolyglotParser as it would use either RewriteGradlePluginOutputParser or RewriteMavenPluginOutputParser.

I didn't think much about the resulting data structure, but I it should allow to access the information provided in the concole output.

String consoleOutput = ...
RewriteMavenPluginOutputParser parser = ...
RewriteMavenPluginExecutionResult result = parser.parse(consoleOutput);

// was recipe run successful
boolean success = result.succeeded()
assertThat(success).isTrue();

// get changed files
List<ChangedResource> changedResources = result.getChangedResources();
assertThat(changedResources.get(0).getPath().toString()).isEqualTo("complete/pom.xml");
assertThat(changedResources.get(1).getPath().toString()).isEqualTo("complete/src/main/java/com/example/accessingdatarest/Person.java");

// get recipes that made changes to a file
assertThat(changedResources.get(0).getRecipesThatMadeChanges().stream().map(r -> r.getName()).toList()).containsExactly(
    "org.openrewrite.maven.UpgradeParentVersion".
    "org.openrewrite.java.migrate.UpgradeJavaVersion", // 11
    "org.openrewrite.java.migrate.UpgradeJavaVersion", // 17
    "org.openrewrite.maven.UpgradeParentVersion",  // 3.0.x
    "org.openrewrite.maven.UpgradeParentVersion". // 3.1.x
);

// details about recipe(s) that made changes:

List<AppliedRecipe> recipesThatMadeChanges = changedResources.get(1).getRecipesThatMadeChanges();
assertThat(recipesThatMadeChanges.get(1).getName()).isEqualTo("org.openrewrite.java.migrate.UpgradeJavaVersion");
assertThat(recipesThatMadeChanges.get(1).getParams()).hasSize(1);
assertThat(recipesThatMadeChanges.get(1).getParams()).contains("version")
assertThat(recipesThatMadeChanges.get(1).getParams().get("version")).isEqualTo(11);
assertThat(recipesThatMadeChanges.get(1).getParent().getName()).isEqualTo("org.openrewrite.java.migrate.JavaVersion11");

// get applied recipes
List<AppliedRecipe> appliedRecipes = result.getAppliedRecipes();
appliedRecipes.forEach(appliedRecipe -> {
    String recipeName = appliedRecipe.getName();
    List<ChangedResource> changedResources =  appliedRecipe.getChangedResources();
    changedResources.forEach(changedResource -> {
        assertThat(changedResource.getAppliedRecipes()).contains(appliedRecipe);
    });
});

Optional<AppliedRecipe> optUpgradeRecipe = result.getAppliedRecipe("org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0");

assertThat(optUpgradeRecipe).isPresent();
AppliedRecipe upgradeRecipe = optUpgradeRecipe.get();
assertThat(upgradeRecipe.getChangedResources()).contains("complete/pom.xml", ""complete/src/main/java/com/example/accessingdatarest/Person.java");

So, we need a structure that allows navigating in both directions (recipes -> resources and resources -> recipes) and provides the hierarchy plus properties of the recipes.

Thoughts?