Skip to content

Commit d40d0d6

Browse files
eaftanronshapiro
authored andcommitted
Implement AOSP import ordering.
Fixes google#319 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=246145469
1 parent 1ab8de2 commit d40d0d6

File tree

6 files changed

+987
-595
lines changed

6 files changed

+987
-595
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private String fixImports(String input) throws FormatterException {
5555
input = RemoveUnusedImports.removeUnusedImports(input);
5656
}
5757
if (parameters.sortImports()) {
58-
input = ImportOrderer.reorderImports(input);
58+
input = ImportOrderer.reorderImports(input, options.style());
5959
}
6060
return input;
6161
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ public String formatSource(String input) throws FormatterException {
216216
* Google Java Style Guide - 3.3.3 Import ordering and spacing</a>
217217
*/
218218
public String formatSourceAndFixImports(String input) throws FormatterException {
219-
input = ImportOrderer.reorderImports(input);
219+
input = ImportOrderer.reorderImports(input, options.style());
220220
input = RemoveUnusedImports.removeUnusedImports(input);
221221
String formatted = formatSource(input);
222222
formatted = StringWrapper.wrap(formatted);

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

Lines changed: 179 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,94 @@
1414
package com.google.googlejavaformat.java;
1515

1616
import static com.google.common.collect.Iterables.getLast;
17+
import static com.google.common.primitives.Booleans.trueFirst;
1718

1819
import com.google.common.base.CharMatcher;
1920
import com.google.common.base.Preconditions;
21+
import com.google.common.base.Splitter;
2022
import com.google.common.collect.ImmutableList;
2123
import com.google.common.collect.ImmutableSet;
2224
import com.google.common.collect.ImmutableSortedSet;
2325
import com.google.googlejavaformat.Newlines;
26+
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
2427
import com.google.googlejavaformat.java.JavaInput.Tok;
2528
import java.util.ArrayList;
29+
import java.util.Comparator;
2630
import java.util.List;
2731
import java.util.Optional;
32+
import java.util.function.BiFunction;
33+
import java.util.stream.Stream;
2834
import org.openjdk.tools.javac.parser.Tokens.TokenKind;
2935

3036
/** Orders imports in Java source code. */
3137
public class ImportOrderer {
38+
39+
private static final Splitter DOT_SPLITTER = Splitter.on('.');
40+
3241
/**
3342
* Reorder the inputs in {@code text}, a complete Java program. On success, another complete Java
3443
* program is returned, which is the same as the original except the imports are in order.
3544
*
3645
* @throws FormatterException if the input could not be parsed.
3746
*/
38-
public static String reorderImports(String text) throws FormatterException {
47+
public static String reorderImports(String text, Style style) throws FormatterException {
3948
ImmutableList<Tok> toks = JavaInput.buildToks(text, CLASS_START);
40-
return new ImportOrderer(text, toks).reorderImports();
49+
return new ImportOrderer(text, toks, style).reorderImports();
50+
}
51+
52+
/**
53+
* Reorder the inputs in {@code text}, a complete Java program, in Google style. On success,
54+
* another complete Java program is returned, which is the same as the original except the imports
55+
* are in order.
56+
*
57+
* @deprecated Use {@link #reorderImports(String, Style)} instead
58+
* @throws FormatterException if the input could not be parsed.
59+
*/
60+
@Deprecated
61+
public static String reorderImports(String text) throws FormatterException {
62+
return reorderImports(text, Style.GOOGLE);
63+
}
64+
65+
private String reorderImports() throws FormatterException {
66+
int firstImportStart;
67+
Optional<Integer> maybeFirstImport = findIdentifier(0, IMPORT_OR_CLASS_START);
68+
if (!maybeFirstImport.isPresent() || !tokenAt(maybeFirstImport.get()).equals("import")) {
69+
// No imports, so nothing to do.
70+
return text;
71+
}
72+
firstImportStart = maybeFirstImport.get();
73+
int unindentedFirstImportStart = unindent(firstImportStart);
74+
75+
ImportsAndIndex imports = scanImports(firstImportStart);
76+
int afterLastImport = imports.index;
77+
78+
// Make sure there are no more imports before the next class (etc) definition.
79+
Optional<Integer> maybeLaterImport = findIdentifier(afterLastImport, IMPORT_OR_CLASS_START);
80+
if (maybeLaterImport.isPresent() && tokenAt(maybeLaterImport.get()).equals("import")) {
81+
throw new FormatterException("Imports not contiguous (perhaps a comment separates them?)");
82+
}
83+
84+
StringBuilder result = new StringBuilder();
85+
String prefix = tokString(0, unindentedFirstImportStart);
86+
result.append(prefix);
87+
if (!prefix.isEmpty() && Newlines.getLineEnding(prefix) == null) {
88+
result.append(lineSeparator).append(lineSeparator);
89+
}
90+
result.append(reorderedImportsString(imports.imports));
91+
92+
List<String> tail = new ArrayList<>();
93+
tail.add(CharMatcher.whitespace().trimLeadingFrom(tokString(afterLastImport, toks.size())));
94+
if (!toks.isEmpty()) {
95+
Tok lastTok = getLast(toks);
96+
int tailStart = lastTok.getPosition() + lastTok.length();
97+
tail.add(text.substring(tailStart));
98+
}
99+
if (tail.stream().anyMatch(s -> !s.isEmpty())) {
100+
result.append(lineSeparator);
101+
tail.forEach(result::append);
102+
}
103+
104+
return result.toString();
41105
}
42106

43107
/**
@@ -55,45 +119,127 @@ public static String reorderImports(String text) throws FormatterException {
55119
private static final ImmutableSet<String> IMPORT_OR_CLASS_START =
56120
ImmutableSet.of("import", "class", "interface", "enum");
57121

122+
/**
123+
* A {@link Comparator} that orders {@link Import}s by Google Style, defined at
124+
* https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing.
125+
*/
126+
private static final Comparator<Import> GOOGLE_IMPORT_COMPARATOR =
127+
Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported);
128+
129+
/**
130+
* A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at
131+
* https://source.android.com/setup/contribute/code-style#order-import-statements and implemented
132+
* in IntelliJ at
133+
* https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml.
134+
*/
135+
private static final Comparator<Import> AOSP_IMPORT_COMPARATOR =
136+
Comparator.comparing(Import::isStatic, trueFirst())
137+
.thenComparing(Import::isAndroid, trueFirst())
138+
.thenComparing(Import::isThirdParty, trueFirst())
139+
.thenComparing(Import::isJava, trueFirst())
140+
.thenComparing(Import::imported);
141+
142+
/**
143+
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
144+
* Import}s based on Google style.
145+
*/
146+
private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) {
147+
return prev.isStatic() && !curr.isStatic();
148+
}
149+
150+
/**
151+
* Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link
152+
* Import}s based on AOSP style.
153+
*/
154+
private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) {
155+
if (prev.isStatic() && !curr.isStatic()) {
156+
return true;
157+
}
158+
// insert blank line between "com.android" from "com.anythingelse"
159+
if (prev.isAndroid() && !curr.isAndroid()) {
160+
return true;
161+
}
162+
return !prev.topLevel().equals(curr.topLevel());
163+
}
164+
58165
private final String text;
59166
private final ImmutableList<Tok> toks;
60167
private final String lineSeparator;
168+
private final Comparator<Import> importComparator;
169+
private final BiFunction<Import, Import, Boolean> shouldInsertBlankLineFn;
61170

62-
private ImportOrderer(String text, ImmutableList<Tok> toks) throws FormatterException {
171+
private ImportOrderer(String text, ImmutableList<Tok> toks, Style style) {
63172
this.text = text;
64173
this.toks = toks;
65174
this.lineSeparator = Newlines.guessLineSeparator(text);
175+
if (style.equals(Style.GOOGLE)) {
176+
this.importComparator = GOOGLE_IMPORT_COMPARATOR;
177+
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle;
178+
} else if (style.equals(Style.AOSP)) {
179+
this.importComparator = AOSP_IMPORT_COMPARATOR;
180+
this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp;
181+
} else {
182+
throw new IllegalArgumentException("Unsupported code style: " + style);
183+
}
66184
}
67185

68186
/** An import statement. */
69-
private class Import implements Comparable<Import> {
187+
class Import {
188+
private final String imported;
189+
private final boolean isStatic;
190+
private final String trailing;
191+
192+
Import(String imported, String trailing, boolean isStatic) {
193+
this.imported = imported;
194+
this.trailing = trailing;
195+
this.isStatic = isStatic;
196+
}
197+
70198
/** The name being imported, for example {@code java.util.List}. */
71-
final String imported;
199+
String imported() {
200+
return imported;
201+
}
202+
203+
/** True if this is {@code import static}. */
204+
boolean isStatic() {
205+
return isStatic;
206+
}
207+
208+
/** The top-level package of the import. */
209+
String topLevel() {
210+
return DOT_SPLITTER.split(imported()).iterator().next();
211+
}
212+
213+
/** True if this is an Android import per AOSP style. */
214+
boolean isAndroid() {
215+
return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.")
216+
.anyMatch(imported::startsWith);
217+
}
218+
219+
/** True if this is a Java import per AOSP style. */
220+
boolean isJava() {
221+
switch (topLevel()) {
222+
case "java":
223+
case "javax":
224+
return true;
225+
default:
226+
return false;
227+
}
228+
}
72229

73230
/**
74231
* The {@code //} comment lines after the final {@code ;}, up to and including the line
75232
* terminator of the last one. Note: In case two imports were separated by a space (which is
76233
* disallowed by the style guide), the trailing whitespace of the first import does not include
77234
* a line terminator.
78235
*/
79-
final String trailing;
80-
81-
/** True if this is {@code import static}. */
82-
final boolean isStatic;
83-
84-
Import(String imported, String trailing, boolean isStatic) {
85-
this.imported = imported;
86-
this.trailing = trailing;
87-
this.isStatic = isStatic;
236+
String trailing() {
237+
return trailing;
88238
}
89239

90-
// This is how the sorting happens, including sorting static imports before non-static ones.
91-
@Override
92-
public int compareTo(Import that) {
93-
if (this.isStatic != that.isStatic) {
94-
return this.isStatic ? -1 : +1;
95-
}
96-
return this.imported.compareTo(that.imported);
240+
/** True if this is a third-party import per AOSP style. */
241+
public boolean isThirdParty() {
242+
return !(isAndroid() || isJava());
97243
}
98244

99245
// One or multiple lines, the import itself and following comments, including the line
@@ -102,61 +248,19 @@ public int compareTo(Import that) {
102248
public String toString() {
103249
StringBuilder sb = new StringBuilder();
104250
sb.append("import ");
105-
if (isStatic) {
251+
if (isStatic()) {
106252
sb.append("static ");
107253
}
108-
sb.append(imported).append(';');
109-
if (trailing.trim().isEmpty()) {
254+
sb.append(imported()).append(';');
255+
if (trailing().trim().isEmpty()) {
110256
sb.append(lineSeparator);
111257
} else {
112-
sb.append(trailing);
258+
sb.append(trailing());
113259
}
114260
return sb.toString();
115261
}
116262
}
117263

118-
private String reorderImports() throws FormatterException {
119-
int firstImportStart;
120-
Optional<Integer> maybeFirstImport = findIdentifier(0, IMPORT_OR_CLASS_START);
121-
if (!maybeFirstImport.isPresent() || !tokenAt(maybeFirstImport.get()).equals("import")) {
122-
// No imports, so nothing to do.
123-
return text;
124-
}
125-
firstImportStart = maybeFirstImport.get();
126-
int unindentedFirstImportStart = unindent(firstImportStart);
127-
128-
ImportsAndIndex imports = scanImports(firstImportStart);
129-
int afterLastImport = imports.index;
130-
131-
// Make sure there are no more imports before the next class (etc) definition.
132-
Optional<Integer> maybeLaterImport = findIdentifier(afterLastImport, IMPORT_OR_CLASS_START);
133-
if (maybeLaterImport.isPresent() && tokenAt(maybeLaterImport.get()).equals("import")) {
134-
throw new FormatterException("Imports not contiguous (perhaps a comment separates them?)");
135-
}
136-
137-
StringBuilder result = new StringBuilder();
138-
String prefix = tokString(0, unindentedFirstImportStart);
139-
result.append(prefix);
140-
if (!prefix.isEmpty() && Newlines.getLineEnding(prefix) == null) {
141-
result.append(lineSeparator).append(lineSeparator);
142-
}
143-
result.append(reorderedImportsString(imports.imports));
144-
145-
List<String> tail = new ArrayList<>();
146-
tail.add(CharMatcher.whitespace().trimLeadingFrom(tokString(afterLastImport, toks.size())));
147-
if (!toks.isEmpty()) {
148-
Tok lastTok = getLast(toks);
149-
int tailStart = lastTok.getPosition() + lastTok.length();
150-
tail.add(text.substring(tailStart));
151-
}
152-
if (tail.stream().anyMatch(s -> !s.isEmpty())) {
153-
result.append(lineSeparator);
154-
tail.forEach(result::append);
155-
}
156-
157-
return result.toString();
158-
}
159-
160264
private String tokString(int start, int end) {
161265
StringBuilder sb = new StringBuilder();
162266
for (int i = start; i < end; i++) {
@@ -191,7 +295,7 @@ private static class ImportsAndIndex {
191295
*/
192296
private ImportsAndIndex scanImports(int i) throws FormatterException {
193297
int afterLastImport = i;
194-
ImmutableSortedSet.Builder<Import> imports = ImmutableSortedSet.naturalOrder();
298+
ImmutableSortedSet.Builder<Import> imports = ImmutableSortedSet.orderedBy(importComparator);
195299
// JavaInput.buildToks appends a zero-width EOF token after all tokens. It won't match any
196300
// of our tests here and protects us from running off the end of the toks list. Since it is
197301
// zero-width it doesn't matter if we include it in our string concatenation at the end.
@@ -258,20 +362,18 @@ private ImportsAndIndex scanImports(int i) throws FormatterException {
258362
private String reorderedImportsString(ImmutableSortedSet<Import> imports) {
259363
Preconditions.checkArgument(!imports.isEmpty(), "imports");
260364

261-
Import firstImport = imports.iterator().next();
262-
263-
// Pretend that the first import was preceded by another import of the same kind
264-
// (static or non-static), so we don't insert a newline there.
265-
boolean lastWasStatic = firstImport.isStatic;
365+
// Pretend that the first import was preceded by another import of the same kind, so we don't
366+
// insert a newline there.
367+
Import prevImport = imports.iterator().next();
266368

267369
StringBuilder sb = new StringBuilder();
268-
for (Import thisImport : imports) {
269-
if (lastWasStatic && !thisImport.isStatic) {
370+
for (Import currImport : imports) {
371+
if (shouldInsertBlankLineFn.apply(prevImport, currImport)) {
270372
// Blank line between static and non-static imports.
271373
sb.append(lineSeparator);
272374
}
273-
lastWasStatic = thisImport.isStatic;
274-
sb.append(thisImport);
375+
sb.append(currImport);
376+
prevImport = currImport;
275377
}
276378
return sb.toString();
277379
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
public class JavaFormatterOptions {
3131

3232
public enum Style {
33-
3433
/** The default Google Java Style configuration. */
3534
GOOGLE(1),
3635

@@ -54,11 +53,16 @@ private JavaFormatterOptions(Style style) {
5453
this.style = style;
5554
}
5655

57-
/** Returns the multiplier for the unit of indent */
56+
/** Returns the multiplier for the unit of indent. */
5857
public int indentationMultiplier() {
5958
return style.indentationMultiplier();
6059
}
6160

61+
/** Returns the code style. */
62+
public Style style() {
63+
return style;
64+
}
65+
6266
/** Returns the default formatting options. */
6367
public static JavaFormatterOptions defaultOptions() {
6468
return builder().build();

0 commit comments

Comments
 (0)