Skip to content

Preserve GString/StringTemplate in ChangeDependency when version unchanged#6830

Draft
timtebeek wants to merge 3 commits intomainfrom
timtebeek/preserve-gstring-in-change-dependency
Draft

Preserve GString/StringTemplate in ChangeDependency when version unchanged#6830
timtebeek wants to merge 3 commits intomainfrom
timtebeek/preserve-gstring-in-change-dependency

Conversation

@timtebeek
Copy link
Member

@timtebeek timtebeek commented Feb 26, 2026

Summary

  • Add overrideManagedVersion guard to GString and Kotlin StringTemplate code paths in ChangeDependency, matching the existing guard on the literal path
  • When only group/artifact changes (version unchanged), preserve the interpolated string structure instead of collapsing to a literal with a pinned version
  • Update two existing tests (worksWithGString, kotlinDslStringInterpolation) to expect the new behavior

Problem

When ChangeDependency renames 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

  • Existing worksWithGString test updated to expect GString preservation
  • Existing kotlinDslStringInterpolation test updated to expect StringTemplate preservation
  • All 19 ChangeDependencyTest tests pass
  • Integration test in rewrite-spring confirms Spring Boot 4 migration no longer pins starter versions

…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}")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant