diff --git a/plugin-maven/README.md b/plugin-maven/README.md index aa855dd4b9..6bb834dcae 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -232,6 +232,7 @@ any other maven phase (i.e. compile) then it can be configured as below; + @@ -257,6 +258,14 @@ any other maven phase (i.e. compile) then it can be configured as below; ``` +### expandWildcardImports + +Automatically replaces wildcard imports (e.g., `import java.util.*`) with explicit imports for the classes actually used in the code. This step analyzes your source code and project dependencies to determine which specific classes are needed and generates the appropriate import statements. + +```xml + +``` + ### forbidModuleImports ```xml diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java index 3952a8de70..d2c84d7cdb 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java @@ -397,7 +397,7 @@ private FormatterConfig getFormatterConfig() { FileLocator fileLocator = getFileLocator(); final Optional optionalRatchetFrom = Optional.ofNullable(this.ratchetFrom) .filter(ratchet -> !RATCHETFROM_NONE.equals(ratchet)); - return new FormatterConfig(baseDir, encoding, lineEndings, optionalRatchetFrom, provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory), lintSuppressions); + return new FormatterConfig(baseDir, encoding, lineEndings, optionalRatchetFrom, provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory), lintSuppressions, project); } private FileLocator getFileLocator() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java index 842d4da68e..4ef7ff1800 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Optional; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.LintSuppression; import com.diffplug.spotless.Provisioner; @@ -35,9 +37,10 @@ public class FormatterConfig { private final List globalStepFactories; private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; private final List lintSuppressions; + private final MavenProject project; public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional ratchetFrom, Provisioner provisioner, - FileLocator fileLocator, List globalStepFactories, Optional spotlessSetLicenseHeaderYearsFromGitHistory, List lintSuppressions) { + FileLocator fileLocator, List globalStepFactories, Optional spotlessSetLicenseHeaderYearsFromGitHistory, List lintSuppressions, MavenProject project) { this.encoding = encoding; this.lineEndings = lineEndings; this.ratchetFrom = ratchetFrom; @@ -46,6 +49,7 @@ public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Op this.globalStepFactories = globalStepFactories; this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; this.lintSuppressions = lintSuppressions; + this.project = project; } public String getEncoding() { @@ -79,4 +83,8 @@ public FileLocator getFileLocator() { public List getLintSuppressions() { return unmodifiableList(lintSuppressions); } + + public MavenProject getProject() { + return project; + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java index 6c7cc0d349..62b266c1c2 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java @@ -185,7 +185,7 @@ Optional ratchetFrom(FormatterConfig config) { } private FormatterStepConfig stepConfig(Charset encoding, FormatterConfig config) { - return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), ratchetFrom(config), config.getProvisioner(), config.getFileLocator(), config.getSpotlessSetLicenseHeaderYearsFromGitHistory()); + return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), ratchetFrom(config), config.getProvisioner(), config.getFileLocator(), config.getSpotlessSetLicenseHeaderYearsFromGitHistory(), config.getProject()); } private static List gatherStepFactories(List allGlobal, List allConfigured) { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java index a1f2e52b41..847cd358b7 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java @@ -18,6 +18,8 @@ import java.nio.charset.Charset; import java.util.Optional; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.Provisioner; public class FormatterStepConfig { @@ -28,14 +30,16 @@ public class FormatterStepConfig { private final Provisioner provisioner; private final FileLocator fileLocator; private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; + private final MavenProject project; - public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional ratchetFrom, Provisioner provisioner, FileLocator fileLocator, Optional spotlessSetLicenseHeaderYearsFromGitHistory) { + public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional ratchetFrom, Provisioner provisioner, FileLocator fileLocator, Optional spotlessSetLicenseHeaderYearsFromGitHistory, MavenProject project) { this.encoding = encoding; this.licenseHeaderDelimiter = licenseHeaderDelimiter; this.ratchetFrom = ratchetFrom; this.provisioner = provisioner; this.fileLocator = fileLocator; this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; + this.project = project; } public Charset getEncoding() { @@ -61,4 +65,8 @@ public FileLocator getFileLocator() { public Optional spotlessSetLicenseHeaderYearsFromGitHistory() { return spotlessSetLicenseHeaderYearsFromGitHistory; } + + public MavenProject getProject() { + return project; + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ExpandWildcardImports.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ExpandWildcardImports.java new file mode 100644 index 0000000000..bcea932608 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ExpandWildcardImports.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.maven.java; + +import java.io.File; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.java.ExpandWildcardImportsStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class ExpandWildcardImports implements FormatterStepFactory { + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + MavenProject project = config.getProject(); + Set typeSolverClasspath = new HashSet<>(); + + // Add all main source roots + project.getCompileSourceRoots().stream() + .map(File::new) + .filter(File::exists) + .forEach(typeSolverClasspath::add); + + // Add all test source roots + project.getTestCompileSourceRoots().stream() + .map(File::new) + .filter(File::exists) + .forEach(typeSolverClasspath::add); + + // Add compiled dependencies + project.getArtifacts().stream() + .map(Artifact::getFile) + .filter(Objects::nonNull) + .forEach(typeSolverClasspath::add); + + return ExpandWildcardImportsStep.create(typeSolverClasspath, config.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java index acc97647d7..53068fead5 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java @@ -88,6 +88,10 @@ public void addFormatAnnotations(FormatAnnotations formatAnnotations) { addStepFactory(formatAnnotations); } + public void addExpandWildcardImports(ExpandWildcardImports expandWildcardImports) { + addStepFactory(expandWildcardImports); + } + public void addCleanthat(CleanthatJava cleanthat) { addStepFactory(cleanthat); } 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 new file mode 100644 index 0000000000..7b7fcbeeb3 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/ExpandWildcardImportsStepTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2025 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.maven.java; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class ExpandWildcardImportsStepTest extends MavenIntegrationHarness { + + @Test + void testExpandWildcardImports() throws Exception { + writePomWithJavaSteps(""); + + // Create the supporting classes needed for import resolution + 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"); + + // Create the annotation class that's used in the test + setFile("src/main/java/org/example/SomeAnnotation.java") + .toContent("package org.example;\n\npublic @interface SomeAnnotation {}\n"); + + // Set the main file to format + String path = "src/main/java/foo/bar/JavaClassWithWildcards.java"; + setFile(path).toResource("java/expandwildcardimports/JavaClassWithWildcardsUnformatted.test"); + + // Run spotless:apply + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Verify the wildcards were expanded + assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test"); + } +}