diff --git a/src/main/java/org/openrewrite/java/flyway/AlignFlywayModuleScopeWithFlywayCore.java b/src/main/java/org/openrewrite/java/flyway/AlignFlywayModuleScopeWithFlywayCore.java new file mode 100644 index 000000000..00ae042ba --- /dev/null +++ b/src/main/java/org/openrewrite/java/flyway/AlignFlywayModuleScopeWithFlywayCore.java @@ -0,0 +1,82 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.flyway; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.maven.ChangeDependencyScope; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.xml.tree.Xml; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AlignFlywayModuleScopeWithFlywayCore extends Recipe { + + @Option(displayName = "Flyway module artifactId", + description = "ArtifactId of the Flyway database module to align with `flyway-core`.", + example = "flyway-database-postgresql") + String artifactId; + + String displayName = "Align Flyway module scope with flyway-core"; + + String description = "Ensures Flyway database modules keep the same declared Maven scope as `flyway-core` " + + "when migrations add or touch those dependencies."; + + @Override + public TreeVisitor getVisitor() { + return new MavenIsoVisitor() { + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + Xml.Document d = super.visitDocument(document, ctx); + String flywayCoreScope = findDeclaredScope(d.getRoot(), "org.flywaydb", "flyway-core"); + if (!"test".equals(flywayCoreScope)) { + return d; + } + return (Xml.Document) new ChangeDependencyScope("org.flywaydb", artifactId, "test").getVisitor().visitNonNull(d, ctx); + } + + private @Nullable String findDeclaredScope(Xml.Tag tag, String groupId, String artifactId) { + if (isDependency(groupId, artifactId, tag)) { + return tag.getChildValue("scope").orElse(null); + } + java.util.List content = tag.getContent(); + if (content == null) { + return null; + } + for (Object child : content) { + if (child instanceof Xml.Tag) { + String scope = findDeclaredScope((Xml.Tag) child, groupId, artifactId); + if (scope != null || isDependency(groupId, artifactId, (Xml.Tag) child)) { + return scope; + } + } + } + return null; + } + + private boolean isDependency(String groupId, String artifactId, Xml.Tag tag) { + return "dependency".equals(tag.getName()) && + groupId.equals(tag.getChildValue("groupId").orElse(null)) && + artifactId.equals(tag.getChildValue("artifactId").orElse(null)); + } + }; + } +} diff --git a/src/main/resources/META-INF/rewrite/flyway-10.yml b/src/main/resources/META-INF/rewrite/flyway-10.yml index 7d64a7c80..ff574d1ef 100644 --- a/src/main/resources/META-INF/rewrite/flyway-10.yml +++ b/src/main/resources/META-INF/rewrite/flyway-10.yml @@ -24,9 +24,17 @@ preconditions: - org.openrewrite.Singleton recipeList: - org.openrewrite.java.flyway.AddFlywayModulePostgreSQL + - org.openrewrite.java.flyway.AlignFlywayModuleScopeWithFlywayCore: + artifactId: flyway-database-postgresql - org.openrewrite.java.flyway.AddFlywayModuleMySQL + - org.openrewrite.java.flyway.AlignFlywayModuleScopeWithFlywayCore: + artifactId: flyway-mysql - org.openrewrite.java.flyway.AddFlywayModuleOracle + - org.openrewrite.java.flyway.AlignFlywayModuleScopeWithFlywayCore: + artifactId: flyway-database-oracle - org.openrewrite.java.flyway.AddFlywayModuleSqlServer + - org.openrewrite.java.flyway.AlignFlywayModuleScopeWithFlywayCore: + artifactId: flyway-sqlserver --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.flyway.AddFlywayModulePostgreSQL diff --git a/src/test/java/org/openrewrite/java/flyway/MigrateToFlyway10Test.java b/src/test/java/org/openrewrite/java/flyway/MigrateToFlyway10Test.java index 845513cbb..66ea04b9f 100644 --- a/src/test/java/org/openrewrite/java/flyway/MigrateToFlyway10Test.java +++ b/src/test/java/org/openrewrite/java/flyway/MigrateToFlyway10Test.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -284,4 +285,185 @@ void addSqlServerDependency() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/1052") + @Test + void addPostgresDependencyWithTestScope() { + assertFlywayModuleAddedWithTestScope( + "org.postgresql", + "postgresql", + "flyway-database-postgresql" + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/1052") + @Test + void preserveExistingPostgresDependencyTestScope() { + rewriteRun( + //language=xml + pomXml( + pom( + "", + "\t4.0.0", + "\t", + "\t\torg.springframework.boot", + "\t\tspring-boot-starter-parent", + "\t\t3.3.12", + "\t\t", + "\t", + "\tcom.example", + "\tdemo", + "\t0.0.1-SNAPSHOT", + "\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-core", + "\t\t\ttest", + "\t\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-database-postgresql", + "\t\t\ttest", + "\t\t", + "\t\t", + "\t\t\torg.postgresql", + "\t\t\tpostgresql", + "\t\t\truntime", + "\t\t", + "\t", + "" + ), + pom( + "", + "\t4.0.0", + "\t", + "\t\torg.springframework.boot", + "\t\tspring-boot-starter-parent", + "\t\t3.3.12", + "\t\t", + "\t", + "\tcom.example", + "\tdemo", + "\t0.0.1-SNAPSHOT", + "\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-core", + "\t\t\ttest", + "\t\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-database-postgresql", + "\t\t\ttest", + "\t\t", + "\t\t", + "\t\t\torg.postgresql", + "\t\t\tpostgresql", + "\t\t\truntime", + "\t\t", + "\t", + "" + ) + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/1052") + @Test + void addMySQLDependencyWithTestScope() { + assertFlywayModuleAddedWithTestScope( + "com.mysql", + "mysql-connector-j", + "flyway-mysql" + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/1052") + @Test + void addOracleDependencyWithTestScope() { + assertFlywayModuleAddedWithTestScope( + "com.oracle.database.jdbc", + "ojdbc11", + "flyway-database-oracle" + ); + } + + @Issue("https://github.com/openrewrite/rewrite-spring/issues/1052") + @Test + void addSqlServerDependencyWithTestScope() { + assertFlywayModuleAddedWithTestScope( + "com.microsoft.sqlserver", + "mssql-jdbc", + "flyway-sqlserver" + ); + } + + private void assertFlywayModuleAddedWithTestScope(String databaseGroupId, String databaseArtifactId, String flywayModuleArtifactId) { + rewriteRun( + //language=xml + pomXml( + pom( + "", + "\t4.0.0", + "\t", + "\t\torg.springframework.boot", + "\t\tspring-boot-starter-parent", + "\t\t3.3.12", + "\t\t", + "\t", + "\tcom.example", + "\tdemo", + "\t0.0.1-SNAPSHOT", + "\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-core", + "\t\t\ttest", + "\t\t", + "\t\t", + ("\t\t\t%s").formatted(databaseGroupId), + ("\t\t\t%s").formatted(databaseArtifactId), + "\t\t\truntime", + "\t\t", + "\t", + "" + ), + pom( + "", + "\t4.0.0", + "\t", + "\t\torg.springframework.boot", + "\t\tspring-boot-starter-parent", + "\t\t3.3.12", + "\t\t", + "\t", + "\tcom.example", + "\tdemo", + "\t0.0.1-SNAPSHOT", + "\t", + "\t\t", + "\t\t\torg.flywaydb", + ("\t\t\t%s").formatted(flywayModuleArtifactId), + "\t\t\ttest", + "\t\t", + "\t\t", + "\t\t\torg.flywaydb", + "\t\t\tflyway-core", + "\t\t\ttest", + "\t\t", + "\t\t", + ("\t\t\t%s").formatted(databaseGroupId), + ("\t\t\t%s").formatted(databaseArtifactId), + "\t\t\truntime", + "\t\t", + "\t", + "" + ) + ) + ); + } + + private static String pom(String... lines) { + return String.join("\n", lines) + "\n"; + } }