diff --git a/CHANGES.md b/CHANGES.md index 6d506cd2e2..3dbe1a9abb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Changes - `Formatter` no longer recomputes line-ending normalization (`LineEnding.toUnix`) a second time for every formatter step that changes content, removing redundant O(n) work from the core formatting loop. ([#2934](https://github.com/diffplug/spotless/pull/2934)) +- expandWildcardImports support pom type dependency. ([#2839](https://github.com/diffplug/spotless/issues/2839)) ## [4.6.1] - 2026-05-15 ### Fixed diff --git a/lib/src/javaParser/java/com/diffplug/spotless/glue/javaparser/ExpandWildcardsFormatterFunc.java b/lib/src/javaParser/java/com/diffplug/spotless/glue/javaparser/ExpandWildcardsFormatterFunc.java index d5c7dd1956..b981386b19 100644 --- a/lib/src/javaParser/java/com/diffplug/spotless/glue/javaparser/ExpandWildcardsFormatterFunc.java +++ b/lib/src/javaParser/java/com/diffplug/spotless/glue/javaparser/ExpandWildcardsFormatterFunc.java @@ -72,7 +72,7 @@ public ExpandWildcardsFormatterFunc(Collection typeSolverClasspath) throws CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(); combinedTypeSolver.add(new ReflectionTypeSolver(ReflectionTypeSolver.JCL_ONLY)); for (File element : typeSolverClasspath) { - if (element.isFile()) { + if (element.isFile() && element.getName().endsWith(".jar")) { combinedTypeSolver.add(new JarTypeSolver(element)); } else if (element.isDirectory()) { combinedTypeSolver.add(new JavaParserTypeSolver(element)); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/ExpandWildcardImportsStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/ExpandWildcardImportsStepTest.java index 922eca8041..394f8d6d0c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/ExpandWildcardImportsStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/ExpandWildcardImportsStepTest.java @@ -15,8 +15,13 @@ */ package com.diffplug.spotless.maven.java; +import java.nio.file.Files; +import java.nio.file.Path; + import org.junit.jupiter.api.Test; +import com.diffplug.common.io.Resources; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.maven.MavenIntegrationHarness; class ExpandWildcardImportsStepTest extends MavenIntegrationHarness { @@ -41,4 +46,243 @@ void expandWildcardImports() throws Exception { assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test"); } + + /** + * Baseline: tests that types from directly declared JAR dependencies + * are correctly resolved for wildcard import expansion. + */ + @Test + void expandWildcardImports_withDirectJarDependency() throws Exception { + setupLocalMavenRepo(); + + String projectDeps = + " \n" + + " com.example\n" + + " test-annotation-lib\n" + + " 1.0.0\n" + + " \n"; + + writePomWithProjectDeps("", projectDeps); + + setFile("src/main/java/foo/bar/AnotherClassInSamePackage.java") + .toResource("java/expandwildcardimports/AnotherClassInSamePackage.test"); + setFile("src/main/java/foo/bar/baz/AnotherImportedClass.java") + .toResource("java/expandwildcardimports/AnotherImportedClass.test"); + + String path = "src/main/java/foo/bar/JavaClassWithWildcards.java"; + setFile(path).toResource("java/expandwildcardimports/JavaClassWithWildcardsUnformatted.test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test"); + } + + /** + * Tests that types from JARs transitively brought in by a POM-type dependency + * are correctly resolved for wildcard import expansion. + */ + @Test + void expandWildcardImports_withPomTypeDependency() throws Exception { + setupLocalMavenRepo(); + + String projectDeps = + " \n" + + " com.example\n" + + " test-dependencies\n" + + " 1.0.0\n" + + " pom\n" + + " \n"; + + writePomWithProjectDeps("", projectDeps); + + // Create supporting classes in source roots + setFile("src/main/java/foo/bar/AnotherClassInSamePackage.java") + .toResource("java/expandwildcardimports/AnotherClassInSamePackage.test"); + setFile("src/main/java/foo/bar/baz/AnotherImportedClass.java") + .toResource("java/expandwildcardimports/AnotherImportedClass.test"); + // SomeAnnotation comes from the JAR (transitively via POM dependency), not from source + + String path = "src/main/java/foo/bar/JavaClassWithWildcards.java"; + setFile(path).toResource("java/expandwildcardimports/JavaClassWithWildcardsUnformatted.test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test"); + } + + /** + * Tests that wildcard imports are expanded for types from JARs transitively + * brought in by a POM-type dependency (e.g. an aggregator POM that lists + * multiple library dependencies). Uses types from two separate transitive + * JARs: one providing an annotation, another providing model classes. + */ + @Test + void expandWildcardImports_withPomDependencyTransitiveJars() throws Exception { + setupLocalMavenRepo(); + + String projectDeps = + " \n" + + " com.example\n" + + " test-dependencies\n" + + " 1.0.0\n" + + " pom\n" + + " \n"; + + writePomWithProjectDeps("", projectDeps); + + // No source-root classes for org.example.model — those types come + // exclusively from the transitive JARs resolved via the POM dependency. + String path = "src/main/java/foo/bar/PomDepClass.java"; + setFile(path).toResource("java/expandwildcardimports/PomDepWildcardImportsUnformatted.test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + assertFile(path).sameAsResource("java/expandwildcardimports/PomDepWildcardImportsFormatted.test"); + } + + /** + * Tests that both direct JAR dependencies and POM-type dependencies work together. + * The SomeAnnotation type is available both directly (via test-annotation-lib JAR) + * and transitively (via test-dependencies POM). + */ + @Test + void expandWildcardImports_withMixedPomAndJarDependencies() throws Exception { + setupLocalMavenRepo(); + + String projectDeps = + " \n" + + " com.example\n" + + " test-dependencies\n" + + " 1.0.0\n" + + " pom\n" + + " \n" + + " \n" + + " com.example\n" + + " test-annotation-lib\n" + + " 1.0.0\n" + + " \n"; + + writePomWithProjectDeps("", projectDeps); + + setFile("src/main/java/foo/bar/AnotherClassInSamePackage.java") + .toResource("java/expandwildcardimports/AnotherClassInSamePackage.test"); + setFile("src/main/java/foo/bar/baz/AnotherImportedClass.java") + .toResource("java/expandwildcardimports/AnotherImportedClass.test"); + + String path = "src/main/java/foo/bar/JavaClassWithWildcards.java"; + setFile(path).toResource("java/expandwildcardimports/JavaClassWithWildcardsUnformatted.test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test"); + } + + private void setupLocalMavenRepo() throws Exception { + // Write a minimal POM so that mvnw can start for install:install-file + setFile("pom.xml").toContent( + "\n" + + " 4.0.0\n" + + " tmp\n" + + " tmp\n" + + " 1.0\n" + + "\n"); + + // Copy JARs using binary I/O (setFile().toResource() uses text I/O which corrupts binary files) + copyBinaryResource("java/expandwildcardimports/example-lib.jar", "test-annotation-lib.jar"); + copyBinaryResource("java/expandwildcardimports/example-model.jar", "test-model-lib.jar"); + + String aggregatorPomContent = + "\n" + + "\n" + + " 4.0.0\n" + + " com.example\n" + + " test-dependencies\n" + + " 1.0.0\n" + + " pom\n" + + " \n" + + " \n" + + " com.example\n" + + " test-annotation-lib\n" + + " 1.0.0\n" + + " \n" + + " \n" + + " com.example\n" + + " test-model-lib\n" + + " 1.0.0\n" + + " \n" + + " \n" + + "\n"; + setFile("test-dependencies-pom.xml").toContent(aggregatorPomContent); + + // Install artifacts into the local Maven repo so transitive resolution works + mavenRunner().withArguments( + "install:install-file", + "-Dfile=test-annotation-lib.jar", + "-DgroupId=com.example", + "-DartifactId=test-annotation-lib", + "-Dversion=1.0.0", + "-Dpackaging=jar").runNoError(); + + mavenRunner().withArguments( + "install:install-file", + "-Dfile=test-model-lib.jar", + "-DgroupId=com.example", + "-DartifactId=test-model-lib", + "-Dversion=1.0.0", + "-Dpackaging=jar").runNoError(); + + mavenRunner().withArguments( + "install:install-file", + "-Dfile=test-dependencies-pom.xml", + "-DgroupId=com.example", + "-DartifactId=test-dependencies", + "-Dversion=1.0.0", + "-Dpackaging=pom").runNoError(); + } + + private void writePomWithProjectDeps(String spotlessConfig, String projectDependencies) throws Exception { + String spotlessVersion = System.getProperty("spotlessMavenPluginVersion"); + String pomContent = + "\n" + + " 4.0.0\n" + + " com.diffplug.spotless\n" + + " spotless-maven-plugin-tests\n" + + " 1.0.0-SNAPSHOT\n" + + " \n" + + " 3.1.0\n" + + " \n" + + " \n" + + " UTF-8\n" + + " 1.8\n" + + " 1.8\n" + + " \n" + + " \n" + + projectDependencies + + " \n" + + " \n" + + " \n" + + " \n" + + " com.diffplug.spotless\n" + + " spotless-maven-plugin\n" + + " " + spotlessVersion + "\n" + + " \n" + + " " + spotlessConfig + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + setFile("pom.xml").toContent(pomContent); + } + + private void copyBinaryResource(String resourcePath, String targetName) throws Exception { + byte[] bytes = Resources.toByteArray(ResourceHarness.class.getResource("/" + resourcePath)); + Path target = newFile(targetName).toPath(); + Files.createDirectories(target.getParent()); + Files.write(target, bytes); + } } diff --git a/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsFormatted.test b/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsFormatted.test new file mode 100644 index 0000000000..7719081c51 --- /dev/null +++ b/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsFormatted.test @@ -0,0 +1,11 @@ +package foo.bar; + +import java.util.List; +import org.example.model.ExampleModel; +import org.example.model.ExampleStatus; + +public class PomDepClass { + private List items; + private ExampleModel model; + private ExampleStatus status; +} diff --git a/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsUnformatted.test b/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsUnformatted.test new file mode 100644 index 0000000000..e6ea35040e --- /dev/null +++ b/testlib/src/main/resources/java/expandwildcardimports/PomDepWildcardImportsUnformatted.test @@ -0,0 +1,10 @@ +package foo.bar; + +import java.util.*; +import org.example.model.*; + +public class PomDepClass { + private List items; + private ExampleModel model; + private ExampleStatus status; +} diff --git a/testlib/src/main/resources/java/expandwildcardimports/example-model.jar b/testlib/src/main/resources/java/expandwildcardimports/example-model.jar new file mode 100644 index 0000000000..748483df52 Binary files /dev/null and b/testlib/src/main/resources/java/expandwildcardimports/example-model.jar differ