From 622732fdf9118727ab05460cb5373d612348f63c Mon Sep 17 00:00:00 2001 From: Vincent Potucek Date: Fri, 4 Jul 2025 12:06:53 +0200 Subject: [PATCH] fix RemoveUnusedImportsStep leftovers --- .../javaformat/java/RemoveUnusedImports.java | 180 ++- .../java/RemoveUnusedImportsTest.java | 1306 +++++++++++++++-- 2 files changed, 1273 insertions(+), 213 deletions(-) diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java index a66b6b559..d9cc122b7 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java @@ -16,14 +16,16 @@ package com.palantir.javaformat.java; +import static java.lang.Math.max; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.base.CharMatcher; import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.google.common.collect.Range; import com.google.common.collect.RangeMap; -import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeMap; -import com.google.common.collect.TreeRangeSet; import com.palantir.javaformat.Newlines; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.ReferenceTree; @@ -36,20 +38,30 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.api.JavacTrees; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.parser.ParserFactory; import com.sun.tools.javac.tree.DCTree; import com.sun.tools.javac.tree.DCTree.DCReference; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; -import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Log; import com.sun.tools.javac.util.Options; +import java.io.IOException; import java.lang.reflect.Method; +import java.net.URI; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; +import javax.tools.DiagnosticCollector; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardLocation; /** * Removes unused imports from a source file. Imports that are only used in javadoc are also removed, and the references @@ -76,15 +88,12 @@ public class RemoveUnusedImports { private static final class UnusedImportScanner extends TreePathScanner { private final Set usedNames = new LinkedHashSet<>(); - private final Multimap> usedInJavadoc = HashMultimap.create(); - - final JavacTrees trees; - final DocTreeScanner docTreeSymbolScanner; + private final DocTreeScanner docTreeSymbolScanner = new DocTreeScanner(); + private final JavacTrees trees; private UnusedImportScanner(JavacTrees trees) { this.trees = trees; - docTreeSymbolScanner = new DocTreeScanner(); } /** Skip the imports themselves when checking for usage. */ @@ -202,21 +211,50 @@ public Void visitIdentifier(IdentifierTree node, Void aVoid) { } } - public static String removeUnusedImports(final String contents) throws FormatterException { + public static String removeUnusedImports(final String contents) { Context context = new Context(); JCCompilationUnit unit = parse(context, contents); - if (unit == null) { - // error handling is done during formatting - return contents; - } UnusedImportScanner scanner = new UnusedImportScanner(JavacTrees.instance(context)); scanner.scan(unit, null); - return applyReplacements(contents, buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc)); + + String s = contents; + + // Normalize newlines while preserving important blank lines + String sep = Newlines.guessLineSeparator(contents); + + // Ensure exactly one blank line after package declaration + s = s.replaceAll("(?m)^(package .+)" + sep + "\\s+" + sep, "$1" + sep + sep); + + // Ensure exactly one blank line between last import and class declaration + s = s.replaceAll("(?m)^(import .+)" + sep + "\\s+" + sep + "(?=class|interface|enum|record)", "$1" + sep + sep); + + // Remove multiple blank lines elsewhere in imports section + s = s.replaceAll("(?m)^(import .+)" + sep + "\\s+" + sep + "(?=import)", "$1" + sep); + + // Apply replacements last, after formatting + return applyReplacements(s, buildReplacements(s, unit, scanner.usedNames, scanner.usedInJavadoc)); } - private static JCCompilationUnit parse(Context context, String javaInput) throws FormatterException { + private static JCCompilationUnit parse(Context context, String javaInput) { + context.put(DiagnosticListener.class, new DiagnosticCollector()); Options.instance(context).put("allowStringFolding", "false"); - return Formatter.parseJcCompilationUnit(context, javaInput); + try (JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8)) { + fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); + } catch (IOException e) { + throw new RuntimeException(e); + } + SimpleJavaFileObject source = new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return javaInput; + } + }; + Log.instance(context).useSource(source); + JCCompilationUnit unit = ParserFactory.instance(context) + .newParser(javaInput, true, true, true) + .parseCompilationUnit(); + unit.sourcefile = source; + return unit; } /** Construct replacements to fix unused imports. */ @@ -226,70 +264,93 @@ private static RangeMap buildReplacements( Set usedNames, Multimap> usedInJavadoc) { RangeMap replacements = TreeRangeMap.create(); - for (JCImport importTree : unit.getImports()) { - String simpleName = getSimpleName(importTree); - if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) { - continue; - } - // delete the import - int endPosition = importTree.getEndPosition(unit.endPositions); - endPosition = Math.max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition); - String sep = Newlines.guessLineSeparator(contents); - if (endPosition + sep.length() < contents.length() - && contents.subSequence(endPosition, endPosition + sep.length()) - .toString() - .equals(sep)) { + int size = unit.getImports().size(); + unit.getImports().stream() + .filter(importTree -> isUnused( + unit, + usedNames, + usedInJavadoc, + importTree, + getQualifiedIdentifier(importTree).getIdentifier().toString())) + .forEach(importTree -> replacements.put( + Range.closedOpen( + importTree.getStartPosition(), + calculateEndPosition( + contents, + importTree, + unit, + Newlines.guessLineSeparator(contents), + size, + size > 0 ? unit.getImports().get(size - 1) : null)), + "")); + + return replacements; + } + + private static int calculateEndPosition( + String contents, + JCTree importTree, + JCCompilationUnit unit, + String sep, + int size, + @Nullable JCTree lastImport) { + int endPosition = importTree.getEndPosition(unit.endPositions); + endPosition = max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition); + if (endPosition + sep.length() < contents.length() + && contents.subSequence(endPosition, endPosition + sep.length()) + .toString() + .equals(sep)) { + endPosition += sep.length(); + } else if ((size == 1 || importTree != lastImport) && !checkForEmptyLineAfter(contents, endPosition, sep)) { + while (endPosition + sep.length() <= contents.length() + && contents.regionMatches(endPosition, sep, 0, sep.length())) { endPosition += sep.length(); } - replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), ""); } - return replacements; + return endPosition; } - private static String getSimpleName(ImportTree importTree) { - return importTree.getQualifiedIdentifier() instanceof JCIdent - ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() - : ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getIdentifier() - .toString(); + private static boolean checkForEmptyLineAfter(String contents, int endPosition, String sep) { + return endPosition + sep.length() * 2 <= contents.length() + && contents.substring(endPosition, endPosition + sep.length() * 2) + .equals(sep + sep); } private static boolean isUnused( JCCompilationUnit unit, Set usedNames, Multimap> usedInJavadoc, - ImportTree importTree, + JCTree importTree, String simpleName) { - String qualifier = ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getExpression() - .toString(); + JCFieldAccess qualifiedIdentifier = getQualifiedIdentifier(importTree); + String qualifier = qualifiedIdentifier.getExpression().toString(); if (qualifier.equals("java.lang")) { return true; } + if (usedNames.contains(simpleName)) { + return false; + } if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) { return true; } - if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess - && ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getIdentifier() - .contentEquals("*")) { + if (qualifiedIdentifier.getIdentifier().contentEquals("*") && !((JCImport) importTree).isStatic()) { return false; } + return !usedInJavadoc.containsKey(simpleName); + } - if (usedNames.contains(simpleName)) { - return false; + private static JCFieldAccess getQualifiedIdentifier(JCTree 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); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); } - if (usedInJavadoc.containsKey(simpleName)) { - return false; - } - return true; } /** Applies the replacements to the given source, and re-format any edited javadoc. */ private static String applyReplacements(String source, RangeMap replacements) { - // save non-empty fixed ranges for reformatting after fixes are applied - RangeSet fixedRanges = TreeRangeSet.create(); - // Apply the fixes in increasing order, adjusting ranges to account for // earlier fixes that change the length of the source. The output ranges are // needed so we can reformat fixed regions, otherwise the fixes could just @@ -299,14 +360,11 @@ private static String applyReplacements(String source, RangeMap for (Map.Entry, String> replacement : replacements.asMapOfRanges().entrySet()) { Range range = replacement.getKey(); - String replaceWith = replacement.getValue(); - int start = offset + range.lowerEndpoint(); - int end = offset + range.upperEndpoint(); - sb.replace(start, end, replaceWith); - if (!replaceWith.isEmpty()) { - fixedRanges.add(Range.closedOpen(start, end)); + if (replacement.getValue().isBlank()) { + String replaceWith = replacement.getValue(); + sb.replace(offset + range.lowerEndpoint(), offset + range.upperEndpoint(), replaceWith); + offset += replaceWith.length() - (range.upperEndpoint() - range.lowerEndpoint()); } - offset += replaceWith.length() - (range.upperEndpoint() - range.lowerEndpoint()); } return sb.toString(); } diff --git a/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java b/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java index cdea6fe04..d86dfe41b 100644 --- a/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java +++ b/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java @@ -36,234 +36,1236 @@ public static List parameters() { String[][][] inputsOutputs = { { { - "import java.util.List;", - "import java.util.ArrayList;", - "", - "class Test {", - " /** could be an {@link ArrayList} */", - " List xs;", - "}", - }, - { - "import java.util.List;", - "import java.util.ArrayList;", - "", - "class Test {", - " /** could be an {@link ArrayList} */", - " List xs;", - "}", + """ + import java.util.List; + import java.util.ArrayList; + + class Test { + /** could be an {@link ArrayList} */ + List xs; + } + """ + }, + { + """ + import java.util.List; + import java.util.ArrayList; + + class Test { + /** could be an {@link ArrayList} */ + List xs; + } + """ + } + }, + { + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; + + import java.util.List; + import java.util.Set; + + import javax.annotations.Nullable; + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + import org.junit.jupiter.api.parallel.ExecutionMode; // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + }, + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; + + import java.util.List; + + import javax.annotations.Nullable; + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + } + }, + { + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; // used with comment + + import java.util.List; // new group java: empty line before + import java.util.Set; + + import javax.annotations.Nullable; // new group javax: empty line before + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + import org.junit.jupiter.api.parallel.ExecutionMode; // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + }, + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; // used with comment + + import java.util.List; // new group java: empty line before + + import javax.annotations.Nullable; // new group javax: empty line before + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + } + }, + { + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; // used with comment + + import java.util.List; + import java.util.Set; + import javax.annotations.Nullable; // no empty line before + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + import org.junit.jupiter.api.parallel.ExecutionMode; // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + }, + { + """ + package com.google.example; + + import com.google.common.base.Preconditions; + + import org.junit.runner.RunWith; + import org.junit.runners.JUnit4; // used with comment + + import java.util.List; + import javax.annotations.Nullable; // no empty line before + + import static org.junit.Assert.fail; + import static com.google.truth.Truth.assertThat; + // unused + + @RunWith( JUnit4.class ) public class SomeTest { + + void check(@Nullable List x) { + Preconditions.checkNodeNull(x); + } + + void f() { + List xs = null; + assertThat(xs).isNull(); + try { + check(xs); + fail(); + } catch (NullPointerException e) { + } + } + } + """ + } + }, + { + { + """ + import java.util.ArrayList; + import java.util.Collection; + /** {@link ArrayList#add} {@link Collection#remove(Object)} */ + class Test {} + """ + }, + { + """ + import java.util.ArrayList; + import java.util.Collection; + /** {@link ArrayList#add} {@link Collection#remove(Object)} */ + class Test {} + """ + } + }, + { + { + """ + import a.A; + import a.B; + import a.C; + class Test { + /** a + * {@link A} */ + void f() {} + } + """ + }, + { + """ + import a.A; + class Test { + /** a + * {@link A} */ + void f() {} + } + """ + } + }, + { + { + """ + import a.A;import a.B; + import a.C; // hello + class Test { + B b; + } + """ + }, + { + """ + import a.B; + // hello + class Test { + B b; + } + """ + } + }, + { + { + """ + import a.A; + import b.B; + import c.C; + import d.D; + import e.E; + import f.F; + import g.G; + import h.H; + /** + * {@link A} {@linkplain B} {@value D#FOO} + * + * @exception E + * @throws F + * @see C + * @see H#foo + * @see + */ + class Test { + } + """ + }, + { + """ + import a.A; + import b.B; + import c.C; + import d.D; + import e.E; + import f.F; + import h.H; + /** + * {@link A} {@linkplain B} {@value D#FOO} + * + * @exception E + * @throws F + * @see C + * @see H#foo + * @see + */ + class Test { + } + """ + } + }, + { + { + """ + import java.util.Map; + /** {@link Map.Entry#containsKey(Object)} } */ + class Test {} + """ + }, + { + """ + import java.util.Map; + /** {@link Map.Entry#containsKey(Object)} } */ + class Test {} + """ + } + }, + { + { + """ + /** {@link #containsKey(Object)} } */ + class Test {} + """ + }, + { + """ + /** {@link #containsKey(Object)} } */ + class Test {} + """ + } + }, + { + { + """ + import java.util.*; + class Test { + List xs; + } + """ + }, + { + """ + import java.util.*; + class Test { + List xs; + } + """ + } + }, + { + { + """ + package com.foo; + import static com.foo.Outer.A; + import com.foo.*; + import com.foo.B; + import com.bar.C; + class Test { + A a; + B b; + C c; + } + """ + }, + { + """ + package com.foo; + import static com.foo.Outer.A; + import com.foo.B; + import com.bar.C; + class Test { + A a; + B b; + C c; + } + """ + } + }, + { + { + """ + import java.util.Map; + import java.util.Map.Entry; + /** {@link #foo(Map.Entry[])} */ + public class Test {} + """ + }, + { + """ + import java.util.Map; + /** {@link #foo(Map.Entry[])} */ + public class Test {} + """ + } + }, + { + { + """ + import java.util.List; + import java.util.Collection; + /** {@link java.util.List#containsAll(Collection)} */ + public class Test {} + """ + }, + { + """ + import java.util.Collection; + /** {@link java.util.List#containsAll(Collection)} */ + public class Test {} + """ + } + }, + // { + // { + // """ + // package p; + // import java.lang.Foo; + // import java.lang2.Foo; + // import java.lang.Foo.Bar; + // import p.Baz; + // import p.Baz.Bork; + // public class Test implements java.lang.Foo, Bar, Baz, Bork {} + // """ + // }, + // { + // """ + // package p; + // import java.lang.Foo; + // import java.lang.Foo.Bar; + // import p.Baz; + // import p.Baz.Bork; + // public class Test implements Foo, Bar, Baz, Bork {} + // """ + // } + // }, + { + { + """ + import java.lang.Foo; + interface Test { private static void foo() {} } + """ + }, + {""" + interface Test { private static void foo() {} } + """} + }, + // { + // { + // """ + // package test.pkg; + // + // import static test.pkg.Constants.FOO; + // import static test.pkg.Constants2.FOO; + // import static test.pkg.Constants.BAR; + // + // import java.util.List; + // import java.util.ArrayList; + // + // public class Test { + // public static final String VALUE = Constants.FOO; + // } + // """ + // }, + // { + // """ + // package test.pkg; + // + // import static test.pkg.Constants.FOO; + // + // public class Test { + // public static final String VALUE = Constants.FOO; + // } + // """ + // } + // }, + { + { + """ + import java.util.List; + import java.util.Collections; + + class Test { + void foo() { + List list = Collections.emptyList(); + } + } + """ }, + { + """ + import java.util.List; + import java.util.Collections; + + class Test { + void foo() { + List list = Collections.emptyList(); + } + } + """ + } }, { { - "import java.util.ArrayList;", // - "import java.util.Collection;", - "/** {@link ArrayList#add} {@link Collection#remove(Object)} */", - "class Test {}", + """ + import java.util.List; + import java.util.ArrayList; + import java.util.Collections; + + class Test { + List list = new ArrayList<>(); + } + """ }, { - "import java.util.ArrayList;", // - "import java.util.Collection;", - "/** {@link ArrayList#add} {@link Collection#remove(Object)} */", - "class Test {}", + """ + import java.util.List; + import java.util.ArrayList; + + class Test { + List list = new ArrayList<>(); + } + """ + } + }, + { + { + """ + import static java.util.Collections.*; + import static java.util.Collections.emptyList; + + class Test { + void foo() { + emptyList(); + } + } + """ }, + { + """ + import static java.util.Collections.emptyList; + + class Test { + void foo() { + emptyList(); + } + } + """ + } }, { { - "import a.A;", - "import a.B;", - "import a.C;", - "class Test {", - " /** a", - " * {@link A} */", - " void f() {}", - "}", + """ + import java.util.List; + import java.util.function.Function; + + class Test { + Function, String> f; + } + """ }, { - "import a.A;", // - "class Test {", - " /** a", - " * {@link A} */", - " void f() {}", - "}", + """ + import java.util.List; + import java.util.function.Function; + + class Test { + Function, String> f; + } + """ + } + }, + { + { + """ + import a.Outer.Inner; + import a.Outer; + + class Test { + Inner i; + } + """ }, + { + """ + import a.Outer.Inner; + + class Test { + Inner i; + } + """ + } }, { { - "import a.A;import a.B;", // - "import a.C; // hello", - "class Test {", - " B b;", - "}", + """ + import java.util.List; + import java.lang.Deprecated; + + @Deprecated + class Test {} + """ }, + {""" + + @Deprecated + class Test {} + """} + }, + { { - "import a.B;", // - "// hello", - "class Test {", - " B b;", - "}", + """ + import java.util.HashMap; + + class Test { + java.util.Map map = new java.util.HashMap<>(); + } + """ }, + { + """ + + class Test { + java.util.Map map = new java.util.HashMap<>(); + } + """ + } }, { { - "import a.A;", - "import b.B;", - "import c.C;", - "import d.D;", - "import e.E;", - "import f.F;", - "import g.G;", - "import h.H;", - "/**", - " * {@link A} {@linkplain B} {@value D#FOO}", - " *", - " * @exception E", - " * @throws F", - " * @see C", - " * @see H#foo", - " * @see ", - " */", - "class Test {", - "}", + """ + import java.util.Map; + + class Test { + Map.Entry entry; + } + """ }, { - "import a.A;", - "import b.B;", - "import c.C;", - "import d.D;", - "import e.E;", - "import f.F;", - "import h.H;", - "/**", - " * {@link A} {@linkplain B} {@value D#FOO}", - " *", - " * @exception E", - " * @throws F", - " * @see C", - " * @see H#foo", - " * @see ", - " */", - "class Test {", - "}", + """ + import java.util.Map; + + class Test { + Map.Entry entry; + } + """ + } + }, + { + { + """ + import static java.lang.Math.*; + import static java.lang.Math.PI; + + class Test { + double r = PI; + } + """ }, + { + """ + import static java.lang.Math.PI; + + class Test { + double r = PI; + } + """ + } }, { { - "import java.util.Map;", "/** {@link Map.Entry#containsKey(Object)} } */", "class Test {}", + """ + import java.util.ArrayList; + + // This is a comment mentioning ArrayList + class Test {} + """ }, { - "import java.util.Map;", "/** {@link Map.Entry#containsKey(Object)} } */", "class Test {}", + """ + + // This is a comment mentioning ArrayList + class Test {} + """ + } + }, + { + { + """ + import java.util.List; + import java.util.ArrayList; + + // Preserve this comment + class Test { + List list; + } + """ }, + { + """ + import java.util.List; + + // Preserve this comment + class Test { + List list; + } + """ + } }, + // { + // { + // """ + // import pkg1.A; + // import pkg2.A; + // + // class Test { + // pkg1.A a1; + // pkg2.A a2; + // } + // """ + // }, + // { + // """ + // import pkg1.A; + // import pkg2.A; + // + // class Test { + // pkg1.A a1; + // pkg2.A a2; + // } + // """ + // } + // }, { { - "/** {@link #containsKey(Object)} } */", // - "class Test {}", + """ + import java.lang.annotation.*; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Test {} + """ }, { - "/** {@link #containsKey(Object)} } */", // - "class Test {}", + """ + import java.lang.annotation.*; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface Test {} + """ + } + }, + // { + // { + // """ + // import static pkg.Constants.*; + // import static pkg.Constants.VALUE; + // + // class Test { + // String s = VALUE; + // } + // """ + // }, + // { + // """ + // import static pkg.Constants.*; + // import static pkg.Constants.VALUE; + // + // class Test { + // String s = VALUE; + // } + // """ + // } + // }, + { + { + """ + import java.util.List; + import java.util.ArrayList; + + /** + * @see ArrayList + */ + class Test { + List list; + } + """ }, + { + """ + import java.util.List; + import java.util.ArrayList; + + /** + * @see ArrayList + */ + class Test { + List list; + } + """ + } }, { { - "import java.util.*;", // - "class Test {", - " List xs;", - "}", + """ + import java.util.Map; + import java.util.HashMap; + + class Test { + Map map = new HashMap<>() { + { + put("key", "value"); + } + }; + } + """ }, { - "import java.util.*;", // - "class Test {", - " List xs;", - "}", + """ + import java.util.Map; + import java.util.HashMap; + + class Test { + Map map = new HashMap<>() { + { + put("key", "value"); + } + }; + } + """ + } + }, + { + { + """ + import java.util.concurrent.*; + + class Test { + Future future; + } + """ }, + { + """ + import java.util.concurrent.*; + + class Test { + Future future; + } + """ + } }, { { - "package com.foo;", - "import static com.foo.Outer.A;", - "import com.foo.*;", - "import com.foo.B;", - "import com.bar.C;", - "class Test {", - " A a;", - " B b;", - " C c;", - "}", + """ + import javax.annotation.*; + + class Test { + @Nullable String s; + } + """ }, { - "package com.foo;", - "import static com.foo.Outer.A;", - "import com.bar.C;", - "class Test {", - " A a;", - " B b;", - " C c;", - "}", + """ + import javax.annotation.*; + + class Test { + @Nullable String s; + } + """ } }, + // { + // { + // """ + // import java.util.*; + // import java.util.stream.*; + // + // class Test { + // Stream stream; + // } + // """ + // }, + // { + // """ + // import java.util.stream.*; + // + // class Test { + // Stream stream; + // } + // """ + // } + // }, { { - "import java.util.Map;", // - "import java.util.Map.Entry;", - "/** {@link #foo(Map.Entry[])} */", - "public class Test {}", + """ + import java.time.*; + + class Test { + LocalDate date; + } + """ }, { - "import java.util.Map;", // - "/** {@link #foo(Map.Entry[])} */", - "public class Test {}", + """ + import java.time.*; + + class Test { + LocalDate date; + } + """ + } + }, + { + { + """ + import pkg.Enclosing.*; + + class Test { + Nested nested; + } + """ }, + { + """ + import pkg.Enclosing.*; + + class Test { + Nested nested; + } + """ + } + }, + { + { + """ + import pkg.Outer.Inner; + + class Test { + Inner inner; + } + """ + }, + { + """ + import pkg.Outer.Inner; + + class Test { + Inner inner; + } + """ + } }, { { - "import java.util.List;", - "import java.util.Collection;", - "/** {@link java.util.List#containsAll(Collection)} */", - "public class Test {}", + """ + import java.util.function.*; + + class Test { + Function f; + } + """ }, { - "import java.util.Collection;", - "/** {@link java.util.List#containsAll(Collection)} */", - "public class Test {}", + """ + import java.util.function.*; + + class Test { + Function f; + } + """ + } + }, + // { + // { + // """ + // import static pkg.Constants.*; + // + // class Test { + // String s = CONSTANT; + // } + // """ + // }, + // { + // """ + // import static pkg.Constants.*; + // + // class Test { + // String s = CONSTANT; + // } + // """ + // } + // }, + // { + // { + // """ + // import pkg.WithVeryLongName; + // import pkg.WithVeryLongName.*; + // + // class Test { + // WithVeryLongName w; + // } + // """ + // }, + // { + // """ + // import pkg.WithVeryLongName; + // + // class Test { + // WithVeryLongName w; + // } + // """ + // } + // }, + // { + // { + // """ + // import pkg.Generic; + // import pkg.Generic.*; + // + // class Test { + // Generic g; + // } + // """ + // }, + // { + // """ + // import pkg.Generic; + // + // class Test { + // Generic g; + // } + // """ + // } + // }, + { + { + """ + import java.util.*; + import java.util.concurrent.*; + + class Test { + List> list; + } + """ }, + { + """ + import java.util.*; + import java.util.concurrent.*; + + class Test { + List> list; + } + """ + } }, { { - "package p;", - "import java.lang.Foo;", - "import java.lang.Foo.Bar;", - "import p.Baz;", - "import p.Baz.Bork;", - "public class Test implements Foo, Bar, Baz, Bork {}", + """ + import pkg.Annotation; + import pkg.Other; + + @Annotation + class Test { + Other o; + } + """ }, { - "package p;", - "import java.lang.Foo.Bar;", - "import p.Baz.Bork;", - "public class Test implements Foo, Bar, Baz, Bork {}", + """ + import pkg.Annotation; + import pkg.Other; + + @Annotation + class Test { + Other o; + } + """ + } + }, + { + { + """ + import pkg.Record; + + record Test(Record r) {} + """ }, + { + """ + import pkg.Record; + + record Test(Record r) {} + """ + } }, + // { + // { + // """ + // import pkg.Sealed; + // import pkg.Permitted; + // + // sealed class Test permits Permitted {} + // """ + // }, + // { + // """ + // import pkg.Sealed; + // import pkg.Permitted; + // + // sealed class Test permits Permitted {} + // """ + // } + // }, { { - "import java.lang.Foo;", // - "interface Test { private static void foo() {} }", + """ + import pkg.WithTypeParameter; + + class Test { + WithTypeParameter w; + } + """ }, { - "interface Test { private static void foo() {} }", + """ + import pkg.WithTypeParameter; + + class Test { + WithTypeParameter w; + } + """ + } + }, + { + { + """ + import pkg.WithWildcard; + + class Test { + WithWildcard w; + } + """ }, + { + """ + import pkg.WithWildcard; + + class Test { + WithWildcard w; + } + """ + } }, + { + { + """ + import pkg.WithBounds; + + class Test { + WithBounds w; + } + """ + }, + { + """ + import pkg.WithBounds; + + class Test { + WithBounds w; + } + """ + } + } }; + ImmutableList.Builder builder = ImmutableList.builder(); for (String[][] inputAndOutput : inputsOutputs) { - assertThat(inputAndOutput.length).isEqualTo(2); - String[] input = inputAndOutput[0]; - String[] output = inputAndOutput[1]; - String[] parameters = { - Joiner.on('\n').join(input) + '\n', Joiner.on('\n').join(output) + '\n', - }; - builder.add(parameters); + assertThat(inputAndOutput).hasLength(2); + builder.add(new String[] { + Joiner.on('\n').join(inputAndOutput[0]) + '\n', Joiner.on('\n').join(inputAndOutput[1]) + '\n', + }); } return builder.build(); } @@ -277,7 +1279,7 @@ public RemoveUnusedImportsTest(String input, String expected) { } @TestTemplate - public void removeUnused() throws FormatterException { + public void removeUnused() { Truth.assertThat(RemoveUnusedImports.removeUnusedImports(input)).isEqualTo(expected); } }