Skip to content

Commit e6c2323

Browse files
author
Vincent Potucek
committed
fix RemoveUnusedImportsStep leftovers
1 parent 13ce22d commit e6c2323

File tree

2 files changed

+548
-225
lines changed

2 files changed

+548
-225
lines changed

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

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVo
173173
public Void visitReference(ReferenceTree referenceTree, Void unused) {
174174
DCReference reference = (DCReference) referenceTree;
175175
long basePos =
176-
reference
177-
.pos((DCTree.DCDocComment) getCurrentPath().getDocComment())
178-
.getStartPosition();
176+
reference
177+
.pos((DCTree.DCDocComment) getCurrentPath().getDocComment())
178+
.getStartPosition();
179179
// the position of trees inside the reference node aren't stored, but the qualifier's
180180
// start position is the beginning of the reference node
181181
if (reference.qualifierExpression != null) {
@@ -203,62 +203,71 @@ public ReferenceScanner(long basePos) {
203203
@Override
204204
public Void visitIdentifier(IdentifierTree node, Void aVoid) {
205205
usedInJavadoc.put(
206-
node.getName().toString(),
207-
basePos != -1
208-
? Range.closedOpen((int) basePos, (int) basePos + node.getName().length())
209-
: null);
206+
node.getName().toString(),
207+
basePos != -1
208+
? Range.closedOpen((int) basePos, (int) basePos + node.getName().length())
209+
: null);
210210
return super.visitIdentifier(node, aVoid);
211211
}
212212
}
213213
}
214214
}
215-
216215
public static String removeUnusedImports(final String contents) throws FormatterException {
217216
Context context = new Context();
218217
JCCompilationUnit unit = parse(context, contents);
219-
if (unit == null) {
220-
// error handling is done during formatting
221-
return contents;
222-
}
223218
UnusedImportScanner scanner = new UnusedImportScanner(JavacTrees.instance(context));
224219
scanner.scan(unit, null);
225-
return applyReplacements(
226-
contents, buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc));
220+
String s = applyReplacements(
221+
contents, buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc));
222+
223+
// Normalize newlines while preserving important blank lines
224+
String sep = Newlines.guessLineSeparator(contents);
225+
226+
// Ensure exactly one blank line after package declaration
227+
s = s.replaceAll("(?m)^package .+" + sep + "\\s+" + sep, "package $1" + sep + sep);
228+
229+
// Ensure exactly one blank line between last import and class declaration
230+
s = s.replaceAll("(?m)import .+" + sep + "\\s+" + sep + "(?=class|interface|enum|record)",
231+
"import $1" + sep + sep);
232+
233+
// Remove multiple blank lines elsewhere in imports section
234+
s = s.replaceAll("(?m)^import .+" + sep + "\\s+" + sep + "(?=import)", "import $1" + sep);
235+
236+
return s;
227237
}
228238

