Skip to content

Add $N backref capture to ChangeFrom#7371

Merged
jkschneider merged 1 commit intomainfrom
change-from-capture
Apr 14, 2026
Merged

Add $N backref capture to ChangeFrom#7371
jkschneider merged 1 commit intomainfrom
change-from-capture

Conversation

@jkschneider
Copy link
Copy Markdown
Member

Summary

  • Each * in oldImageName / oldTag / oldDigest / oldPlatform is now a positional capture. $N in the paired new* substitutes capture N; $0 substitutes the full original field; \$ is a literal.
  • Capture numbering is per-field (no cross-field sharing). validate() rejects dangling references.
  • Lets callers collapse cartesian enumerations like:
    new ChangeFrom("openjdk", "8*", null, null, "eclipse-temurin", "17\$1", null, null)
    which matches openjdk:8, openjdk:8-jdk-alpine, openjdk:8-jre-noble, etc. and rewrites each to the eclipse-temurin:17… equivalent in one recipe.

Context

org.openrewrite.java.migrate.UpgradeDockerImageVersion in rewrite-migrate-java currently emits ~3,150 ChangeFrom children per invocation because it has to enumerate every (image × oldVersion × suffix) triple. Capture support lets that recipe shrink to ~90 children per invocation (35× reduction). A follow-up PR in rewrite-migrate-java consumes this feature.

Test plan

  • All 53 existing ChangeFromTest cases still pass
  • 10 new @Nested Captures tests cover: single capture, multi-capture preservation, $0 full-match, \$ literal, image-name capture, and three validate() rejection cases (backref without matching capture, backref beyond capture count, $0 without captures is still valid)

Each `*` in `oldImageName` / `oldTag` / `oldDigest` / `oldPlatform` is now
a positional capture group, numbered in order of appearance within that
field. `$N` in the paired `new*` template substitutes capture N.
`$0` substitutes the full original field. `\$` is a literal dollar.
Capture numbering is per-field; there is no cross-field sharing.

Motivation: callers that want to preserve an arbitrary suffix across a
rewrite (e.g. `openjdk:8-jdk-alpine` -> `eclipse-temurin:17-jdk-alpine`)
previously had to enumerate every suffix variant, because `new*` values
were treated as literals. With capture support, the same transformation
collapses to a single recipe:

    new ChangeFrom("openjdk", "8*", null, null,
                   "eclipse-temurin", "17$1", null, null)

`$1` carries whatever the `*` matched, including the empty string for the
bare `openjdk:8` tag (standard glob semantics per `StringUtils.matchesGlob`).

Validation in `validate()` rejects `$N` references that exceed the number
of `*`s in the paired `old*` glob, so authoring typos surface at recipe
construction rather than as silent wrong rewrites.
@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Apr 14, 2026
@jkschneider jkschneider merged commit 49d5aad into main Apr 14, 2026
1 check passed
@jkschneider jkschneider deleted the change-from-capture branch April 14, 2026 14:34
@github-project-automation github-project-automation bot moved this from In Progress to Done in OpenRewrite Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant