Only remove MockUtil declarations whose uses are all migrated - #1084
Draft
martinfrancois wants to merge 4 commits into
Draft
Only remove MockUtil declarations whose uses are all migrated#1084martinfrancois wants to merge 4 commits into
MockUtil declarations whose uses are all migrated#1084martinfrancois wants to merge 4 commits into
Conversation
MockUtilsToStatic scheduled DeleteStatement for the whole enclosing J.VariableDeclarations as soon as it found new MockUtil() as an initializer, without analysing how the declared variable is used. Uses that ChangeMethodTargetToStatic does not rewrite, such as arguments, returns, aliases and comparisons, were left referring to a name that no longer exists, and deleting the statement also dropped sibling declarators together with the evaluation of their initializers, so the recipe emitted source that does not compile. The declaration is now analysed before anything is removed: a declarator is obsolete only when every reference to it in the compilation unit is the receiver of a MockUtil call that becomes static, written bare or through this, as an invocation or as a method reference. Only that declarator is removed, so siblings keep their type, order and initializer evaluation. A declaration without complete symbol attribution is left alone, and the MockUtil import is removed with the last declaration. Two limits worth noting. The analysis covers a single compilation unit, so uses of a visible field from another source file are not considered. Side effects in a call qualifier are still lost inside ChangeMethodTargetToStatic; that is a separate defect and is not addressed here. No existing test expectation changed.
MockUtil declarations whose uses are all migrated
4 tasks
4 tasks
martinfrancois
marked this pull request as draft
August 16, 2026 01:10
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.
Suggested review order: 48 of 52 (Score: 1)
Review first: #1088
What's changed?
MockUtilsToStaticnow removes aMockUtildeclaration only when every use of that variable in the same file is one the migration rewrites away: the variable is the receiver of a call to aMockUtilmethod (util.isMock(x),this.util.isMock(x)) or the qualifier of a method reference to one (util::isMock,this.util::isMock). Those uses becomeMockUtil.isMock(x)andMockUtil::isMockand no longer need the instance. Any other use of the variable anywhere in the file keeps the declaration.The rule is applied per declared variable, so in
MockUtil util = new MockUtil(), observed = createObserved();onlyutilis dropped,observedkeeps its initializer andcreateObserved()is still evaluated; the statement itself goes only when no declared variable is left in it. TheMockUtilimport is removed only when nothing in the file still needs it. For example, the import remains when the recipe's output names the type inMockUtil.isMock(x).Before
Actual after the recipe
Using main today. The declaration of
utilhas been deleted, so theobserve(util)line no longer compiles:Expected after the recipe
The declaration stays, because
observe(util)is a use that the migration does not rewrite away:Rewriting the calls themselves is still left to
ChangeMethodTargetToStatic, exactly as before;MockUtilsToStaticonly decides which declarations to remove. The two cannot disagree about which calls count as rewritten: both are driven by the same method pattern,"org.mockito.internal.util.MockUtil *(..)", held in a single constant that the recipe passes toChangeMethodTargetToStaticand also uses to build its ownMethodMatcher.The removal has also moved. The
visitNewClassoverride that handed the enclosing statement toDeleteStatementis deleted, and with it the last use and the import ofDeleteStatement; a newvisitVariableDeclarationsoverride can drop a single declared variable out of a statement that declares several, preserving the spacing that the removed declarator carried around the comma and the semicolon.What's your motivation?
Recipe:
org.openrewrite.java.testing.mockito.MockUtilsToStatic.Main deletes the whole declaration statement as soon as it sees
new MockUtil()as an initializer, without checking whether the variable is still used anywhere, so the output above does not compile. With several declarators more than a name is lost: givenMockUtil util = new MockUtil(), observed = createObserved();the whole statement disappears, socreateObserved()is never called. That output reaches users through theMockito1to3Migrationcomposite inmockito.ymlas well as through a direct run of the recipe. Reproduced on 3.43.0 and on 3.44.0-SNAPSHOT built from main at 96ec8d6.Anything in particular you'd like reviewers to focus on?
No existing test expectation changed: the three existing tests are untouched.
Three limits are worth knowing:
case 1:in an old styleswitchare where this shows up: they hang off thecase, not off a block, so aMockUtildeclaration there stays even when every use of the variable is rewritten away, as it does on main. Where such a statement declares more than one variable, theMockUtilvariable is still dropped from it.new MockUtil()initializer, which does not compile against Mockito 2 and later, where that constructor is private, andMockito1to3Migrationbumps Mockito to 3.x in the same run. Main deletes the declaration and with it that call, so this change trades one declaration to fix by hand for a name that was undefined at every use site.Have you considered any alternatives or workarounds?
The analysis of uses lives in a private static nested class of this recipe's visitor,
FindUnmigratedUses. The only thing it reads from this repository is theMethodMatcherconstant of the enclosing visitor. Moving it as a shared helper into therewrite-javamodule ofopenrewrite/rewriterequires making that matcher a constructor parameter, opening a separate pull request there first, and rebasing this change onto the helper. Say so in review and I will open it; otherwiseFindUnmigratedUsesstays where it is.Any additional context
This change adds 17 test methods to
MockUtilsToStaticTest. The parameterizedremoveOnlyTheMigratedDeclaratormethod supplies 5 declaration layouts, so the focused class reports 24 executions: 21 added executions plus the 3 existing tests. Without the code change, 13 added executions fail. Four check that a declaration is kept when some use of the variable is not rewritten away. The parameterized cases cover removal of the first or last declarator and preserve comma and line-break formatting. The remaining cases guard against keeping a declaration because the nameutilalso appears as a package segment, enum constant, annotation attribute, nested type or type parameter.This change was prepared with AI assistance (Claude Code). I reviewed the code, the tests and this description.
Checklist
./gradlew buildlocally, and committed any resulting changes torecipes.csv