Preserve GString/StringTemplate in ChangeDependency when version unchanged#6830
Preserve GString/StringTemplate in ChangeDependency when version unchanged#6830
Conversation
…rsion is unchanged
When `ChangeDependency` renames a dependency that uses a GString or Kotlin
StringTemplate (e.g. `"group:artifact:${version}"`), and the version is not
being changed, the GString/StringTemplate structure is now preserved instead
of being collapsed to a literal with a pinned version.
Previously, the GString and Kotlin StringTemplate code paths unconditionally
applied `newVersion` without checking the `overrideManagedVersion` guard that
the literal path already had. This caused recipes like the Spring Boot 4
migration to pin hardcoded versions on dependencies that used property
references for their version (e.g. `${springBootVersion}`).
The fix adds the same `overrideManagedVersion` guard to both the GString and
Kotlin StringTemplate paths, and when only group/artifact changes (not
version), updates only the literal prefix while preserving the interpolated
structure.
| def version = '2.6' | ||
| dependencies { | ||
| implementation platform("org.apache.commons:commons-lang3:3.11") | ||
| implementation platform("org.apache.commons:commons-lang3:${version}") |
There was a problem hiding this comment.
Now we're effectively not making any change, other than the markers; We should add gradle.properties files here and assert those are updated.
Move version variables from inline definitions to gradle.properties in worksWithGString and kotlinDslStringInterpolation tests, making them closer to real-world usage patterns. The properties file remains unchanged, confirming the GString/StringTemplate structure is preserved.
| spec -> spec.recipe(new ChangeDependency("commons-lang", "commons-lang", "org.apache.commons", "commons-lang3", "3.11.x", null, null, true)), | ||
| properties( | ||
| """ | ||
| commonsLangVersion=2.6 |
There was a problem hiding this comment.
This version should be changed to a specific version in the 3.11 range. Without it projects would break.
When ChangeDependency renames a GString dependency and resolves a new
version, the version property in gradle.properties is now updated to
match. This preserves the GString structure while ensuring the property
value is correct for the new artifact.
For example, renaming commons-lang:commons-lang to
org.apache.commons:commons-lang3 with newVersion 3.11.x now:
- Preserves "org.apache.commons:commons-lang3:${commonsLangVersion}"
- Updates gradle.properties: commonsLangVersion=3.11
The composite visitor handles both Gradle build scripts and properties
files in a single recipe cycle.
|
|
||
| @Value | ||
| @EqualsAndHashCode(callSuper = false) | ||
| public class ChangeDependency extends Recipe { |
There was a problem hiding this comment.
Will this "expectation" work without transitioning to a Scanning recipe?
Which will bring a whole new set of potential issues due to the timing of scan/edit.
There was a problem hiding this comment.
Honestly, I'm a bit fried looking at this PR; if you see a good way forward feel free to take over, or close this if there's a better alternative. With travel ahead I don't think I'll get to this one this week.
Summary
overrideManagedVersionguard to GString and Kotlin StringTemplate code paths inChangeDependency, matching the existing guard on the literal pathworksWithGString,kotlinDslStringInterpolation) to expect the new behaviorProblem
When
ChangeDependencyrenames a dependency that uses a GString or Kotlin StringTemplate (e.g."group:artifact:${version}"), it unconditionally collapsed the interpolated string to a literal and pinned the resolved version. This caused the Spring Boot 4 migration recipe to turn"org.springframework.boot:spring-boot-starter-web:${springBootVersion}"into"org.springframework.boot:spring-boot-starter-webmvc:4.0.3", destroying the property reference.The literal code path already had the correct guard: it only pins a new version when the original dependency had an explicit version or
overrideManagedVersion=true. The GString and Kotlin StringTemplate paths were missing this guard.Test plan
worksWithGStringtest updated to expect GString preservationkotlinDslStringInterpolationtest updated to expect StringTemplate preservationChangeDependencyTesttests pass