Skip to content

Commit

Permalink
Change Error Prone and Refaster import ordering logic
Browse files Browse the repository at this point in the history
to match new rules (see: google/styleguide#160).

MOE_MIGRATED_REVID=127228892
  • Loading branch information
eaftan authored and cushon committed Jul 13, 2016
1 parent 749671e commit 0d11c44
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 348 deletions.
180 changes: 57 additions & 123 deletions core/src/main/java/com/google/errorprone/apply/ImportStatements.java
Expand Up @@ -16,7 +16,6 @@


package com.google.errorprone.apply; package com.google.errorprone.apply;


import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
Expand All @@ -30,54 +29,43 @@


import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/** /**
* Represents a list of import statements. Supports adding and removing * Represents a list of import statements. Supports adding and removing import statements and pretty
* import statements and pretty printing the result as source code. Correctly * printing the result as source code. Correctly sorts the imports according to Google Java Style
* sorts the imports according to Google Java Style Guide rules. * Guide rules.
* *
* @author eaftan@google.com (Eddie Aftandilian) * @author eaftan@google.com (Eddie Aftandilian)
*/ */
public class ImportStatements { public class ImportStatements {


/** An {@link Ordering} that sorts import statements according to the Google Java Style Guide. */
private static final Ordering<String> IMPORT_ORDERING =
new Ordering<String>() {
@Override
public int compare(String s1, String s2) {
return ComparisonChain.start()
.compareTrueFirst(isStatic(s1), isStatic(s2))
.compare(s1, s2)
.result();
}
};

private int startPos = Integer.MAX_VALUE; private int startPos = Integer.MAX_VALUE;
private int endPos = -1; private int endPos = -1;
private final Set<String> importStrings; private final SortedSet<String> importStrings;
private boolean hasExistingImports; private boolean hasExistingImports;


/**
* An Ordering that sorts import statements based on the Google Java Style
* Guide.
*/
private static final Ordering<String> IMPORT_ORDERING = new Ordering<String>() {
@Override
public int compare(String s1, String s2) {
return ComparisonChain.start()
.compare(Kind.getKind(s1), Kind.getKind(s2))
.compare(s1, s2)
.result();
}
};

/**
* A regex to use for finding the top-level package in an import
* statement.
*/
private static final Pattern TOPLEVEL_PATTERN =
Pattern.compile("import\\s+(static\\s+|)([^.]+).");

public static ImportStatements create(JCCompilationUnit compilationUnit) { public static ImportStatements create(JCCompilationUnit compilationUnit) {
return new ImportStatements((JCExpression) compilationUnit.getPackageName(), return new ImportStatements((JCExpression) compilationUnit.getPackageName(),
compilationUnit.getImports(), compilationUnit.endPositions); compilationUnit.getImports(), compilationUnit.endPositions);
} }

public ImportStatements(JCExpression packageTree, List<JCImport> importTrees, public ImportStatements(JCExpression packageTree, List<JCImport> importTrees,
EndPosTable endPositions) { EndPosTable endPositions) {

// find start, end positions for current list of imports (for replacement) // find start, end positions for current list of imports (for replacement)
if (importTrees.isEmpty()) { if (importTrees.isEmpty()) {
// start/end positions are just after the package expression // start/end positions are just after the package expression
Expand All @@ -96,12 +84,12 @@ public ImportStatements(JCExpression packageTree, List<JCImport> importTrees,


startPos = Math.min(startPos, currStartPos); startPos = Math.min(startPos, currStartPos);
endPos = Math.max(endPos, currEndPos); endPos = Math.max(endPos, currEndPos);
} }
} }

// sanity check for start/end positions // sanity check for start/end positions
Preconditions.checkState(startPos <= endPos); Preconditions.checkState(startPos <= endPos);

// convert list of JCImports to list of strings // convert list of JCImports to list of strings
importStrings = new TreeSet<>(IMPORT_ORDERING); importStrings = new TreeSet<>(IMPORT_ORDERING);
importStrings.addAll(Lists.transform(importTrees, new Function<JCImport, String>() { importStrings.addAll(Lists.transform(importTrees, new Function<JCImport, String>() {
Expand All @@ -112,7 +100,7 @@ public String apply(JCImport input) {
} }
})); }));
} }

/** /**
* Return the start position of the import statements. * Return the start position of the import statements.
*/ */
Expand All @@ -126,141 +114,87 @@ public int getStartPos() {
public int getEndPos() { public int getEndPos() {
return endPos; return endPos;
} }

/** /**
* Add an import to the list of imports. If the import is already in the * Add an import to the list of imports. If the import is already in the list, does nothing. The
* list, does nothing. The import should be of the form "import foo.bar". * import should be of the form "import foo.bar".
* *
* @param importToAdd a string representation of the import to add * @param importToAdd a string representation of the import to add
* @return true if the import was added * @return true if the import was added
*/ */
public boolean add(String importToAdd) { public boolean add(String importToAdd) {
return importStrings.add(importToAdd); return importStrings.add(importToAdd);
} }

/** /**
* Add all imports in a collection to this list of imports. Does not add * Add all imports in a collection to this list of imports. Does not add any imports that are
* any imports that are already in the list. * already in the list.
* *
* @param importsToAdd a collection of imports to add * @param importsToAdd a collection of imports to add
* @return true if any imports were added to the list * @return true if any imports were added to the list
*/ */
public boolean addAll(Collection<String> importsToAdd) { public boolean addAll(Collection<String> importsToAdd) {
return importStrings.addAll(importsToAdd); return importStrings.addAll(importsToAdd);
} }

/** /**
* Remove an import from the list of imports. If the import is not in the * Remove an import from the list of imports. If the import is not in the list, does nothing. The
* list, does nothing. The import should be of the form "import foo.bar". * import should be of the form "import foo.bar".
* *
* @param importToRemove a string representation of the import to remove * @param importToRemove a string representation of the import to remove
* @return true if the import was removed * @return true if the import was removed
*/ */
public boolean remove(String importToRemove) { public boolean remove(String importToRemove) {
return importStrings.remove(importToRemove); return importStrings.remove(importToRemove);
} }

/** /**
* Removes all imports in a collection to this list of imports. Does not * Removes all imports in a collection to this list of imports. Does not remove any imports that
* remove any imports that are not in the list. * are not in the list.
* *
* @param importsToRemove a collection of imports to remove * @param importsToRemove a collection of imports to remove
* @return true if any imports were removed from the list * @return true if any imports were removed from the list
*/ */
public boolean removeAll(Collection<String> importsToRemove) { public boolean removeAll(Collection<String> importsToRemove) {
return importStrings.removeAll(importsToRemove); return importStrings.removeAll(importsToRemove);
} }


/** /** Returns a string representation of the imports as Java code in proper Google Java Style. */
* Returns a string representation of the imports, as proper Java code.
* Includes newlines in the correct places as defined by the Google
* Java Style Guide.
*/
@Override @Override
public String toString() { public String toString() {
if (importStrings.size() == 0) { if (importStrings.size() == 0) {
return ""; return "";
} }

StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();

if (!hasExistingImports) { if (!hasExistingImports) {
// insert a newline after the package expression, then add imports // insert a newline after the package expression, then add imports
result.append('\n'); result.append('\n');
} }

// output sorted imports, with line breaks between sections // output sorted imports, with a line break between static and non-static imports
Kind prevKind = null; boolean first = true;
String prevTopLevel = null; boolean prevIsStatic = true;
for (String importString : importStrings) { for (String importString : importStrings) {
Kind currKind = Kind.getKind(importString); boolean isStatic = isStatic(importString);
String currTopLevel = getTopLevel(importString); if (!first && prevIsStatic && !isStatic) {
if (prevKind != null && prevKind != currKind) {
result.append('\n'); result.append('\n');
} else if (currKind == Kind.THIRD_PARTY) {
if (prevTopLevel != null && !prevTopLevel.equals(currTopLevel)) {
result.append('\n');
}
} }
result.append(importString).append(";\n"); result.append(importString).append(";\n");
prevKind = currKind; prevIsStatic = isStatic;
prevTopLevel = currTopLevel; first = false;
} }

String replacementString = result.toString(); String replacementString = result.toString();
if (!hasExistingImports) { if (!hasExistingImports) {
return replacementString; return replacementString;
} else { } else {
return replacementString.substring(0, replacementString.length() - 1); // trim last newline return replacementString.substring(0, replacementString.length() - 1); // trim last newline
} }
} }

/**
* Given an import string, returns the top-level package for that
* import.
*/
@VisibleForTesting
static String getTopLevel(String importString) {
Matcher m = TOPLEVEL_PATTERN.matcher(importString);
if (m.find()) {
return m.group(2);
} else {
throw new IllegalArgumentException(importString + " is not a valid import statement");
}
}


/** private static boolean isStatic(String importString) {
* An enumeration of the different kinds of import statements we might return importString.startsWith("import static");
* encounter. Each kind must be sorted into its own bucket in the import
* statements.
*/
private enum Kind {
STATIC, // import static
GOOGLE, // import com.google...
THIRD_PARTY, // import org.foo.bar...
JAVA, // import java...
JAVAX; // import javax...

/**
* Determines the Kind of an import statement.
*
* @param importString the import statement as a string
* @return the kind of the import statement
*/
public static Kind getKind(String importString) {
if (importString.startsWith("import static")) {
return STATIC;
} else if (importString.startsWith("import com.google.")) {
return GOOGLE;
} else if (importString.startsWith("import java.")) {
return JAVA;
} else if (importString.startsWith("import javax.")) {
return JAVAX;
} else {
return THIRD_PARTY;
}
}
} }

} }

0 comments on commit 0d11c44

Please sign in to comment.