diff --git a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java index 9d04790c..d56fa86e 100644 --- a/src/main/java/org/checkerframework/specimin/SpeciminRunner.java +++ b/src/main/java/org/checkerframework/specimin/SpeciminRunner.java @@ -356,23 +356,7 @@ private static void handleUnsolvedSymbolEnumeratorResult( Set createdDirectories, Formatter formatter) throws IOException { - Set usedPackages = new HashSet<>(); - for (CompilationUnit cu : sliceResult.solvedSlice()) { - if (cu.getPackageDeclaration().isEmpty()) { - usedPackages.add(""); - continue; - } - usedPackages.add(cu.getPackageDeclaration().get().getNameAsString()); - } - - for (String className : enumeratorResult.classNamesToFileContent().keySet()) { - int lastDot = className.lastIndexOf('.'); - if (lastDot < 0) { - usedPackages.add(""); - } else { - usedPackages.add(className.substring(0, lastDot)); - } - } + Set usedPackagesAndClasses = getUsedPackagesAndClasses(sliceResult, enumeratorResult); for (CompilationUnit original : sliceResult.solvedSlice()) { if (isEmptyCompilationUnit(original)) { @@ -436,7 +420,7 @@ private static void handleUnsolvedSymbolEnumeratorResult( writer.print( formatter.formatSourceAndFixImports( getCompilationUnitWithUnusedWildcardImportsRemoved( - getCompilationUnitWithCommentsTrimmed(cu), usedPackages) + getCompilationUnitWithCommentsTrimmed(cu), usedPackagesAndClasses) .toString())); } catch (IOException | FormatterException e) { System.out.println("failed to write output file " + targetOutputPath); @@ -471,6 +455,45 @@ private static void handleUnsolvedSymbolEnumeratorResult( } } } + /** + * Gets the packages and classes used in the given slice and unsolved symbol enumeration. + * + * @param sliceResult The slice + * @param enumeratorResult The unsolved enumeration + * @return The used packages and classes + */ + private static Set getUsedPackagesAndClasses( + SliceResult sliceResult, UnsolvedSymbolEnumeratorResult enumeratorResult) { + Set usedPackagesAndClasses = new HashSet<>(); + for (CompilationUnit cu : sliceResult.solvedSlice()) { + if (cu.getPackageDeclaration().isEmpty()) { + usedPackagesAndClasses.add(""); + continue; + } + usedPackagesAndClasses.add(cu.getPackageDeclaration().get().getNameAsString()); + + for (TypeDeclaration typeDecl : cu.findAll(TypeDeclaration.class)) { + String fqn = typeDecl.getFullyQualifiedName().orElse(null); + + if (fqn == null) { + continue; + } + + usedPackagesAndClasses.add(fqn); + } + } + + for (String className : enumeratorResult.classNamesToFileContent().keySet()) { + int lastDot = className.lastIndexOf('.'); + if (lastDot < 0) { + usedPackagesAndClasses.add(""); + } else { + usedPackagesAndClasses.add(className.substring(0, lastDot)); + usedPackagesAndClasses.add(className); + } + } + return usedPackagesAndClasses; + } /** * Checks that the root directory is specified correctly, by checking that the files for the @@ -576,22 +599,22 @@ private static void mapNodes(Node original, Node clone, IdentityHashMap usedPackages) { + CompilationUnit cu, Set usedPackagesAndClasses) { for (ImportDeclaration decl : List.copyOf(cu.getImports())) { if (!decl.isAsterisk()) { continue; } - String packageName = decl.getNameAsString(); + String qualifier = decl.getNameAsString(); - if (JavaLangUtils.inJdkPackage(packageName)) { + if (JavaLangUtils.inJdkPackage(qualifier)) { continue; } - if (!usedPackages.contains(packageName)) { + if (!usedPackagesAndClasses.contains(qualifier)) { decl.remove(); } } diff --git a/src/test/java/org/checkerframework/specimin/StaticWildcardClassImportTest.java b/src/test/java/org/checkerframework/specimin/StaticWildcardClassImportTest.java new file mode 100644 index 00000000..6a3f2ed6 --- /dev/null +++ b/src/test/java/org/checkerframework/specimin/StaticWildcardClassImportTest.java @@ -0,0 +1,18 @@ +package org.checkerframework.specimin; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +/** + * This test checks that static wildcard imports targeting classes that have used members in the + * target file are not removed. + */ +public class StaticWildcardClassImportTest { + @Test + public void runTest() throws IOException { + SpeciminTestExecutor.runTestWithoutJarPaths( + "staticwildcardclassimport", + new String[] {"com/example/Simple.java"}, + new String[] {"com.example.Simple#foo()"}); + } +} diff --git a/src/test/resources/staticwildcardclassimport/expected/com/example/Foo.java b/src/test/resources/staticwildcardclassimport/expected/com/example/Foo.java new file mode 100644 index 00000000..8f8040e8 --- /dev/null +++ b/src/test/resources/staticwildcardclassimport/expected/com/example/Foo.java @@ -0,0 +1,7 @@ +package com.example; + +public class Foo { + public static void staticFoo() { + throw new java.lang.Error(); + } +} \ No newline at end of file diff --git a/src/test/resources/staticwildcardclassimport/expected/com/example/Simple.java b/src/test/resources/staticwildcardclassimport/expected/com/example/Simple.java new file mode 100644 index 00000000..74a360af --- /dev/null +++ b/src/test/resources/staticwildcardclassimport/expected/com/example/Simple.java @@ -0,0 +1,9 @@ +package com.example; + +import static com.example.Foo.*; + +public class Simple { + void foo() { + staticFoo(); + } +} \ No newline at end of file diff --git a/src/test/resources/staticwildcardclassimport/input/com/example/Foo.java b/src/test/resources/staticwildcardclassimport/input/com/example/Foo.java new file mode 100644 index 00000000..9cd09389 --- /dev/null +++ b/src/test/resources/staticwildcardclassimport/input/com/example/Foo.java @@ -0,0 +1,5 @@ +package com.example; + +public class Foo { + public static void staticFoo() { } +} \ No newline at end of file diff --git a/src/test/resources/staticwildcardclassimport/input/com/example/Simple.java b/src/test/resources/staticwildcardclassimport/input/com/example/Simple.java new file mode 100644 index 00000000..74a360af --- /dev/null +++ b/src/test/resources/staticwildcardclassimport/input/com/example/Simple.java @@ -0,0 +1,9 @@ +package com.example; + +import static com.example.Foo.*; + +public class Simple { + void foo() { + staticFoo(); + } +} \ No newline at end of file