Skip to content

Commit 05d8954

Browse files
authored
Feature maven expand wildcard import (#2930 fixes #2829)
2 parents 3b6401d + 0267bed commit 05d8954

9 files changed

Lines changed: 162 additions & 5 deletions

File tree

plugin-maven/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
77
- `<scalafmt>` now reads the version from the `version` field in the scalafmt config file when no `<version>` is explicitly set, falling back to the built-in default only if neither is available. ([#2922](https://github.com/diffplug/spotless/pull/2922))
88
- Add `<toml>` format type with `<versionCatalog>` step for formatting and sorting Gradle version catalog files. ([#2916](https://github.com/diffplug/spotless/issues/2916))
99
- Add `<javaparserVersion>` option to `<cleanthat>`, allowing users to override the JavaParser version pulled in transitively by Cleanthat. ([#2903](https://github.com/diffplug/spotless/pull/2903))
10+
- Add a `expandWildcardImports` API for java ([#2829](https://github.com/diffplug/spotless/pull/2930))
1011
### Fixed
1112
- Preserve case of JDBI named bind params that collide with SQL keywords (e.g. `:limit`, `:offset`) in the DBeaver SQL formatter. ([#2899](https://github.com/diffplug/spotless/pull/2899))
1213
- The `-Dspotless.ratchetFrom=...` user property now takes priority over `<ratchetFrom>` configured in the plugin or in individual formatters, instead of being overridden by them. ([#2896](https://github.com/diffplug/spotless/pull/2896), fixes [#2842](https://github.com/diffplug/spotless/issues/2842))

plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Collections;
2828
import java.util.HashSet;
2929
import java.util.LinkedHashMap;
30+
import java.util.LinkedHashSet;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Objects;
@@ -412,7 +413,36 @@ private FormatterConfig getFormatterConfig() {
412413
final Optional<String> userRatchetFrom = Optional.ofNullable((String) mavenSession.getUserProperties().get("ratchetFrom"));
413414
final Optional<String> optionalRatchetFrom = Optional.ofNullable(this.ratchetFrom)
414415
.filter(ratchet -> !RATCHETFROM_NONE.equals(ratchet));
415-
return new FormatterConfig(baseDir, encoding, lineEndings, userRatchetFrom, optionalRatchetFrom, provisioner, p2Provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory), lintSuppressions);
416+
Optional<Set<File>> projectClasspath = computeTypeSolverClasspath(resolver);
417+
return new FormatterConfig(baseDir, encoding, lineEndings, userRatchetFrom, optionalRatchetFrom, provisioner, p2Provisioner, fileLocator, formatterStepFactories, Optional.ofNullable(setLicenseHeaderYearsFromGitHistory), lintSuppressions, projectClasspath);
418+
}
419+
420+
private Optional<Set<File>> computeTypeSolverClasspath(ArtifactResolver resolver) {
421+
Set<File> classpath = new LinkedHashSet<>();
422+
423+
// Add source roots (directories containing .java files for JavaParserTypeSolver)
424+
for (String sourceRoot : project.getCompileSourceRoots()) {
425+
File dir = new File(sourceRoot);
426+
if (dir.exists()) {
427+
classpath.add(dir);
428+
}
429+
}
430+
for (String sourceRoot : project.getTestCompileSourceRoots()) {
431+
File dir = new File(sourceRoot);
432+
if (dir.exists()) {
433+
classpath.add(dir);
434+
}
435+
}
436+
437+
// Resolve dependency JARs via RepositorySystem (for JarTypeSolver)
438+
try {
439+
Set<File> dependencyJars = resolver.resolveProjectDependencies(project, repositories);
440+
classpath.addAll(dependencyJars);
441+
} catch (Exception e) {
442+
getLog().warn("Could not resolve project dependencies for expandWildcardImports: " + e.getMessage());
443+
}
444+
445+
return Optional.of(classpath);
416446
}
417447

418448
private FileLocator getFileLocator() {

plugin-maven/src/main/java/com/diffplug/spotless/maven/ArtifactResolver.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2025 DiffPlug
2+
* Copyright 2016-2026 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,11 +21,13 @@
2121
import java.io.File;
2222
import java.util.ArrayList;
2323
import java.util.Collection;
24+
import java.util.Collections;
2425
import java.util.List;
2526
import java.util.Objects;
2627
import java.util.Set;
2728

2829
import org.apache.maven.plugin.logging.Log;
30+
import org.apache.maven.project.MavenProject;
2931
import org.eclipse.aether.RepositorySystem;
3032
import org.eclipse.aether.RepositorySystemSession;
3133
import org.eclipse.aether.artifact.Artifact;
@@ -89,6 +91,34 @@ private DependencyResult resolveDependencies(DependencyRequest dependencyRequest
8991
}
9092
}
9193

94+
/**
95+
* Resolves all dependencies (all scopes) of the given Maven project using the
96+
* specified artifact repositories. Returns the set of resolved JAR files.
97+
*/
98+
public Set<File> resolveProjectDependencies(MavenProject project, List<RemoteRepository> artifactRepositories) {
99+
List<Dependency> dependencies = project.getDependencies().stream()
100+
.map(d -> new Dependency(
101+
new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion()),
102+
d.getScope()))
103+
.collect(toList());
104+
105+
if (dependencies.isEmpty()) {
106+
return Collections.emptySet();
107+
}
108+
109+
CollectRequest collectRequest = new CollectRequest(dependencies, null, artifactRepositories);
110+
DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null);
111+
DependencyResult dependencyResult = resolveDependencies(dependencyRequest);
112+
113+
return dependencyResult.getArtifactResults()
114+
.stream()
115+
.map(ArtifactResult::getArtifact)
116+
.filter(Objects::nonNull)
117+
.map(Artifact::getFile)
118+
.filter(Objects::nonNull)
119+
.collect(toSet());
120+
}
121+
92122
private void logResolved(ArtifactResult artifactResult) {
93123
if (log.isDebugEnabled()) {
94124
log.debug("Resolved artifact: " + artifactResult);

plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterConfig.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.io.File;
2121
import java.util.List;
2222
import java.util.Optional;
23+
import java.util.Set;
2324

2425
import com.diffplug.spotless.LineEnding;
2526
import com.diffplug.spotless.LintSuppression;
@@ -38,9 +39,10 @@ public class FormatterConfig {
3839
private final List<FormatterStepFactory> globalStepFactories;
3940
private final Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory;
4041
private final List<LintSuppression> lintSuppressions;
42+
private final Optional<Set<File>> projectClasspath;
4143

4244
public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Optional<String> userRatchetFrom, Optional<String> ratchetFrom, Provisioner provisioner,
43-
P2Provisioner p2Provisioner, FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory, List<LintSuppression> lintSuppressions) {
45+
P2Provisioner p2Provisioner, FileLocator fileLocator, List<FormatterStepFactory> globalStepFactories, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory, List<LintSuppression> lintSuppressions, Optional<Set<File>> projectClasspath) {
4446
this.encoding = encoding;
4547
this.lineEndings = lineEndings;
4648
this.userRatchetFrom = userRatchetFrom;
@@ -51,6 +53,7 @@ public FormatterConfig(File baseDir, String encoding, LineEnding lineEndings, Op
5153
this.globalStepFactories = globalStepFactories;
5254
this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory;
5355
this.lintSuppressions = lintSuppressions;
56+
this.projectClasspath = projectClasspath;
5457
}
5558

5659
public String getEncoding() {
@@ -92,4 +95,8 @@ public FileLocator getFileLocator() {
9295
public List<LintSuppression> getLintSuppressions() {
9396
return unmodifiableList(lintSuppressions);
9497
}
98+
99+
public Optional<Set<File>> getProjectClasspath() {
100+
return projectClasspath;
101+
}
95102
}

plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private Optional<String> optionalRatchetFrom() {
187187
}
188188

189189
private FormatterStepConfig stepConfig(Charset encoding, FormatterConfig config) {
190-
return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), ratchetFrom(config), config.getProvisioner(), config.getP2Provisioner(), config.getFileLocator(), config.getSpotlessSetLicenseHeaderYearsFromGitHistory());
190+
return new FormatterStepConfig(encoding, licenseHeaderDelimiter(), ratchetFrom(config), config.getProvisioner(), config.getP2Provisioner(), config.getFileLocator(), config.getSpotlessSetLicenseHeaderYearsFromGitHistory(), config.getProjectClasspath());
191191
}
192192

193193
private static List<FormatterStepFactory> gatherStepFactories(List<FormatterStepFactory> allGlobal, List<FormatterStepFactory> allConfigured) {

plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterStepConfig.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*/
1616
package com.diffplug.spotless.maven;
1717

18+
import java.io.File;
1819
import java.nio.charset.Charset;
1920
import java.util.Optional;
21+
import java.util.Set;
2022

2123
import com.diffplug.spotless.Provisioner;
2224
import com.diffplug.spotless.extra.P2Provisioner;
@@ -30,15 +32,17 @@ public class FormatterStepConfig {
3032
private final P2Provisioner p2Provisioner;
3133
private final FileLocator fileLocator;
3234
private final Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory;
35+
private final Optional<Set<File>> projectClasspath;
3336

34-
public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional<String> ratchetFrom, Provisioner provisioner, P2Provisioner p2Provisioner, FileLocator fileLocator, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory) {
37+
public FormatterStepConfig(Charset encoding, String licenseHeaderDelimiter, Optional<String> ratchetFrom, Provisioner provisioner, P2Provisioner p2Provisioner, FileLocator fileLocator, Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory, Optional<Set<File>> projectClasspath) {
3538
this.encoding = encoding;
3639
this.licenseHeaderDelimiter = licenseHeaderDelimiter;
3740
this.ratchetFrom = ratchetFrom;
3841
this.provisioner = provisioner;
3942
this.p2Provisioner = p2Provisioner;
4043
this.fileLocator = fileLocator;
4144
this.spotlessSetLicenseHeaderYearsFromGitHistory = spotlessSetLicenseHeaderYearsFromGitHistory;
45+
this.projectClasspath = projectClasspath;
4246
}
4347

4448
public Charset getEncoding() {
@@ -68,4 +72,8 @@ public FileLocator getFileLocator() {
6872
public Optional<String> spotlessSetLicenseHeaderYearsFromGitHistory() {
6973
return spotlessSetLicenseHeaderYearsFromGitHistory;
7074
}
75+
76+
public Optional<Set<File>> getProjectClasspath() {
77+
return projectClasspath;
78+
}
7179
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025-2026 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.maven.java;
17+
18+
import java.io.File;
19+
import java.util.Collections;
20+
import java.util.Set;
21+
22+
import com.diffplug.spotless.FormatterStep;
23+
import com.diffplug.spotless.java.ExpandWildcardImportsStep;
24+
import com.diffplug.spotless.maven.FormatterStepConfig;
25+
import com.diffplug.spotless.maven.FormatterStepFactory;
26+
27+
public class ExpandWildcardImports implements FormatterStepFactory {
28+
@Override
29+
public FormatterStep newFormatterStep(FormatterStepConfig config) {
30+
Set<File> classpath = config.getProjectClasspath().orElse(Collections.emptySet());
31+
return ExpandWildcardImportsStep.create(classpath, config.getProvisioner());
32+
}
33+
}

plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public void addForbidWildcardImports(ForbidWildcardImports forbidWildcardImports
7979
addStepFactory(forbidWildcardImports);
8080
}
8181

82+
public void addExpandWildcardImports(ExpandWildcardImports expandWildcardImports) {
83+
addStepFactory(expandWildcardImports);
84+
}
85+
8286
public void addForbidModuleImports(ForbidModuleImports forbidModuleImports) {
8387
addStepFactory(forbidModuleImports);
8488
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025-2026 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.maven.java;
17+
18+
import org.junit.jupiter.api.Test;
19+
20+
import com.diffplug.spotless.maven.MavenIntegrationHarness;
21+
22+
class ExpandWildcardImportsStepTest extends MavenIntegrationHarness {
23+
24+
@Test
25+
void expandWildcardImports() throws Exception {
26+
writePomWithJavaSteps("<expandWildcardImports/>");
27+
28+
// Create supporting classes in source roots so JavaParserTypeSolver can resolve them
29+
setFile("src/main/java/foo/bar/AnotherClassInSamePackage.java")
30+
.toResource("java/expandwildcardimports/AnotherClassInSamePackage.test");
31+
setFile("src/main/java/foo/bar/baz/AnotherImportedClass.java")
32+
.toResource("java/expandwildcardimports/AnotherImportedClass.test");
33+
// Source for the annotation used in the test (resolves via source root, not JAR)
34+
setFile("src/main/java/org/example/SomeAnnotation.java")
35+
.toContent("package org.example;\n\npublic @interface SomeAnnotation {}\n");
36+
37+
String path = "src/main/java/foo/bar/JavaClassWithWildcards.java";
38+
setFile(path).toResource("java/expandwildcardimports/JavaClassWithWildcardsUnformatted.test");
39+
40+
mavenRunner().withArguments("spotless:apply").runNoError();
41+
42+
assertFile(path).sameAsResource("java/expandwildcardimports/JavaClassWithWildcardsFormatted.test");
43+
}
44+
}

0 commit comments

Comments
 (0)