Add pmd recipes - #1034
Open
sullis wants to merge 3 commits into
Open
Conversation
sullis
force-pushed
the
ss-pmd
branch
3 times, most recently
from
August 30, 2026 17:05
e8df97a to
2c3e6ca
Compare
sullis
force-pushed
the
ss-pmd
branch
2 times, most recently
from
August 30, 2026 17:18
5d4e4d3 to
0ce45f1
Compare
sullis
marked this pull request as ready for review
August 30, 2026 17:20
Author
|
ready for review |
Contributor
|
Great job here! I am wondering, what if we consider this test case: @Test
void removeStaleExclusionWhenReplacementMovesToAnotherRuleset() {
rewriteRun(
spec -> spec.recipe(new ReplacePmdRule(
"category/java/errorprone.xml/EmptyIfStmt", "category/java/codestyle.xml/EmptyControlStatement")),
//language=xml
xml(
"""
<?xml version="1.0"?>
<ruleset name="custom">
<rule ref="category/java/errorprone.xml">
<exclude name="EmptyIfStmt"/>
<exclude name="EmptyCatchBlock"/>
</rule>
</ruleset>
""",
"""
<?xml version="1.0"?>
<ruleset name="custom">
<rule ref="category/java/errorprone.xml">
<exclude name="EmptyCatchBlock"/>
</rule>
</ruleset>
"""
)
);
}It currently fails. Shouldn't the migration recipe take care of it? |
sullis
force-pushed
the
ss-pmd
branch
2 times, most recently
from
August 31, 2026 15:36
27beecf to
0f71baf
Compare
Author
Thanks for the feedback! I added this test case. I pushed a new implementation on this branch |
Author
|
ready for review / feedback @greg-at-moderne |
Author
|
Maybe we should move these recipes into a dedicated repo (rewrite-pmd) |
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.
What's changed
Adds recipes that operate on PMD ruleset XML files, so a project's PMD configuration can be migrated automatically alongside its code.
ReplacePmdRule— updates<rule ref="..."/>references and<exclude name="..."/>elements to name a rule's replacement.oldRuleandnewRulemay each be a fully qualified reference (category/java/errorprone.xml/MissingBreakInSwitch) or a bare rule name: a bareoldRulematches regardless of which ruleset file it is referenced from, and a barenewRulekeeps the existing ruleset file. An<exclude>is only renamed when the replacement lives in the same ruleset file, since an exclusion can only name a rule from the ruleset its enclosing<rule>refers to; when the replacement moved to a different ruleset file the exclusion no longer names a rule that ruleset knows, so it is removed instead.RemovePmdRule— removes both<rule ref="..."/>references to a rule and<exclude name="..."/>elements naming it, for rules PMD deleted without offering a replacement.AddPmdRule— adds a<rule ref="..."/>reference to rulesets that do not have one yet.rulemust be fully qualified, since PMD resolves a rule through the ruleset file it lives in; a bare name failsvalidate(). When the ruleset already pulls in the whole ruleset file, the rule is enabled by dropping the<exclude>that was keeping it out rather than by adding a second reference to it.Pmd6to7Migration(declarative, inpmd.yml) — composes 49 applications ofReplacePmdRuleandRemovePmdRule: 45 replacements and 4 removals. PMD 7 deleted the rules that had been deprecated throughout the PMD 6 line, and PMD refuses to load a ruleset that references a rule it does not know, so an un-migrated ruleset is a hard failure rather than a warning.Pmd7RuleRenames(declarative, inpmd.yml) — composes 13 applications ofReplacePmdRulefor the renames PMD made within the PMD 7 line (7.7.0, 7.18.0, 7.25.0, 7.27.0). Unlike the 6→7 deletions these are not hard failures yet — PMD keeps the old name as a deprecated alias — so this is a separate recipe rather than part ofPmd6to7Migration: applying it silences the deprecation warnings and keeps the ruleset loading once PMD 8 drops the aliases, but it also raises the required PMD version to 7.27.0, which is a choice the 6→7 migration should not make on a project's behalf.ModernizePmd(declarative, inpmd.yml) — the entry point for a project that just wants its ruleset to name each rule as PMD knows it today: runsPmd6to7Migrationfollowed byPmd7RuleRenames. Because it includes the renames, its result requires PMD 7.27.0 or later; a project pinned to an earlier PMD 7 should runPmd6to7Migrationon its own.All three imperative recipes are scoped by returning the document unchanged from
visitDocumentunless its root tag is<ruleset>, rather than by a filename glob — PMD rulesets are not named consistently (ruleset.xml,pmd-rules.xml, …), and tags as generic as<rule>and<exclude>show up in unrelated XML. Two helpers are shared between them:PmdRuleRefparses a rule reference into ruleset file plus rule name, andRemoveRulesetContentVisitorextendsRemoveContentVisitorto collapse a<rule>whose last<exclude>was just removed back to a self-closing tag, while leaving an emptied<ruleset>with its body intact.Rules whose behaviour PMD split or redirected across a different successor are deliberately left out of the declarative recipes, because picking a single replacement for them requires a judgement call the recipe should not make silently:
VariableNamingConventionsand the primitive wrapper*Instantiationrules;AvoidCatchingNPE→AvoidCatchingGenericException,GenericsNaming→TypeParameterNamingConventions,UnnecessaryLocalBeforeReturn→VariableCanBeInlined,UseObjectForClearerAPI→ExcessiveParameterList, andCheckSkipResult/AvoidLosingExceptionInformation/UselessOperationOnImmutable→UnusedReturnValue. Each successor reports something different from the rule it replaces, so adopting it is an opt-in, not a rename.Other changes
build.gradle.kts: addsprovided("org.openrewrite:rewrite-xml")alongside the otherprovidedlanguage modules, keeping it off downstream consumers' compile classpaths while making it available here.recipes.csv: regenerated viarecipeCsvGenerateto add the six new recipes (recipeCsvValidateCompletenessfails the build otherwise).recipe-writing-lessons.md: notes from writing these.Testing
43 tests across
AddPmdRuleTest,RemovePmdRuleTest,ReplacePmdRuleTest,Pmd6to7MigrationTest,Pmd7RuleRenamesTest, andModernizePmdTestcover the fully qualified and bare-name forms,<exclude>handling in both the same-ruleset and cross-ruleset cases, enabling a rule by removing its exclusion, rejection of a bareAddPmdRulerule name, a ruleset that needs both a 6→7 replacement and a 7-line rename in one pass, non-<ruleset>XML that must be left alone, and no-change cases../gradlew buildpasses locally.PMD project
https://docs.pmd-code.org/latest/index.html
https://github.com/pmd/pmd
PMD release notes
https://docs.pmd-code.org/latest/pmd_release_notes.html
https://docs.pmd-code.org/pmd-doc-7.0.0/pmd_release_notes_pmd7.html
🤖 Generated with Claude Code