Skip to content

Commit 6afe380

Browse files
cushongoogle-java-format Team
authored andcommitted
Initial support for import module in google-java-format
#1213 PiperOrigin-RevId: 816705772
1 parent 00a908b commit 6afe380

File tree

6 files changed

+134
-20
lines changed

6 files changed

+134
-20
lines changed

core/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,24 @@ private String reorderImports() throws FormatterException {
122122
/**
123123
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
124124
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
125+
*
126+
* <p>Module imports are not allowed by Google Style, so we make an arbitrary choice about where
127+
* to include them if they are present.
125128
*/
126129
private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
127-
Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
130+
Comparator.comparing(Import::importType).thenComparing(Import::imported);
128131

129132
/**
130133
* A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
131134
* https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
132135
* in IntelliJ at
133136
* https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
137+
*
138+
* <p>Module imports are not mentioned by Android Style, so we make an arbitrary choice about
139+
* where to include them if they are present.
134140
*/
135141
private static final Comparator<Import> AOSP_IMPORT_COMPARATOR =
136-
Comparator.comparing(Import::isStatic, trueFirst())
142+
Comparator.comparing(Import::importType)
137143
.thenComparing(Import::isAndroid, trueFirst())
138144
.thenComparing(Import::isThirdParty, trueFirst())
139145
.thenComparing(Import::isJava, trueFirst())
@@ -144,15 +150,15 @@ private String reorderImports() throws FormatterException {
144150
* Import}s based on Google style.
145151
*/
146152
private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
147-
return prev.isStatic() && !curr.isStatic();
153+
return !prev.importType().equals(curr.importType());
148154
}
149155

150156
/**
151157
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
152158
* Import}s based on AOSP style.
153159
*/
154160
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
155-
if (prev.isStatic() && !curr.isStatic()) {
161+
if (!prev.importType().equals(curr.importType())) {
156162
return true;
157163
}
158164
// insert blank line between "com.android" from "com.anythingelse"
@@ -183,26 +189,32 @@ private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
183189
}
184190
}
185191

