Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Fixed
- `shortenFullyQualifiedTypes` leaves a qualified name alone when the enclosing type extends or implements a type that is not in the same file, so an inherited member type cannot take the simple name. ([#3117](https://github.com/diffplug/spotless/issues/3117))
- `expandWildcardImports` no longer drops the import of a type that is only used to qualify a static member (such as `Collections.sort(list)` or `TimeUnit.SECONDS`), or the static import of a field or enum constant (such as `PI` from `import static java.lang.Math.*`), which produced code that did not compile. ([#2833](https://github.com/diffplug/spotless/issues/2833))

## [4.10.3] - 2026-09-25

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@
import com.github.javaparser.ast.ImportDeclaration;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.FieldAccessExpr;
import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.MethodReferenceExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
Expand All @@ -47,6 +50,7 @@
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedAnnotationDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
Expand Down Expand Up @@ -187,6 +191,65 @@ public void visit(final MethodCallExpr n, final Map<ImportDeclaration, Set<Impor
super.visit(n, importMap);
}

@Override
public void visit(final NameExpr n, final Map<ImportDeclaration, Set<ImportDeclaration>> importMap) {
Optional<ResolvedValueDeclaration> value = resolveValue(n);
if (value.isPresent()) {
// static imports of fields and enum constants, e.g. `PI` or `SECONDS`
staticMemberName(value.get()).ifPresent(name -> matchTypeName(importMap, name, true));
} else if (isQualifier(n)) {
// a type that is only used to qualify a member, e.g. `Collections.sort(list)`
resolveTypeName(n).ifPresent(name -> matchTypeName(importMap, name, false));
}
super.visit(n, importMap);
}

private static Optional<ResolvedValueDeclaration> resolveValue(NameExpr n) {
try {
return Optional.of(n.resolve());
} catch (RuntimeException ex) {
// not a resolvable variable, parameter, field or enum constant; possibly the name of a type or package
return Optional.empty();
}
}

private static Optional<String> staticMemberName(ResolvedValueDeclaration value) {
if (value.isField() && value.asField().isStatic()) {
return Optional.of(value.asField().declaringType().getQualifiedName() + "." + value.getName());
}
if (value.isEnumConstant()) {
ResolvedType enumType = value.getType();
if (enumType.isReferenceType()) {
return Optional.of(enumType.asReferenceType().getQualifiedName() + "." + value.getName());
}
}
return Optional.empty();
}

private static boolean isQualifier(NameExpr n) {
Node parent = n.getParentNode().orElse(null);
if (parent instanceof MethodCallExpr call) {
return call.getScope().filter(scope -> scope == n).isPresent();
}
if (parent instanceof FieldAccessExpr access) {
return access.getScope() == n;
}
if (parent instanceof MethodReferenceExpr reference) {
return reference.getScope() == n;
}
return false;
}

private static Optional<String> resolveTypeName(NameExpr n) {
try {
ResolvedType type = n.calculateResolvedType();
return type.isReferenceType() ? Optional.of(type.asReferenceType().getQualifiedName()) : Optional.empty();
} catch (RuntimeException ex) {
// e.g. the first segment of a fully qualified name, which needs no import
return Optional.empty();
}
}

private static <T extends Node, R> R wrapUnsolvedSymbolException(T node, Function<T, R> func) {
try {
return func.apply(node);
Expand Down
1 change: 1 addition & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Fixed
- `shortenFullyQualifiedTypes` leaves a qualified name alone when the enclosing type extends or implements a type that is not in the same file, so an inherited member type cannot take the simple name. ([#3117](https://github.com/diffplug/spotless/issues/3117))
- `expandWildcardImports` no longer drops the import of a type that is only used to qualify a static member (such as `Collections.sort(list)` or `TimeUnit.SECONDS`), or the static import of a field or enum constant (such as `PI` from `import static java.lang.Math.*`), which produced code that did not compile. ([#2833](https://github.com/diffplug/spotless/issues/2833))

## [8.10.3] - 2026-09-25
### Changes
Expand Down
1 change: 1 addition & 0 deletions plugin-maven/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

### Fixed
- `shortenFullyQualifiedTypes` leaves a qualified name alone when the enclosing type extends or implements a type that is not in the same file, so an inherited member type cannot take the simple name. ([#3117](https://github.com/diffplug/spotless/issues/3117))
- `expandWildcardImports` no longer drops the import of a type that is only used to qualify a static member (such as `Collections.sort(list)` or `TimeUnit.SECONDS`), or the static import of a field or enum constant (such as `PI` from `import static java.lang.Math.*`), which produced code that did not compile. ([#2833](https://github.com/diffplug/spotless/issues/2833))

### Fixed
Migrate Plexus component injection to JSR-330 and Maven parameter. ([#3122](https://github.com/diffplug/spotless/pull/3122))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test;

import java.util.List;

public class Test {
private List<String> items;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test;

import java.util.*;

public class Test {
private List<String> items;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test;

import java.io.StringReader;
import org.xml.sax.InputSource;

public class Test {
InputSource inputSource(String value) {
return new InputSource(new StringReader(value));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package test;

import java.io.*;
import org.xml.sax.InputSource;

public class Test {
InputSource inputSource(String value) {
return new InputSource(new StringReader(value));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package test;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Test {
List<String> sorted(List<String> values) {
Collections.sort(values);
var iterator = values.iterator();
values.forEach(value -> System.out.println(value.length()));
return iterator.hasNext() ? values : Collections.emptyList();
}

long timeoutMillis() {
return TimeUnit.SECONDS.toMillis(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package test;

import java.util.*;
import java.util.concurrent.*;

public class Test {
List<String> sorted(List<String> values) {
Collections.sort(values);
var iterator = values.iterator();
values.forEach(value -> System.out.println(value.length()));
return iterator.hasNext() ? values : Collections.emptyList();
}

long timeoutMillis() {
return TimeUnit.SECONDS.toMillis(1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test;

import static java.lang.Math.PI;
import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.SECONDS;

public class Test {
double circleArea(double radius) {
return PI * radius * radius;
}

long timeoutMillis() {
return SECONDS.toMillis(max(1, 2));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test;

import static java.lang.Math.*;
import static java.util.concurrent.TimeUnit.*;

public class Test {
double circleArea(double radius) {
return PI * radius * radius;
}

long timeoutMillis() {
return SECONDS.toMillis(max(1, 2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,61 +126,29 @@ void expandWildcardImports_emptyClasspath() throws Exception {
FormatterStep step = ExpandWildcardImportsStep.create(Collections.emptySet(), TestProvisioner.mavenCentral());

// Even with empty classpath, Java standard library imports should still be expanded
String simpleCode = """
package test;

import java.util.*;

public class Test {
private List<String> items;
}
""";

String expectedOutput = """
package test;

import java.util.List;

public class Test {
private List<String> items;
}
""";

// The step should still expand java.util.* to java.util.List
StepHarness.forStep(step).test(simpleCode, expectedOutput);
StepHarness.forStep(step).testResource("java/expandwildcardimports/JavaUtilWildcardUnformatted.test", "java/expandwildcardimports/JavaUtilWildcardFormatted.test");
}

@Test
void expandWildcardImports_resolvesJdkXmlTypes() throws Exception {
FormatterStep step = ExpandWildcardImportsStep.create(Collections.emptySet(), TestProvisioner.mavenCentral());

String simpleCode = """
package test;

import java.io.*;
import org.xml.sax.InputSource;

public class Test {
InputSource inputSource(String value) {
return new InputSource(new StringReader(value));
}
}
""";
StepHarness.forStep(step).testResource("java/expandwildcardimports/JdkXmlTypesUnformatted.test", "java/expandwildcardimports/JdkXmlTypesFormatted.test");
}

String expectedOutput = """
package test;
@Test
void expandWildcardImports_keepsTypesOnlyUsedAsQualifiers() throws Exception {
FormatterStep step = ExpandWildcardImportsStep.create(Collections.emptySet(), TestProvisioner.mavenCentral());

import java.io.StringReader;
import org.xml.sax.InputSource;
StepHarness.forStep(step).testResource("java/expandwildcardimports/QualifierTypesUnformatted.test", "java/expandwildcardimports/QualifierTypesFormatted.test");
}

public class Test {
InputSource inputSource(String value) {
return new InputSource(new StringReader(value));
}
}
""";
@Test
void expandWildcardImports_keepsStaticFieldsAndEnumConstants() throws Exception {
FormatterStep step = ExpandWildcardImportsStep.create(Collections.emptySet(), TestProvisioner.mavenCentral());

StepHarness.forStep(step).test(simpleCode, expectedOutput);
StepHarness.forStep(step).testResource("java/expandwildcardimports/StaticFieldsAndEnumConstantsUnformatted.test", "java/expandwildcardimports/StaticFieldsAndEnumConstantsFormatted.test");
}

@Test
Expand Down