Convert ChangeDependency to ScanningRecipe to preserve GString/StringTemplate versions#6858
Merged
Jenson3210 merged 15 commits intomainfrom Mar 5, 2026
Merged
Conversation
…pendency When a dependency version comes from a gradle.properties variable, ChangeDependency should preserve the GString/StringTemplate structure and update the property value instead of collapsing to a literal.
… preservation
When a dependency version comes from a gradle.properties variable (e.g.,
"group:artifact:${springBootVersion}"), ChangeDependency now preserves the
interpolated string structure instead of collapsing it to a literal with a
pinned version. The version property is updated in gradle.properties.
This uses the ScanningRecipe pattern (consistent with UpgradeDependencyVersion
and UpgradeTransitiveDependencyVersion) to:
1. Scan phase: Collect property keys from gradle.properties and identify
version variable names used in GString/StringTemplate dependencies
2. Edit phase: Update group:artifact prefix in the interpolated string while
preserving the variable reference, and update the property value in
gradle.properties
For local def variables (not backed by gradle.properties), the existing
behavior of collapsing to a string literal is preserved.
Fixes moderneinc/customer-requests#1920
…le.properties Always preserve interpolated dependency strings regardless of where the version variable is defined. Local def/val variable declarations are now updated in-place alongside gradle.properties entries, removing the previous distinction that only handled gradle.properties-backed variables.
Jenson3210
commented
Mar 3, 2026
rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java
Outdated
Show resolved
Hide resolved
Follow the same pattern as UpgradeDependencyVersion: store the exception in the accumulator during the scanning phase, then check for it in the visitor phase and apply Markup.warn() to the affected tree nodes (variable declarations and gradle.properties entries).
Match the UpgradeDependencyVersion pattern: use a JavaVisitor with isAcceptable to filter to .gradle/.gradle.kts files instead of a raw TreeVisitor wrapping a separate JavaIsoVisitor. This removes the manual instanceof checks and nested visitor, making the scanner a direct participant in the visit tree.
When a version variable (e.g. springVersion) is used by multiple dependencies but only some match the old group:artifact, updating the variable would break unrelated dependencies. The scanner now tracks ALL dependencies using each version variable. In the visitor, if any usage doesn't match oldGroupId:oldArtifactId, the GString/StringTemplate is collapsed to a plain literal with the resolved version instead of preserving the interpolation.
Jenson3210
commented
Mar 4, 2026
rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java
Outdated
Show resolved
Hide resolved
Replace manual notation-specific code in scanner and visitor with trait methods: getVersionVariable(), withDeclaredGroupId(), withDeclaredArtifactId(), withDeclaredVersion(), getDeclaredVersion(), getConfigurationName(). Fix trait bugs: K.StringTemplate withDeclaredGroupId/withDeclaredArtifactId now preserve template structure instead of collapsing to literal, and GString withDeclaredVersion preserves command expression formatting.
Jenson3210
commented
Mar 4, 2026
rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java
Show resolved
Hide resolved
Jenson3210
commented
Mar 4, 2026
rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java
Outdated
Show resolved
Hide resolved
- Revert GString withDeclaredVersion to convert to J.Literal (original intent), update test expectations for parenthesized output - Remove redundant version resolution in visitor — scanner already resolves and records the version
…sion Converting GString to J.Literal causes the Groovy printer to add parentheses around the argument, breaking command expression formatting. Collapsing to a single-element GString preserves the original formatting.
Test that withDeclaredGroupId and withDeclaredArtifactId preserve GString and K.StringTemplate structure (version interpolation kept), and that withDeclaredVersion correctly collapses interpolated strings to concrete values.
shanman190
approved these changes
Mar 5, 2026
rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java
Show resolved
Hide resolved
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ChangeDependencyfromRecipetoScanningRecipeto reliably handle version variables in GString and Kotlin StringTemplate dependency declarationsChangeDependencyby leveraging theGradleDependencytrait'swithDeclaredGroupId,withDeclaredArtifactId, andwithDeclaredVersionmethodsGradleDependencytrait wherewithDeclaredGroupId/withDeclaredArtifactIdonK.StringTemplatecollapsed the template to a literal (destroying version interpolation), andwithDeclaredVersiononG.GStringconverted toJ.Literal(causing the Groovy printer to add unwanted parentheses)Why ScanningRecipe?
When a dependency uses a version variable (e.g.,
implementation "group:artifact:${springBootVersion}"), we want to:${variable}intactgradle.propertiesor localdef/val)This requires two passes: a scan phase to discover which variables need updating and resolve the target version, then a visit phase to apply changes. A single-pass
Recipecannot reliably do this because it processes files independently without shared context.For dependencies without version variables (plain string literals, map notation, multi-component literals), the scanning phase simply collects no variable information, and the visitor applies changes directly — exactly as the old non-scanning
Recipedid. TheScanningRecipeconversion has no behavioral impact on these cases.This approach was recommended in PR #6830 review feedback as the reliable alternative to the
ConcurrentHashMap-based approach that depended on file processing order.What changed
ChangeDependency.javaRecipetoScanningRecipewith anAccumulatorthat maps version variable names to resolved versions (orMavenDownloadingException)GradleDependencytrait:withDeclaredGroupId(),withDeclaredArtifactId(),withDeclaredVersion(),getVersionVariable(),getDeclaredVersion(),isPlatform()GradleDependency.java(trait)withDeclaredGroupIdandwithDeclaredArtifactIdforK.StringTemplate: now preserves template structure (updates only the literal prefix) instead of collapsing to a plainJ.LiteralwithDeclaredVersionforG.GString: collapses to a single-elementGStringinstead ofJ.Literal, which preserves Groovy command expression formatting (implementation "..."stays without parentheses)Tests
ChangeDependencyTesttests for shared version variable detection and collapseGradleDependencyTesttests coveringwithDeclaredGroupId/withDeclaredArtifactId/withDeclaredVersiononGStringandK.StringTemplateTest plan
ChangeDependencyTesttests pass (23 existing + 3 new shared variable tests)GradleDependencyTesttrait tests passGradleDependencyTesttests passFixes moderneinc/customer-requests#1920