diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java new file mode 100644 index 00000000000..6ab0314d3a8 --- /dev/null +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeRepository.java @@ -0,0 +1,329 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.gradle; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.gradle.internal.ChangeStringLiteral; +import org.openrewrite.groovy.tree.G; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.RandomizeIdVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.Statement; +import org.openrewrite.kotlin.tree.K; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; + +@Value +@EqualsAndHashCode(callSuper = false) +public class ChangeRepository extends Recipe { + + String displayName = "Change repository"; + + String description = "Replace a repository in Gradle build scripts to standardize repository usage across an organization."; + + @Option(displayName = "Old type", + description = "The type of the artifact repository to replace. " + + "Named types include \"jcenter\", \"mavenCentral\", \"mavenLocal\", \"google\", and \"gradlePluginPortal\". " + + "If not specified, matches any repository type with the given URL.", + required = false, + example = "jcenter") + @Nullable + String oldType; + + @Option(displayName = "Old URL", + description = "The URL of the artifact repository to replace. If not specified, matches any repository of the given type.", + required = false, + example = "https://old-nexus.example.com/releases") + @Nullable + String oldUrl; + + @Option(displayName = "New type", + description = "The type of the new artifact repository. " + + "If not specified, the matched repository's type will be preserved.", + required = false, + example = "mavenCentral") + @Nullable + String newType; + + @Option(displayName = "New URL", + description = "The URL of the new artifact repository. Required when the new type is not a named repository.", + required = false, + example = "https://new-nexus.example.com/releases") + @Nullable + String newUrl; + + @Override + public Validated validate() { + return super.validate() + .and(Validated.required("newType", newType).or(Validated.required("newUrl", newUrl))) + .and(Validated.test( + "repository", + "Old and new repository must be different", + this, + r -> !(Objects.equals(r.oldType, r.newType) && Objects.equals(r.oldUrl, r.newUrl)) + )); + } + + @Override + public TreeVisitor getVisitor() { + MethodMatcher repoMatcher = new MethodMatcher("org.gradle.api.artifacts.dsl.RepositoryHandler " + (oldType != null ? oldType : "*") + "(..)", true); + + return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation m = super.visitMethodInvocation(method, ctx); + + if (!repoMatcher.matches(m, true)) { + return m; + } + + // Ensure we're inside a repositories {} block + if (!isInsideRepositoriesBlock()) { + return m; + } + + // If oldUrl is specified, verify the URL matches + if (oldUrl != null && !urlMatches(m)) { + return m; + } + + // If newType is not specified, keep the matched repository's type + String effectiveNewType = newType != null ? newType : m.getSimpleName(); + + boolean isKotlinDsl = getCursor().firstEnclosingOrThrow(JavaSourceFile.class) instanceof K.CompilationUnit; + + // If the target repository already exists as a sibling, remove the old one instead of replacing + if (newRepoAlreadyExists(m, effectiveNewType)) { + //noinspection DataFlowIssue + return null; + } + + // Named → Named (no URLs involved) + if (oldUrl == null && newUrl == null) { + if (m.getSimpleName().equals(effectiveNewType)) { + return m; + } + return m.withName(m.getName().withSimpleName(effectiveNewType)); + } + + // Custom → Custom with same type: just change the URL + if (m.getSimpleName().equals(effectiveNewType) && newUrl != null) { + if (oldUrl != null && oldUrl.equals(newUrl)) { + return m; + } + return replaceUrl(m, newUrl, isKotlinDsl); + } + + // All other cases: generate a new repository node and swap it in + J.MethodInvocation replacement = generateRepositoryInvocation(effectiveNewType, isKotlinDsl, ctx); + return (J.MethodInvocation) autoFormat(replacement.withPrefix(m.getPrefix()), ctx, getCursor().getParentOrThrow()); + } + + private boolean isInsideRepositoriesBlock() { + try { + getCursor().dropParentUntil(e -> + e instanceof J.MethodInvocation && + "repositories".equals(((J.MethodInvocation) e).getSimpleName())); + return true; + } catch (Exception e) { + return false; + } + } + + private boolean newRepoAlreadyExists(J.MethodInvocation current, String effectiveNewType) { + try { + Cursor reposCursor = getCursor().dropParentUntil(e -> + e instanceof J.MethodInvocation && + "repositories".equals(((J.MethodInvocation) e).getSimpleName())); + J.MethodInvocation repos = reposCursor.getValue(); + if (repos.getArguments().isEmpty() || !(repos.getArguments().get(0) instanceof J.Lambda)) { + return false; + } + J.Lambda lambda = (J.Lambda) repos.getArguments().get(0); + if (!(lambda.getBody() instanceof J.Block)) { + return false; + } + J.Block block = (J.Block) lambda.getBody(); + for (Statement sibling : block.getStatements()) { + Statement s = sibling instanceof J.Return ? (Statement) ((J.Return) sibling).getExpression() : sibling; + if (!(s instanceof J.MethodInvocation)) { + continue; + } + J.MethodInvocation siblingInvocation = (J.MethodInvocation) s; + // Skip the node we're currently visiting + if (siblingInvocation.getId().equals(current.getId())) { + continue; + } + if (!effectiveNewType.equals(siblingInvocation.getSimpleName())) { + continue; + } + if (newUrl == null) { + // Named repo match (e.g. mavenCentral()) + return true; + } + // Custom repo: check URL matches + String siblingUrl = extractUrlFromInvocation(siblingInvocation); + if (newUrl.equals(siblingUrl)) { + return true; + } + } + } catch (Exception ignored) { + } + return false; + } + + private @Nullable String extractUrlFromInvocation(J.MethodInvocation m) { + if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) { + return null; + } + J.Lambda lambda = (J.Lambda) m.getArguments().get(0); + if (!(lambda.getBody() instanceof J.Block)) { + return null; + } + J.Block block = (J.Block) lambda.getBody(); + for (Statement statement : block.getStatements()) { + Statement s = statement instanceof J.Return ? (Statement) ((J.Return) statement).getExpression() : statement; + if (s != null) { + String url = extractUrl(s); + if (url != null) { + return url; + } + } + } + return null; + } + + private boolean urlMatches(J.MethodInvocation m) { + if (m.getArguments().isEmpty() || !(m.getArguments().get(0) instanceof J.Lambda)) { + return false; + } + J.Lambda lambda = (J.Lambda) m.getArguments().get(0); + if (!(lambda.getBody() instanceof J.Block)) { + return false; + } + J.Block block = (J.Block) lambda.getBody(); + for (Statement statement : block.getStatements()) { + Statement s = statement instanceof J.Return ? (Statement) ((J.Return) statement).getExpression() : statement; + if (s == null) { + continue; + } + String extractedUrl = extractUrl(s); + if (oldUrl.equals(extractedUrl)) { + return true; + } + } + return false; + } + + private @Nullable String extractUrl(Statement s) { + // Handle: url = "..." or url = uri("...") + if (s instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) s; + if (assignment.getVariable() instanceof J.Identifier && + "url".equals(((J.Identifier) assignment.getVariable()).getSimpleName())) { + return extractUrlFromExpression(assignment.getAssignment()); + } + } + // Handle: setUrl("...") or url("...") or setUrl(uri("...")) + if (s instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) s; + if ("setUrl".equals(mi.getSimpleName()) || "url".equals(mi.getSimpleName())) { + return extractUrlFromExpression(mi.getArguments().get(0)); + } + } + return null; + } + + private @Nullable String extractUrlFromExpression(J expr) { + if (expr instanceof J.Literal) { + return (String) ((J.Literal) expr).getValue(); + } + if (expr instanceof J.MethodInvocation && "uri".equals(((J.MethodInvocation) expr).getSimpleName())) { + J.MethodInvocation uri = (J.MethodInvocation) expr; + if (!uri.getArguments().isEmpty() && uri.getArguments().get(0) instanceof J.Literal) { + return (String) ((J.Literal) uri.getArguments().get(0)).getValue(); + } + } + return null; + } + + private J.MethodInvocation replaceUrl(J.MethodInvocation m, String newUrl, boolean isKotlinDsl) { + return (J.MethodInvocation) new JavaIsoVisitor() { + @Override + public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) { + if (literal.getValue() instanceof String && literal.getValue().equals(oldUrl)) { + return ChangeStringLiteral.withStringValue(literal, newUrl); + } + return literal; + } + }.visitNonNull(m, new InMemoryExecutionContext()); + } + + private J.MethodInvocation generateRepositoryInvocation(String effectiveNewType, boolean isKotlinDsl, ExecutionContext ctx) { + String code; + if (newUrl == null) { + code = effectiveNewType + "()"; + } else if (isKotlinDsl) { + code = effectiveNewType + " {\n url = uri(\"" + newUrl + "\")\n}"; + } else { + code = effectiveNewType + " {\n url = \"" + newUrl + "\"\n}"; + } + + String template = "repositories {\n " + code + "\n}"; + Path path = Paths.get(isKotlinDsl ? "build.gradle.kts" : "build.gradle"); + + J.MethodInvocation reposBlock; + if (isKotlinDsl) { + K.CompilationUnit cu = GradleParser.builder().build() + .parseInputs(singletonList(Parser.Input.fromString(path, template)), null, ctx) + .map(K.CompilationUnit.class::cast) + .collect(toList()).get(0); + J.Block block = (J.Block) cu.getStatements().get(0); + reposBlock = (J.MethodInvocation) block.getStatements().get(0); + } else { + G.CompilationUnit cu = GradleParser.builder().build() + .parseInputs(singletonList(Parser.Input.fromString(path, template)), null, ctx) + .map(G.CompilationUnit.class::cast) + .collect(toList()).get(0); + reposBlock = (J.MethodInvocation) cu.getStatements().get(0); + } + + // Extract the repository from inside the repositories {} block + J.Lambda lambda = (J.Lambda) reposBlock.getArguments().get(0); + J.Block body = (J.Block) lambda.getBody(); + Statement stmt = body.getStatements().get(0); + J.MethodInvocation repo; + if (stmt instanceof J.Return) { + repo = (J.MethodInvocation) ((J.Return) stmt).getExpression(); + } else { + repo = (J.MethodInvocation) stmt; + } + + return (J.MethodInvocation) new RandomizeIdVisitor().visit(repo, 0); + } + }); + } +} diff --git a/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv b/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv index 9008b3883f6..801524c7188 100644 --- a/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv +++ b/rewrite-gradle/src/main/resources/META-INF/rewrite/recipes.csv @@ -7,6 +7,7 @@ maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeDependencyGrou maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeExtraProperty,Change Extra Property,Gradle's [ExtraPropertiesExtension](https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html) is a commonly used mechanism for setting arbitrary key/value pairs on a project. This recipe will change the value of a property with the given key name if that key can be found. It assumes that the value being set is a String literal. Does not add the value if it does not already exist.,1,,Gradle,"[{""name"":""key"",""type"":""String"",""displayName"":""Key"",""description"":""The key of the property to change."",""example"":""foo"",""required"":true},{""name"":""value"",""type"":""String"",""displayName"":""Value"",""description"":""The new value to set. The value will be treated the contents of a string literal."",""example"":""bar"",""required"":true}]", maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeManagedDependency,Change Gradle managed dependency,"Change a Gradle managed dependency coordinates. The `newGroupId` or `newArtifactId` **MUST** be different from before. For now, only Spring Dependency Management Plugin entries are supported and no other forms of managed dependencies (yet).",1,,Gradle,"[{""name"":""oldGroupId"",""type"":""String"",""displayName"":""Old groupId"",""description"":""The old groupId to replace. The groupId is the first part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""org.openrewrite.recipe"",""required"":true},{""name"":""oldArtifactId"",""type"":""String"",""displayName"":""Old artifactId"",""description"":""The old artifactId to replace. The artifactId is the second part of a dependency coordinate 'com.google.guava:guava:VERSION'. Supports glob expressions."",""example"":""rewrite-testing-frameworks"",""required"":true},{""name"":""newGroupId"",""type"":""String"",""displayName"":""New groupId"",""description"":""The new groupId to use. Defaults to the existing group id."",""example"":""corp.internal.openrewrite.recipe""},{""name"":""newArtifactId"",""type"":""String"",""displayName"":""New artifactId"",""description"":""The new artifactId to use. Defaults to the existing artifact id."",""example"":""rewrite-testing-frameworks""},{""name"":""newVersion"",""type"":""String"",""displayName"":""New version"",""description"":""An exact version number or node-style semver selector used to select the version number. You can also use `latest.release` for the latest available version and `latest.patch` if the current version is a valid semantic version. For more details, you can look at the documentation page of [version selectors](https://docs.openrewrite.org/reference/dependency-version-selectors)."",""example"":""29.X""},{""name"":""versionPattern"",""type"":""String"",""displayName"":""Version pattern"",""description"":""Allows version selection to be extended beyond the original Node Semver semantics. So for example,Setting 'version' to \""25-29\"" can be paired with a metadata pattern of \""-jre\"" to select Guava 29.0-jre"",""example"":""-jre""}]","[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeRepository,Change repository,Replace a repository in Gradle build scripts to standardize repository usage across an organization.,1,,Gradle,"[{""name"":""oldType"",""type"":""String"",""displayName"":""Old type"",""description"":""The type of the artifact repository to replace. Named types include \""jcenter\"", \""mavenCentral\"", \""mavenLocal\"", \""google\"", and \""gradlePluginPortal\"". If not specified, matches any repository type with the given URL."",""example"":""jcenter""},{""name"":""oldUrl"",""type"":""String"",""displayName"":""Old URL"",""description"":""The URL of the artifact repository to replace. If not specified, matches any repository of the given type."",""example"":""https://old-nexus.example.com/releases""},{""name"":""newType"",""type"":""String"",""displayName"":""New type"",""description"":""The type of the new artifact repository. If not specified, the matched repository's type will be preserved."",""example"":""mavenCentral""},{""name"":""newUrl"",""type"":""String"",""displayName"":""New URL"",""description"":""The URL of the new artifact repository. Required when the new type is not a named repository."",""example"":""https://new-nexus.example.com/releases""}]", maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.ChangeTaskToTasksRegister,Change Gradle task eager creation to lazy registration,"Changes eager task creation `task exampleName(type: ExampleType)` to lazy registration `tasks.register(""exampleName"", ExampleType)`. Also supports Kotlin DSL: `task(""exampleName"")` to `tasks.register(""exampleName"")`.",1,,Gradle,, maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.DependencyConstraintToRule,Dependency constraint to resolution rule,"Gradle [dependency constraints](https://docs.gradle.org/current/userguide/dependency_constraints.html#dependency-constraints) are useful for managing the versions of transitive dependencies. Some plugins, such as the Spring Dependency Management plugin, do not respect these constraints. This recipe converts constraints into [resolution rules](https://docs.gradle.org/current/userguide/resolution_rules.html), which can achieve similar effects to constraints but are harder for plugins to ignore.",1,,Gradle,, maven,org.openrewrite:rewrite-gradle,org.openrewrite.gradle.DependencyUseMapNotation,Use `Map` notation for Gradle dependency declarations,"In Gradle, dependencies can be expressed as a `String` like `""groupId:artifactId:version""`, or equivalently as a `Map` like `group: 'groupId', name: 'artifactId', version: 'version'` (groovy) or `group = ""groupId"", name = ""artifactId"", version = ""version""` (kotlin). This recipe replaces dependencies represented as `Strings` with an equivalent dependency represented as a `Map`.",1,,Gradle,, diff --git a/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java new file mode 100644 index 00000000000..e61140ee7b7 --- /dev/null +++ b/rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeRepositoryTest.java @@ -0,0 +1,565 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.gradle; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.gradle.Assertions.buildGradle; +import static org.openrewrite.gradle.Assertions.buildGradleKts; + +class ChangeRepositoryTest implements RewriteTest { + + @DocumentExample + @Test + void namedToNamed() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradle( + """ + repositories { + jcenter() + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void namedToNamedKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradleKts( + """ + repositories { + jcenter() + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void namedToCustomMaven() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "maven", "https://nexus.example.com/releases")), + buildGradle( + """ + repositories { + jcenter() + } + """, + """ + repositories { + maven { + url = "https://nexus.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void namedToCustomMavenKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "maven", "https://nexus.example.com/releases")), + buildGradleKts( + """ + repositories { + jcenter() + } + """, + """ + repositories { + maven { + url = uri("https://nexus.example.com/releases") + } + } + """ + ) + ); + } + + @Test + void customMavenToNamed() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://jcenter.bintray.com", "mavenCentral", null)), + buildGradle( + """ + repositories { + maven { + url = "https://jcenter.bintray.com" + } + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void customMavenToNamedKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://jcenter.bintray.com", "mavenCentral", null)), + buildGradleKts( + """ + repositories { + maven { + url = uri("https://jcenter.bintray.com") + } + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void changeUrl() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void changeUrlKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradleKts( + """ + repositories { + maven { + url = uri("https://old-nexus.example.com/releases") + } + } + """, + """ + repositories { + maven { + url = uri("https://new-nexus.example.com/releases") + } + } + """ + ) + ); + } + + @Test + void noChangeWhenAlreadyCorrect() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradle( + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void onlyMatchesSpecifiedUrl() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://other-repo.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void preservesOtherRepositories() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradle( + """ + repositories { + jcenter() + mavenLocal() + } + """, + """ + repositories { + mavenCentral() + mavenLocal() + } + """ + ) + ); + } + + @Test + void changeUrlUsingSetUrlMethod() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + setUrl("https://old-nexus.example.com/releases") + } + } + """, + """ + repositories { + maven { + setUrl("https://new-nexus.example.com/releases") + } + } + """ + ) + ); + } + + @Test + void doesNotMatchOutsideRepositoriesBlock() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradle( + """ + repositories { + mavenLocal() + } + """ + ) + ); + } + + @Test + void matchByUrlOnly() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void newUrlOnlyKeepsType() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", null, "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void newUrlOnlyKeepsTypeKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", null, "https://new-nexus.example.com/releases")), + buildGradleKts( + """ + repositories { + maven { + url = uri("https://old-nexus.example.com/releases") + } + } + """, + """ + repositories { + maven { + url = uri("https://new-nexus.example.com/releases") + } + } + """ + ) + ); + } + + @Test + void matchByUrlOnlyReplace() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository(null, "https://old-nexus.example.com/releases", "mavenCentral", null)), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + } + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void preservesCredentialsWhenChangingUrl() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + credentials { + username = findProperty("mavenUsername") + password = findProperty("mavenPassword") + } + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + credentials { + username = findProperty("mavenUsername") + password = findProperty("mavenPassword") + } + } + } + """ + ) + ); + } + + @Test + void preservesCredentialsWhenChangingUrlKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradleKts( + """ + repositories { + maven { + url = uri("https://old-nexus.example.com/releases") + credentials { + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") + } + } + } + """, + """ + repositories { + maven { + url = uri("https://new-nexus.example.com/releases") + credentials { + username = System.getenv("MAVEN_USERNAME") + password = System.getenv("MAVEN_PASSWORD") + } + } + } + """ + ) + ); + } + + @Test + void preservesAuthenticationBlockWhenChangingUrl() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + credentials { + username = findProperty("mavenUsername") + password = findProperty("mavenPassword") + } + authentication { + basic(BasicAuthentication) + } + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + credentials { + username = findProperty("mavenUsername") + password = findProperty("mavenPassword") + } + authentication { + basic(BasicAuthentication) + } + } + } + """ + ) + ); + } + + @Test + void preservesNameAndOtherPropertiesWhenChangingUrl() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + name = "myRepo" + url = "https://old-nexus.example.com/releases" + allowInsecureProtocol = true + } + } + """, + """ + repositories { + maven { + name = "myRepo" + url = "https://new-nexus.example.com/releases" + allowInsecureProtocol = true + } + } + """ + ) + ); + } + + @Test + void removesOldWhenTargetNamedRepoAlreadyExists() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradle( + """ + repositories { + jcenter() + mavenCentral() + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } + + @Test + void removesOldWhenTargetCustomRepoAlreadyExists() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("maven", "https://old-nexus.example.com/releases", "maven", "https://new-nexus.example.com/releases")), + buildGradle( + """ + repositories { + maven { + url = "https://old-nexus.example.com/releases" + } + maven { + url = "https://new-nexus.example.com/releases" + } + } + """, + """ + repositories { + maven { + url = "https://new-nexus.example.com/releases" + } + } + """ + ) + ); + } + + @Test + void removesOldWhenTargetNamedRepoAlreadyExistsKts() { + rewriteRun( + spec -> spec.recipe(new ChangeRepository("jcenter", null, "mavenCentral", null)), + buildGradleKts( + """ + repositories { + jcenter() + mavenCentral() + } + """, + """ + repositories { + mavenCentral() + } + """ + ) + ); + } +}