Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,24 @@ private String reorderImports() throws FormatterException {
/**
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
*
* <p>Module imports are not allowed by Google Style, so we make an arbitrary choice about where
* to include them if they are present.
*/
private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
Comparator.comparing(Import::importType).thenComparing(Import::imported);

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

/**
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
* Import}s based on AOSP style.
*/
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
if (prev.isStatic() && !curr.isStatic()) {
if (!prev.importType().equals(curr.importType())) {
return true;
}
// insert blank line between "com.android" from "com.anythingelse"
Expand Down Expand Up @@ -183,26 +189,32 @@ private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
}
}

enum ImportType {
STATIC,
MODULE,
NORMAL
}

/** An import statement. */
class Import {
private final String imported;
private final boolean isStatic;
private final String trailing;
private final ImportType importType;

Import(String imported, String trailing, boolean isStatic) {
Import(String imported, String trailing, ImportType importType) {
this.imported = imported;
this.trailing = trailing;
this.isStatic = isStatic;
this.importType = importType;
}

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

/** True if this is {@code import static}. */
boolean isStatic() {
return isStatic;
/** Returns the {@link ImportType}. */
ImportType importType() {
return importType;
}

/** The top-level package of the import. */
Expand Down Expand Up @@ -245,8 +257,10 @@ public boolean isThirdParty() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("import ");
if (isStatic()) {
sb.append("static ");
switch (importType) {
case STATIC -> sb.append("static ");
case MODULE -> sb.append("module ");
case NORMAL -> {}
}
sb.append(imported()).append(';');
if (trailing().trim().isEmpty()) {
Expand Down Expand Up @@ -301,8 +315,13 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
if (isSpaceToken(i)) {
i++;
}
boolean isStatic = tokenAt(i).equals("static");
if (isStatic) {
ImportType importType =
switch (tokenAt(i)) {
case "static" -> ImportType.STATIC;
case "module" -> ImportType.MODULE;
default -> ImportType.NORMAL;
};
if (!importType.equals(ImportType.NORMAL)) {
i++;
if (isSpaceToken(i)) {
i++;
Expand Down Expand Up @@ -347,7 +366,7 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
// Extra semicolons are not allowed by the JLS but are accepted by javac.
i++;
}
imports.add(new Import(importedName, trailing.toString(), isStatic));
imports.add(new Import(importedName, trailing.toString(), importType));
// Remember the position just after the import we just saw, before skipping blank lines.
// If the next thing after the blank lines is not another import then we don't want to
// include those blank lines in the text to be replaced.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -1220,6 +1221,10 @@ public Void visitImport(ImportTree node, Void unused) {
sync(node);
token("import");
builder.space();
if (isModuleImport(node)) {
token("module");
builder.space();
}
if (node.isStatic()) {
token("static");
builder.space();
Expand All @@ -1231,6 +1236,27 @@ public Void visitImport(ImportTree node, Void unused) {
return null;
}

private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();

private static @Nullable Method getIsModuleMethod() {
try {
return ImportTree.class.getMethod("isModule");
} catch (NoSuchMethodException ignored) {
return null;
}
}

private static boolean isModuleImport(ImportTree importTree) {
if (IS_MODULE_METHOD == null) {
return false;
}
try {
return (boolean) IS_MODULE_METHOD.invoke(importTree);
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
}

private void checkForTypeAnnotation(ImportTree node) {
Name simpleName = getSimpleName(node);
Collection<String> wellKnownAnnotations = TYPE_ANNOTATIONS.get(simpleName.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.googlejavaformat.java;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.Math.max;
import static java.nio.charset.StandardCharsets.UTF_8;

Expand Down Expand Up @@ -67,6 +68,7 @@
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardLocation;
import org.jspecify.annotations.Nullable;

/**
* Removes unused imports from a source file. Imports that are only used in javadoc are also
Expand Down Expand Up @@ -274,6 +276,9 @@ private static RangeMap<Integer, String> buildReplacements(
Multimap<String, Range<Integer>> usedInJavadoc) {
RangeMap<Integer, String> replacements = TreeRangeMap.create();
for (JCTree importTree : unit.getImports()) {
if (isModuleImport(importTree)) {
continue;
}
String simpleName = getSimpleName(importTree);
if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
continue;
Expand Down Expand Up @@ -322,10 +327,42 @@ private static boolean isUnused(
return true;
}

private static final Method GET_QUALIFIED_IDENTIFIER_METHOD = getQualifiedIdentifierMethod();

private static @Nullable Method getQualifiedIdentifierMethod() {
try {
return JCImport.class.getMethod("getQualifiedIdentifier");
} catch (NoSuchMethodException e) {
return null;
}
}

private static JCFieldAccess getQualifiedIdentifier(JCTree importTree) {
checkArgument(!isModuleImport(importTree));
// Use reflection because the return type is JCTree in some versions and JCFieldAccess in others
try {
return (JCFieldAccess) JCImport.class.getMethod("getQualifiedIdentifier").invoke(importTree);
return (JCFieldAccess) GET_QUALIFIED_IDENTIFIER_METHOD.invoke(importTree);
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
}

private static final @Nullable Method IS_MODULE_METHOD = getIsModuleMethod();

private static @Nullable Method getIsModuleMethod() {
try {
return ImportTree.class.getMethod("isModule");
} catch (NoSuchMethodException ignored) {
return null;
}
}

private static boolean isModuleImport(JCTree importTree) {
if (IS_MODULE_METHOD == null) {
return false;
}
try {
return (boolean) IS_MODULE_METHOD.invoke(importTree);
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public class FormatterIntegrationTest {
"I981",
"I1020",
"I1037")
.putAll(25, "ModuleImport")
.build();

@Parameters(name = "{index}: {0}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,23 @@ public static Collection<Object[]> parameters() {
"",
"public class Blim {}",
},
},
{
{
"import module java.base;", //
"import static java.lang.Math.min;",
"import java.util.List;",
"class Test {}",
},
{
"import static java.lang.Math.min;", //
"",
"import module java.base;",
"",
"import java.util.List;",
"",
"class Test {}",
},
}
};
ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.googlejavaformat.java.RemoveUnusedImports.removeUnusedImports;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import org.junit.Test;
Expand Down Expand Up @@ -255,14 +254,29 @@ public static Collection<Object[]> parameters() {
"interface Test { private static void foo() {} }",
},
},
{
{
"import module java.base;", //
"import java.lang.Foo;",
"interface Test { private static void foo() {} }",
},
{
"import module java.base;", //
"interface Test { private static void foo() {} }",
},
},
};
ImmutableList.Builder<Object[]> builder = ImmutableList.builder();
for (String[][] inputAndOutput : inputsOutputs) {
assertThat(inputAndOutput).hasLength(2);
String[] input = inputAndOutput[0];
String[] output = inputAndOutput[1];
String input = String.join("\n", inputAndOutput[0]) + "\n";
String output = String.join("\n", inputAndOutput[1]) + "\n";
if (input.contains("import module") && Runtime.version().feature() < 25) {
// TODO: cushon - remove this once the minimum supported JDK updates past 25
continue;
}
String[] parameters = {
Joiner.on('\n').join(input) + '\n', Joiner.on('\n').join(output) + '\n',
input, output,
};
builder.add(parameters);
}
Expand Down
Loading