192+
enum ImportType {
193+
STATIC,
194+
MODULE,
195+
NORMAL
196+
}
197+
186198
/** An import statement. */
187199
class Import {
188200
private final String imported;
189-
private final boolean isStatic;
190201
private final String trailing;
202+
private final ImportType importType;
191203

192-
Import(String imported, String trailing, boolean isStatic) {
204+
Import(String imported, String trailing, ImportType importType) {
193205
this.imported = imported;
194206
this.trailing = trailing;
195-
this.isStatic = isStatic;
207+
this.importType = importType;
196208
}
197209

198210
/** The name being imported, for example {@code java.util.List}. */
199211
String imported() {
200212
return imported;
201213
}
202214

203-
/** True if this is {@code import static}. */
204-
boolean isStatic() {
205-
return isStatic;
215+
/** Returns the {@link ImportType}. */
216+
ImportType importType() {
217+
return importType;
206218
}
207219

208220
/** The top-level package of the import. */
@@ -245,8 +257,10 @@ public boolean isThirdParty() {
245257
public String toString() {
246258
StringBuilder sb = new StringBuilder();
247259
sb.append("import ");
248-
if (isStatic()) {
249-
sb.append("static ");
260+
switch (importType) {
261+
case STATIC -> sb.append("static ");
262+
case MODULE -> sb.append("module ");
263+
case NORMAL -> {}
250264
}
251265
sb.append(imported()).append(';');
252266
if (trailing().trim().isEmpty()) {
@@ -301,8 +315,13 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
301315
if (isSpaceToken(i)) {
302316
i++;
303317
}
304-
boolean isStatic = tokenAt(i).equals("static");
305-
if (isStatic) {
318+
ImportType importType =
319+
switch (tokenAt(i)) {
320+
case "static" -> ImportType.STATIC;
321+
case "module" -> ImportType.MODULE;
322+
default -> ImportType.NORMAL;
323+
};
324+
if (!importType.equals(ImportType.NORMAL)) {
306325
i++;
307326
if (isSpaceToken(i)) {
308327
i++;
@@ -347,7 +366,7 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
347366
// Extra semicolons are not allowed by the JLS but are accepted by javac.
348367
i++;
349368
}
350-
imports.add(new Import(importedName, trailing.toString(), isStatic));
369+
imports.add(new Import(importedName, trailing.toString(), importType));
351370
// Remember the position just after the import we just saw, before skipping blank lines.
352371
// If the next thing after the blank lines is not another import then we don't want to
353372
// include those blank lines in the text to be replaced.

core/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
158158
import com.sun.tools.javac.tree.TreeInfo;
159159
import com.sun.tools.javac.tree.TreeScanner;
160+
import java.lang.reflect.Method;
160161
import java.util.ArrayDeque;
161162
import java.util.ArrayList;
162163
import java.util.Collection;
@@ -1220,6 +1221,10 @@ public Void visitImport(ImportTree node, Void unused) {
12201221
sync(node);
12211222
token("import");
12221223
builder.space();
1224+
if (isModuleImport(node)) {
1225+
token("module");
1226+
builder.space();
1227+
}
12231228
if (node.isStatic()) {
12241229
token("static");
12251230
builder.space();
@@ -1231,6 +1236,27 @@ public Void visitImport(ImportTree node, Void unused) {
12311236
return null;
12321237
}
12331238

1239+
private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();
1240+
1241+
private static @Nullable Method getIsModuleMethod() {
1242+
try {
1243+
return ImportTree.class.getMethod("isModule");
1244+
} catch (NoSuchMethodException ignored) {
1245+
return null;
1246+
}
1247+
}
1248+
1249+
private static boolean isModuleImport(ImportTree importTree) {
1250+
if (IS_MODULE_METHOD == null) {
1251+
return false;
1252+
}
1253+
try {
1254+
return (boolean) IS_MODULE_METHOD.invoke(importTree);
1255+
} catch (ReflectiveOperationException e) {
1256+
throw new LinkageError(e.getMessage(), e);
1257+
}
1258+
}
1259+
12341260
private void checkForTypeAnnotation(ImportTree node) {
12351261
Name simpleName = getSimpleName(node);
12361262
Collection<String> wellKnownAnnotations = TYPE_ANNOTATIONS.get(simpleName.toString());

core/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.googlejavaformat.java;
1818

19+
import static com.google.common.base.Preconditions.checkArgument;
1920
import static java.lang.Math.max;
2021
import static java.nio.charset.StandardCharsets.UTF_8;
2122

@@ -67,6 +68,7 @@
6768
import javax.tools.JavaFileObject;
6869
import javax.tools.SimpleJavaFileObject;
6970
import javax.tools.StandardLocation;
71+
import org.jspecify.annotations.Nullable;
7072

7173
/**
7274
* Removes unused imports from a source file. Imports that are only used in javadoc are also
@@ -274,6 +276,9 @@ private static RangeMap<Integer, String> buildReplacements(
274276
Multimap<String, Range<Integer>> usedInJavadoc) {
275277
RangeMap<Integer, String> replacements = TreeRangeMap.create();
276278
for (JCTree importTree : unit.getImports()) {
279+
if (isModuleImport(importTree)) {
280+
continue;
281+
}
277282
String simpleName = getSimpleName(importTree);
278283
if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
279284
continue;
@@ -322,10 +327,42 @@ private static boolean isUnused(
322327
return true;
323328
}
324329

330+
private static final Method GET_QUALIFIED_IDENTIFIER_METHOD = getQualifiedIdentifierMethod();
331+
332+
private static @Nullable Method getQualifiedIdentifierMethod() {
333+
try {
334+
return JCImport.class.getMethod("getQualifiedIdentifier");
335+
} catch (NoSuchMethodException e) {
336+
return null;
337+
}
338+
}
339+
325340
private static JCFieldAccess getQualifiedIdentifier(JCTree importTree) {
341+
checkArgument(!isModuleImport(importTree));
326342
// Use reflection because the return type is JCTree in some versions and JCFieldAccess in others
327343
try {
328-
return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree);
344+
return (JCFieldAccess) GET_QUALIFIED_IDENTIFIER_METHOD.invoke(importTree);
345+
} catch (ReflectiveOperationException e) {
346+
throw new LinkageError(e.getMessage(), e);
347+
}
348+
}
349+
350+
private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();
351+
352+
private static @Nullable Method getIsModuleMethod() {
353+
try {
354+
return ImportTree.class.getMethod("isModule");
355+
} catch (NoSuchMethodException ignored) {
356+
return null;
357+
}
358+
}
359+
360+
private static boolean isModuleImport(JCTree importTree) {
361+
if (IS_MODULE_METHOD == null) {
362+
return false;
363+
}
364+
try {
365+
return (boolean) IS_MODULE_METHOD.invoke(importTree);
329366
} catch (ReflectiveOperationException e) {
330367
throw new LinkageError(e.getMessage(), e);
331368
}

core/src/test/java/com/google/googlejavaformat/java/FormatterIntegrationTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public class FormatterIntegrationTest {
5959
"I981",
6060
"I1020",
6161
"I1037")
62+
.putAll(25, "ModuleImport")
6263
.build();
6364

6465
@Parameters(name = "{index}: {0}")

core/src/test/java/com/google/googlejavaformat/java/ImportOrdererTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,23 @@ public static Collection<Object[]> parameters() {
820820
"",
821821
"public class Blim {}",
822822
},
823+
},
824+
{
825+
{
826+
"import module java.base;", //
827+
"import static java.lang.Math.min;",
828+
"import java.util.List;",
829+
"class Test {}",
830+
},
831+
{
832+
"import static java.lang.Math.min;", //
833+
"",
834+
"import module java.base;",
835+
"",
836+
"import java.util.List;",
837+
"",
838+
"class Test {}",
839+
},
823840
}
824841
};
825842
ImmutableList.Builder<Object[]> builder = ImmutableList.builder();

core/src/test/java/com/google/googlejavaformat/java/RemoveUnusedImportsTest.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818
import static com.google.googlejavaformat.java.RemoveUnusedImports.removeUnusedImports;
1919

20-
import com.google.common.base.Joiner;
2120
import com.google.common.collect.ImmutableList;
2221
import java.util.Collection;
2322
import org.junit.Test;
@@ -255,14 +254,29 @@ public static Collection<Object[]> parameters() {
255254
"interface Test { private static void foo() {} }",
256255
},
257256
},
257+
{
258+
{
259+
"import module java.base;", //
260+
"import java.lang.Foo;",
261+
"interface Test { private static void foo() {} }",
262+
},
263+
{
264+
"import module java.base;", //
265+
"interface Test { private static void foo() {} }",
266+
},
267+
},
258268
};
259269
ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
260270
for (String[][] inputAndOutput : inputsOutputs) {
261271
assertThat(inputAndOutput).hasLength(2);
262-
String[] input = inputAndOutput[0];
263-
String[] output = inputAndOutput[1];
272+
String input = String.join("\n", inputAndOutput[0]) + "\n";
273+
String output = String.join("\n", inputAndOutput[1]) + "\n";
274+
if (input.contains("import module") && Runtime.version().feature() < 25) {
275+
// TODO: cushon - remove this once the minimum supported JDK updates past 25
276+
continue;
277+
}
264278
String[] parameters = {
265-
Joiner.on('\n').join(input) + '\n', Joiner.on('\n').join(output) + '\n',
279+
input, output,
266280
};
267281
builder.add(parameters);
268282
}

0 commit comments

Comments
 (0)