Repository navigation
Report unknown declarative recipe options as validation failures - #9393
Merged
Merged
Conversation
Option keys a recipe can't bind were silently dropped by both the reflective and the Jackson construction paths, so typos and stale options passed validateAll() while the recipe ran without them.
This was referenced Oct 25, 2026
Merged
timtebeek
marked this pull request as draft
October 25, 2026 18:16
timtebeek
commented
Oct 25, 2026
| addRecipe.accept(recipeLoader.apply(recipeName, (Map<String, Object>) recipeArgs)); | ||
| Recipe recipe = recipeLoader.apply(recipeName, options); | ||
| addRecipe.accept(recipe); | ||
| validateOptionNames(recipeName, recipe, options, addValidation); |
Member
Author
There was a problem hiding this comment.
It seems strong to fail to load if we had ignored this up to now; should we introduce a grace period where we only validate this in tests? Or would that risk too many untested recipes continuing to ignore invalid options?
timtebeek
marked this pull request as ready for review
October 25, 2026 18:20
Member
Author
|
As discussed on Slack: we've already cleared out the issues on openrewrite/; now doing the same for moderneinc/; those downstream PRs can land already; we'll hold off on this one until after the next release is out. |
18 of 41 tasks
Member
Author
|
With the 8.94.0 release now out we'll merge this one to avoid further potential issues in recipes. There's one more fix coming for rewrite-spring-to-quarkus. |
timtebeek
pushed a commit
to openrewrite/rewrite-spring-to-quarkus
that referenced
this pull request
Oct 27, 2026
…nore (#97) 41 option keys in these declarative recipes are not options of the recipe they configure. The recipe ignores them silently today, openrewrite/rewrite#9393 reports them as validation failures, and they shipped in v0.14.0. 32 are `onlyDirect: true` on `org.openrewrite.java.dependencies.DependencyInsight`, which has no such option and so matched transitive dependencies too. A module that got `spring-boot-starter-web` only through a sibling module was given its own `quarkus-resteasy-jackson`, and one that got `h2` that way its own `quarkus-jdbc-h2`. `org.openrewrite.java.dependencies.FindDependency` matches only declared dependencies, in Maven and Gradle: - The 28 `SpringBoot*`/`SpringKafka*ToQuarkus` preconditions in boot-starters.yml now use it. - `H2TestDriverToQuarkus` now uses it and loses `scope: test`. Every declared scope is on Maven's test classpath, and Gradle never read that option. - `H2DriverToQuarkus` and `DerbyDriverToQuarkus` only lose the key. Their `ChangeDependency` steps touch only declared dependencies anyway. - `DerbyTestDriverToQuarkus` only loses the key. Its `derby|derbyclient` pattern matches nothing, because glob patterns have no alternation, so the recipe never runs either way. `ModuleHasDependency` does take `onlyDirect`, but it records its matches before any recipe edits. With it, `SpringBootWebToQuarkusClassic` would still match after `SpringBootWebToQuarkusReactive` removed the starter, and both Quarkus REST stacks would be added. The AMQP and Kafka pairs work the same way. The other 9 are `fileMatcher`, which neither `DeleteProperty` nor `DeleteKey` reads, so the DevTools and Actuator deletions apply to every properties and YAML file. That stays. Only Spring Boot reads these keys, it also reads them from files outside `application*` (`spring.config.import`, Spring Cloud's `bootstrap*`), and no other property step here is limited to particular files. The two DevTools `DeleteKey` steps were then identical, so they are merged. New tests run `MigrateBootStarters` and `MigrateDatabaseDrivers` on two modules, the second getting the starter or `h2` only through the first. Both failed before this change. Another test covers the merged `DeleteKey`. Against a rewrite-core built from #9393, all 68 recipes here validate and no test fails on an unknown option. Before this change, all 37 tests of the affected recipes failed against it.
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.
Option keys in declarative YAML that a recipe can't bind were silently dropped. Reflective construction only reads constructor parameters, and the Jackson fallback has
FAIL_ON_UNKNOWN_PROPERTIESdisabled. As a result, a typo or a stale option passedvalidateAll()and the recipe ran without it:#4377 originally made tests fail on unknown keys, through the
FAIL_ON_UNKNOWN_PROPERTIESmapper customizer inRecipeSpec. #5050 then moved recipe construction ontoRecipeLoader's own mapper, so that customizer no longer reached recipes, andRecipeLifecycleTestwas flipped tocanCall…WithUnnecessaryArgs. This PR restores the original intent for every caller, not just tests.YamlResourceLoadernow checks each key after the recipe is built. A key is known if it is a public constructor parameter name, an@Optionfield, or a property Jackson can set on the recipe class itself, including@JsonAliasaliases.@Optionfields count even when Jackson can't set them, becauseRecipe.withOptions(the marketplace path) binds fields at any visibility. The check is skipped for:RpcRecipe, whose options the remote validates;@JsonIgnoreProperties(ignoreUnknown = true)or a@JsonAnySetter;A
DeclarativeRecipereached through the marketplace takes no options. Keys are matched case-sensitively, like the reflective path, although Jackson accepts any case.Merge after the downstream fixes
RewriteTestassertsvalidateAll(), and downstream CI picks up rewrite snapshots within minutes. These recipes pass unknown keys today, so their repos' tests break as soon as this merges:AddManualTriggerto workflow files rewrite-github-actions#240:AddManualTriggerMergeYamlfileMatcher→filePatternconfiguration, notscope, to GradleDependencyInsightinSpringWebDependencyrewrite-spring#1227:SpringWebDependencyGradleDependencyInsightscope→configurationexcludedBoundsinCommonDeclarationSiteTypeVariancesrewrite-static-analysis#1131:CommonDeclarationSiteTypeVariancesexcludeBounds→excludedBoundschangeManagedDependencyfrom GradleChangeDependencyClassifierrewrite-migrate-java#1363:UpdateApacheShiroDependenciesdrops the Maven-onlychangeManagedDependencyfrom the Gradle recipeMigrateToHibernate70AddDependencynewVersion→versionMigrateToJsf_2_3dropsfileMatcherfromChangeTagAttribute, which has no file filterIn each, the recipe silently ignored the misspelled option. The github-actions, rewrite-spring, static-analysis and hibernate ones were actual behaviour bugs.
Verification
./gradlew :rewrite-core:test :rewrite-test:test :rewrite-yaml:testpasses, as do the inline-YAML recipe tests in rewrite-gradle, hcl, java-test, json, kotlin and maven.AppendToSequenceTest.appendTwicestill passedfileMatcher, removed fromAppendToSequencein Improved support for large repositories #3118, so I dropped that key.validateAll()over 842 declarative recipes: this repo's YAML plus 23 published recipe modules (spring, migrate-java, static-analysis, testing-frameworks, etc.). The only new failures were the four openrewrite ones above. With those fixes swapped into the jars, the failure count matches the unpatched baseline. Load time is unchanged within noise.