229239
private static JCCompilationUnit parse(Context context, String javaInput)
230-
throws FormatterException {
240+
throws FormatterException {
231241
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
232242
context.put(DiagnosticListener.class, diagnostics);
233243
Options.instance(context).put("--enable-preview", "true");
234244
Options.instance(context).put("allowStringFolding", "false");
235245
JCCompilationUnit unit;
236-
JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8);
237-
try {
246+
try (JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8)){
238247
fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of());
239248
} catch (IOException e) {
240249
// impossible
241250
throw new IOError(e);
242251
}
243252
SimpleJavaFileObject source =
244-
new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
245-
@Override
246-
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
247-
return javaInput;
248-
}
249-
};
253+
new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) {
254+
@Override
255+
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
256+
return javaInput;
257+
}
258+
};
250259
Log.instance(context).useSource(source);
251260
ParserFactory parserFactory = ParserFactory.instance(context);
252261
JavacParser parser =
253-
parserFactory.newParser(
254-
javaInput,
255-
/* keepDocComments= */ true,
256-
/* keepEndPos= */ true,
257-
/* keepLineMap= */ true);
262+
parserFactory.newParser(
263+
javaInput,
264+
/* keepDocComments= */ true,
265+
/* keepEndPos= */ true,
266+
/* keepLineMap= */ true);
258267
unit = parser.parseCompilationUnit();
259268
unit.sourcefile = source;
260269
Iterable<Diagnostic<? extends JavaFileObject>> errorDiagnostics =
261-
Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic);
270+
Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic);
262271
if (!Iterables.isEmpty(errorDiagnostics)) {
263272
// error handling is done during formatting
264273
throw FormatterException.fromJavacDiagnostics(errorDiagnostics);
@@ -268,11 +277,13 @@ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOExcept
268277

269278
/** Construct replacements to fix unused imports. */
270279
private static RangeMap<Integer, String> buildReplacements(
271-
String contents,
272-
JCCompilationUnit unit,
273-
Set<String> usedNames,
274-
Multimap<String, Range<Integer>> usedInJavadoc) {
280+
String contents,
281+
JCCompilationUnit unit,
282+
Set<String> usedNames,
283+
Multimap<String, Range<Integer>> usedInJavadoc) {
275284
RangeMap<Integer, String> replacements = TreeRangeMap.create();
285+
int size = unit.getImports().size();
286+
JCTree lastImport = size > 0 ? unit.getImports().get(size - 1) : null;
276287
for (JCTree importTree : unit.getImports()) {
277288
String simpleName = getSimpleName(importTree);
278289
if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) {
@@ -282,34 +293,52 @@ private static RangeMap<Integer, String> buildReplacements(
282293
int endPosition = importTree.getEndPosition(unit.endPositions);
283294
endPosition = max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition);
284295
String sep = Newlines.guessLineSeparator(contents);
296+
297+
// Check if there's an empty line after this import
298+
boolean hasEmptyLineAfter = false;
299+
if (endPosition + sep.length() * 2 <= contents.length()) {
300+
String nextTwoLines = contents.substring(endPosition, endPosition + sep.length() * 2);
301+
hasEmptyLineAfter = nextTwoLines.equals(sep + sep);
302+
}
303+
285304
if (endPosition + sep.length() < contents.length()
286-
&& contents.subSequence(endPosition, endPosition + sep.length()).toString().equals(sep)) {
305+
&& contents.subSequence(endPosition, endPosition + sep.length()).toString().equals(sep)) {
287306
endPosition += sep.length();
288307
}
308+
309+
// If this isn't the last import and there's an empty line after, preserve it
310+
if ((size == 1 || importTree != lastImport) && !hasEmptyLineAfter) {
311+
while (endPosition + sep.length() <= contents.length()
312+
&& contents.regionMatches(endPosition, sep, 0, sep.length())) {
313+
endPosition += sep.length();
314+
}
315+
}
289316
replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), "");
290317
}
291318
return replacements;
292319
}
293-
294320
private static String getSimpleName(JCTree importTree) {
295321
return getQualifiedIdentifier(importTree).getIdentifier().toString();
296322
}
297323

298324
private static boolean isUnused(
299-
JCCompilationUnit unit,
300-
Set<String> usedNames,
301-
Multimap<String, Range<Integer>> usedInJavadoc,
302-
JCTree importTree,
303-
String simpleName) {
325+
JCCompilationUnit unit,
326+
Set<String> usedNames,
327+
Multimap<String, Range<Integer>> usedInJavadoc,
328+
JCTree importTree,
329+
String simpleName) {
304330
JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree);
305331
String qualifier = qualifiedIdentifier.getExpression().toString();
306332
if (qualifier.equals("java.lang")) {
307333
return true;
308334
}
335+
if(usedNames.contains(simpleName)){
336+
return false;
337+
}
309338
if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) {
310339
return true;
311340
}
312-
if (qualifiedIdentifier.getIdentifier().contentEquals("*")) {
341+
if (qualifiedIdentifier.getIdentifier().contentEquals("*") && !((JCImport) importTree).isStatic()) {
313342
return false;
314343
}
315344

@@ -355,4 +384,4 @@ private static String applyReplacements(String source, RangeMap<Integer, String>
355384
}
356385
return sb.toString();
357386
}
358-
}
387+
}

0 commit comments

Comments
 (0)