Skip to content

Fix RemoveDependency.unlessUsing for multi-module Maven projects#183

Open
steve-aom-elliott wants to merge 2 commits into
mainfrom
fix-remove-dependency-multimodule-unlessusing
Open

Fix RemoveDependency.unlessUsing for multi-module Maven projects#183
steve-aom-elliott wants to merge 2 commits into
mainfrom
fix-remove-dependency-multimodule-unlessusing

Conversation

@steve-aom-elliott

Copy link
Copy Markdown
Contributor

Summary

RemoveDependency's unlessUsing guard is per-JavaProject marker, which corresponds to a single Maven module. In a multi-module reactor the parent pom carries a different JavaProject than its children, so type usages found in child modules never gate removal from the parent pom — the <dependency> gets stripped from the parent while child modules still rely on inheriting it, producing surprise compile errors in downstream builds.

This came up while looking into a Spring Boot 4 migration failure where io.moderne.java.spring.boot4.MigrateSpringRetry (which calls RemoveDependency: unlessUsing: org.springframework.retry..* at the end of its recipe list) removed spring-retry from a parent pom even though child modules contained extensive RetryTemplate/RetryPolicy/BackOffPolicy usage.

Two commits:

  1. Fix RemoveDependency.unlessUsing for multi-module Maven projects — scanner now also records MavenResolutionResult.id -> JavaProject. When the visitor is about to remove from a pom, it walks MavenResolutionResult.getModules() recursively and preserves the dep if any descendant's JavaProject shows the type in use.
  2. Allow RemoveDependency to remove ancestor decl when descendants self-declare — refines the descendant check: a descendant only blocks removal if it uses the type AND does not redeclare the dep directly. A descendant that self-declares is self-sufficient, so the ancestor's declaration is dead weight. The self-declaration check uses the raw Pom.getRequested().getDependencies() rather than the resolved/inherited list, so a child that inherits the dep from the very ancestor we're modifying still correctly blocks removal.

Test plan

Five new tests in RemoveDependencyTest (all passing):

  • doNotRemoveSpringRetryWhenRetryTemplateInUse — single module, body usage → preserved
  • doNotRemoveSpringRetryWhenOnlyImportsPresent — single module, imports only → preserved
  • doNotRemoveSpringRetryWhenDepAndUsageInSameChildModule — multi-module, dep + Java in same child → preserved
  • doNotRemoveSpringRetryInMultiModuleWhenChildUsesItpreviously failing, parent has dep, child uses → preserved
  • removeParentDependencyWhenChildSelfDeclaresAndUses — parent has dep, child redeclares and uses → parent dep removed (commit 2 behavior)

Existing tests continue to pass.

`RemoveDependency` scans every source file and records whether `unlessUsing`
matched against the file's `JavaProject` marker. In a multi-module Maven
project the parent pom carries its own `JavaProject` distinct from the
children, so the parent module's accumulator entry never reflects type
usages found in child modules. The visitor would then strip the
`<dependency>` from the parent pom even though child modules in the same
reactor still relied on inheriting it.

Now the scanner additionally records the `MavenResolutionResult.id ->
JavaProject` mapping for every pom it visits. When the visitor is about to
remove a `<dependency>` from a pom, it walks that pom's descendant modules
via `MavenResolutionResult.getModules()` and preserves the declaration if
any descendant's `JavaProject` shows the type in use.
…f-declare

The descendant-aware preservation introduced in the previous commit was
intentionally conservative: any descendant module whose Java sources used
the `unlessUsing` type blocked removal of the dep from an ancestor pom.
That over-preserves when a descendant module also declares the same
dependency directly in its own `<dependencies>` — in that case the
descendant is self-sufficient and the ancestor's declaration is dead
weight.

Refine the check: a descendant only blocks removal if it uses the type AND
does not declare the dependency itself. The self-declaration check looks
at the raw `requested` pom (as-written), not the resolved/inherited
dependency list, so a child that inherits the dep from the ancestor we
might modify still correctly blocks removal.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Jun 12, 2026
@steve-aom-elliott steve-aom-elliott added bug Something isn't working enhancement New feature or request recipe labels Jun 12, 2026
@steve-aom-elliott steve-aom-elliott moved this from In Progress to Ready to Review in OpenRewrite Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request recipe

Projects

Status: Ready to Review

Development

Successfully merging this pull request may close these issues.

1 participant