diff --git a/lib/src/main/java/com/diffplug/spotless/java/ExpandWildcardImportsStep.java b/lib/src/main/java/com/diffplug/spotless/java/ExpandWildcardImportsStep.java new file mode 100644 index 0000000000..9549baf61a --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/java/ExpandWildcardImportsStep.java @@ -0,0 +1,87 @@ +/* + * Copyright 2025-2026 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.java; + +import java.io.File; +import java.io.Serial; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +public final class ExpandWildcardImportsStep implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Java-Parser; maybe you set an incompatible version?"; + private static final String MAVEN_COORDINATES = "com.github.javaparser:javaparser-symbol-solver-core"; + public static final String DEFAULT_VERSION = "3.27.1"; + + private final Collection typeSolverClasspath; + private final JarState.Promised jarState; + + private ExpandWildcardImportsStep(Collection typeSolverClasspath, JarState.Promised jarState) { + this.typeSolverClasspath = typeSolverClasspath; + this.jarState = jarState; + } + + public static FormatterStep create(Set typeSolverClasspath, Provisioner provisioner) { + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.create("expandwildcardimports", + new ExpandWildcardImportsStep(typeSolverClasspath, + JarState.promise(() -> JarState.from(MAVEN_COORDINATES + ":" + DEFAULT_VERSION, provisioner))), + ExpandWildcardImportsStep::equalityState, + State::toFormatter); + } + + private State equalityState() { + return new State(typeSolverClasspath, jarState.get()); + } + + private static class State implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + private final Collection typeSolverClasspath; + private final JarState jarState; + + public State(Collection typeSolverClasspath, JarState jarState) { + this.typeSolverClasspath = typeSolverClasspath; + this.jarState = jarState; + } + + FormatterFunc toFormatter() { + try { + return (FormatterFunc) jarState + .getClassLoader() + .loadClass("com.diffplug.spotless.glue.javaparser.ExpandWildcardsFormatterFunc") + .getConstructor(Collection.class) + .newInstance(typeSolverClasspath); + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException + | InstantiationException | IllegalAccessException | NoClassDefFoundError cause) { + throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); + } + } + + } + +} 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..2345ee61b9 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, Optional.ofNullable(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..619466d073 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 @@ -20,6 +20,9 @@ import java.io.File; import java.util.List; import java.util.Optional; +import java.util.Set; + +import org.apache.maven.project.MavenProject; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.LintSuppression; @@ -35,9 +38,10 @@ public class FormatterConfig { private final List globalStepFactories; private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; private final List lintSuppressions; + private final Optional mavenProject; 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, Optional mavenProject) { this.encoding = encoding; this.lineEndings = lineEndings; this.ratchetFrom = ratchetFrom; @@ -46,6 +50,7 @@ public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Op this.globalStepFactories = globalStepFactories; this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; this.lintSuppressions = lintSuppressions; + this.mavenProject = mavenProject; } public String getEncoding() { @@ -79,4 +84,8 @@ public FileLocator getFileLocator() { public List getLintSuppressions() { return unmodifiableList(lintSuppressions); } + + public Optional getMavenProject() { + return mavenProject; + } } 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..306f5db8cf 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 @@ -184,8 +184,12 @@ Optional ratchetFrom(FormatterConfig config) { } } + protected Optional> getProjectClasspath(FormatterConfig config) { + return Optional.empty(); + } + 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(), getProjectClasspath(config)); } 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..2c93d33586 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 @@ -15,8 +15,10 @@ */ package com.diffplug.spotless.maven; +import java.io.File; import java.nio.charset.Charset; import java.util.Optional; +import java.util.Set; import com.diffplug.spotless.Provisioner; @@ -28,14 +30,16 @@ public class FormatterStepConfig { private final Provisioner provisioner; private final FileLocator fileLocator; private final Optional spotlessSetLicenseHeaderYearsFromGitHistory; + private final Optional> projectClasspath; - 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, Optional> projectClasspath) { this.encoding = encoding; this.licenseHeaderDelimiter = licenseHeaderDelimiter; this.ratchetFrom = ratchetFrom; this.provisioner = provisioner; this.fileLocator = fileLocator; this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory; + this.projectClasspath = projectClasspath; } public Charset getEncoding() { @@ -61,4 +65,8 @@ public FileLocator getFileLocator() { public Optional spotlessSetLicenseHeaderYearsFromGitHistory() { return spotlessSetLicenseHeaderYearsFromGitHistory; } + + public Optional> getProjectClasspath() { + return projectClasspath; + } } 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..5c0170da67 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ExpandWildcardImports.java @@ -0,0 +1,33 @@ +/* + * 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.Collections; +import java.util.Set; + +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) { + Set classpath = config.getProjectClasspath().orElse(Collections.emptySet()); + return ExpandWildcardImportsStep.create(classpath, 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..4ab56b68bf 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 @@ -20,13 +20,18 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.model.Build; import org.apache.maven.project.MavenProject; import com.diffplug.spotless.generic.LicenseHeaderStep; +import com.diffplug.spotless.maven.FormatterConfig; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -80,6 +85,10 @@ public void addForbidWildcardImports(ForbidWildcardImports forbidWildcardImports addStepFactory(forbidWildcardImports); } + public void addExpandWildcardImports(ExpandWildcardImports expandWildcardImports) { + addStepFactory(expandWildcardImports); + } + public void addForbidModuleImports(ForbidModuleImports forbidModuleImports) { addStepFactory(forbidModuleImports); } @@ -92,6 +101,27 @@ public void addCleanthat(CleanthatJava cleanthat) { addStepFactory(cleanthat); } + @Override + protected Optional> getProjectClasspath(FormatterConfig config) { + return config.getMavenProject().map(project -> { + try { + Set classpath = new HashSet<>(); + // Add compile classpath + for (String element : project.getCompileClasspathElements()) { + classpath.add(new File(element)); + } + // Add test classpath + for (String element : project.getTestClasspathElements()) { + classpath.add(new File(element)); + } + return classpath; + } catch (DependencyResolutionRequiredException e) { + // If we can't resolve dependencies, return empty set + return Collections.emptySet(); + } + }); + } + private static String fileMask(Path path) { String dir = path.toString(); if (!dir.endsWith(File.separator)) {