Skip to content

Skip the JsonPathMatcher walk for non-matching keys in setup-java/setup-node recipes - #220

Merged
timtebeek merged 3 commits into
mainfrom
tim/guard-jsonpath-matchers
Aug 23, 2026
Merged

Skip the JsonPathMatcher walk for non-matching keys in setup-java/setup-node recipes#220
timtebeek merged 3 commits into
mainfrom
tim/guard-jsonpath-matchers

Conversation

@timtebeek

@timtebeek timtebeek commented Aug 23, 2026

Copy link
Copy Markdown
Member

What's changed?

Carries the JsonPathMatcher optimization from #219 back to the recipes that PR was modelled on.

  • Test the mapping entry's key name before calling JsonPathMatcher.matches, in SetupJavaUpgradeJavaVersion, SetupNodeUpgradeNodeVersion and SetupJavaDistributionReplacerVisitor.
  • Add a strategy.matrix test per recipe.
  • Reindent SetupNodeUpgradeNodeVersionTest to the continuation indent .editorconfig asks for (separate whitespace-only commit).

What's your motivation?

JsonPathMatcher.matches walks the whole document: for a ..-prefixed path find0 starts at cursorPath.get(0), so calling it from visitMappingEntry re-visits the enclosing document once per mapping entry. Testing the key name first skips that walk for every entry that cannot match. Visit time on a synthetic workflow, parse excluded, mean of 10 warmed runs:

workflow before after
74 lines 0.40 ms 0.04 ms
284 lines 2.80 ms 0.24 ms
1124 lines 32.25 ms 2.49 ms

Anything in particular you'd like reviewers to focus on?

The key comparison is the one thing here that could change behaviour, because the key name alone no longer distinguishes a matrix declaration from a step input:

strategy:
  matrix:
    node-version: [18, 20]
steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}

None of the three recipes had any matrix coverage, so the new tests pin down that only the step input is considered. In SetupJavaDistributionReplacerVisitor this is load-bearing rather than theoretical: I first hoisted the originalDistributions membership test above the matcher, which let the matrix sequence reach (Yaml.Scalar) entry.getValue() and throw ClassCastException. The matcher is back in front of it, and doNotChangeMatrixDistribution fails if it moves again.

Have you considered any alternatives or workarounds?

The other changes in #219 do not carry over:

  • The block scalar style guard is not needed. A folded value arrives as -\n 18\n and a literal as \n 18\n, and the version pattern is anchored at a digit, so both already fall through.
  • The Semver rewrite has nothing to replace. These recipes parse a leading major with one regex and compare ints; they never attempt range arithmetic. Making the Node recipe understand ranges like ^18 or 18.x would be a behaviour change, so I left it out.

I also had instanceof Yaml.Scalar guards here for a sequence written directly under with, but that is not valid GitHub Actions input, so covering it is not worth the code.

Checklist

  • I have added negative tests that demonstrate unchanged cases.
  • I ran ./gradlew build.

`JsonPathMatcher.matches` walks the whole document: for a `..`-prefixed
path `find0` starts at `cursorPath.get(0)`, so calling it from
`visitMappingEntry` re-visits the enclosing document once per mapping
entry. Testing the key name first skips that walk for every entry that
cannot match. Visit time on a synthetic workflow, parse excluded, mean
of 10 warmed runs:

    lines=74    0.40 ms -> 0.04 ms
    lines=284   2.80 ms -> 0.24 ms
    lines=1124 32.25 ms -> 2.49 ms

The unchecked `(Yaml.Scalar) entry.getValue()` is a crash rather than a
cost. The path matches the entry regardless of what its value holds, so
a sequence value throws `ClassCastException`:

    java-version:
      - 11
      - 17

Adding `instanceof Yaml.Scalar` leaves those unchanged, matching how the
recipes already skip values they cannot interpret.

`SetupJavaDistributionReplacerVisitor` carries the same two problems, so
it gets the same guards, with the existing `originalDistributions`
membership test moved ahead of the matcher for the same reason.
`.editorconfig` sets `ij_continuation_indent_size = 2` for
`src/test/java`, and every other test file in the repository follows it.
This one used 8-space continuation steps, making it the sole outlier.

Whitespace only; the text block contents are unchanged once the
incidental indentation is stripped, which the tests confirm.
A sequence under `with` is not valid GitHub Actions input, so it is not
worth covering. Drop those tests.

`strategy.matrix.<name>` is valid and common, though, and it is the case
the new key comparison actually puts at risk: the key name alone no
longer distinguishes a matrix declaration from a step input, so these
tests pin down that only the latter is rewritten.

That also removes the reason for the `instanceof Yaml.Scalar` guards,
which only ever caught the invalid spelling. The one place they were
load-bearing was `SetupJavaDistributionReplacerVisitor`, where hoisting
the `originalDistributions` test above the matcher let a matrix sequence
reach the cast; putting the matcher back in front fixes that without the
extra guard, and the matcher was always the expensive half anyway.
@timtebeek timtebeek changed the title Guard setup-java/setup-node version matchers on key and scalar Skip the JsonPathMatcher walk for non-matching keys in setup-java/setup-node recipes Aug 23, 2026
@timtebeek
timtebeek merged commit 4e5513a into main Aug 23, 2026
1 check passed
@timtebeek
timtebeek deleted the tim/guard-jsonpath-matchers branch August 23, 2026 16:13
@github-project-automation github-project-automation Bot moved this from In Progress to Done in OpenRewrite Aug 23, 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