diff --git a/error-prone-contrib/pom.xml b/error-prone-contrib/pom.xml index 7c12e0c3e9..f268bbeab3 100644 --- a/error-prone-contrib/pom.xml +++ b/error-prone-contrib/pom.xml @@ -72,6 +72,11 @@ auto-service-annotations provided + + com.google.auto.value + auto-value-annotations + provided + com.google.googlejavaformat google-java-format diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java new file mode 100644 index 0000000000..4c84992619 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java @@ -0,0 +1,211 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.CUSTOM; +import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; +import static com.google.errorprone.BugPattern.StandardTags.STYLE; +import static tech.picnic.errorprone.bugpatterns.StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS; +import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; + +import com.google.auto.service.AutoService; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.ImmutableTable; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.code.Symbol; +import org.jspecify.annotations.Nullable; +import tech.picnic.errorprone.bugpatterns.util.SourceCode; + +/** + * A {@link BugChecker} that flags static imports of type members that should *not* be statically + * imported. + */ +// XXX: This check is closely linked to `StaticImport`. Consider merging the two. +// XXX: Add suppression support. If qualification of one more more identifiers is suppressed, then +// the associated static import should *not* be removed. +// XXX: Also introduce logic that disallows statically importing `ZoneOffset.ofHours` and other +// `ofXXX`-style methods. +@AutoService(BugChecker.class) +@BugPattern( + summary = "Member should not be statically imported", + link = BUG_PATTERNS_BASE_URL + "NonStaticImport", + linkType = CUSTOM, + severity = SUGGESTION, + tags = STYLE) +public final class NonStaticImport extends BugChecker implements CompilationUnitTreeMatcher { + private static final long serialVersionUID = 1L; + + /** + * Types whose members should not be statically imported, unless exempted by {@link + * StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS}. + * + *

Types listed here should be mutually exclusive with {@link + * StaticImport#STATIC_IMPORT_CANDIDATE_TYPES}. + */ + @VisibleForTesting + static final ImmutableSet NON_STATIC_IMPORT_CANDIDATE_TYPES = + ImmutableSet.of("com.google.common.base.Strings", "java.time.Clock", "java.time.ZoneOffset"); + + /** + * Type members that should never be statically imported. + * + *

Please note that: + * + *

+ */ + // XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any + // method name that could be considered "too vague" or could conceivably mean something else in a + // specific context is left out. + static final ImmutableSetMultimap NON_STATIC_IMPORT_CANDIDATE_MEMBERS = + ImmutableSetMultimap.builder() + .put("com.google.common.base.Predicates", "contains") + .putAll( + "java.util.Collections", + "addAll", + "copy", + "fill", + "list", + "max", + "min", + "nCopies", + "rotate", + "sort", + "swap") + .put("java.util.Locale", "ROOT") + .putAll("java.util.regex.Pattern", "compile", "matches", "quote") + .put("org.springframework.http.MediaType", "ALL") + .build(); + + /** + * Identifiers that should never be statically imported. + * + *

Please note that: + * + *

    + *
  • Identifiers listed by {@link StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS} should be + * mutually exclusive with identifiers listed here. + *
  • This list should contain a superset of the identifiers flagged by {@link + * com.google.errorprone.bugpatterns.BadImport}. + *
+ */ + static final ImmutableSet NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS = + ImmutableSet.of( + "builder", + "copyOf", + "create", + "empty", + "from", + "getDefaultInstance", + "INSTANCE", + "MAX", + "MAX_VALUE", + "MIN", + "MIN_VALUE", + "newBuilder", + "newInstance", + "of", + "ONE", + "parse", + "valueOf", + "ZERO"); + + /** Instantiates a new {@link NonStaticImport} instance. */ + public NonStaticImport() {} + + @Override + public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { + ImmutableTable undesiredStaticImports = + getUndesiredStaticImports(tree, state); + + if (!undesiredStaticImports.isEmpty()) { + replaceUndesiredStaticImportUsages(tree, undesiredStaticImports, state); + + for (UndesiredStaticImport staticImport : undesiredStaticImports.values()) { + state.reportMatch( + describeMatch(staticImport.importTree(), staticImport.fixBuilder().build())); + } + } + + /* Any violations have been flagged against the offending static import statement. */ + return Description.NO_MATCH; + } + + private static ImmutableTable getUndesiredStaticImports( + CompilationUnitTree tree, VisitorState state) { + ImmutableTable.Builder imports = + ImmutableTable.builder(); + for (ImportTree importTree : tree.getImports()) { + Tree qualifiedIdentifier = importTree.getQualifiedIdentifier(); + if (importTree.isStatic() && qualifiedIdentifier instanceof MemberSelectTree) { + MemberSelectTree memberSelectTree = (MemberSelectTree) qualifiedIdentifier; + String type = SourceCode.treeToString(memberSelectTree.getExpression(), state); + String member = memberSelectTree.getIdentifier().toString(); + if (shouldNotBeStaticallyImported(type, member)) { + imports.put( + type, + member, + new AutoValue_NonStaticImport_UndesiredStaticImport( + importTree, SuggestedFix.builder().removeStaticImport(type + '.' + member))); + } + } + } + + return imports.build(); + } + + private static boolean shouldNotBeStaticallyImported(String type, String member) { + return (NON_STATIC_IMPORT_CANDIDATE_TYPES.contains(type) + && !STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member)) + || NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member) + || NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(member); + } + + private static void replaceUndesiredStaticImportUsages( + CompilationUnitTree tree, + ImmutableTable undesiredStaticImports, + VisitorState state) { + new TreeScanner<@Nullable Void, @Nullable Void>() { + @Override + public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) { + Symbol symbol = ASTHelpers.getSymbol(node); + if (symbol != null) { + UndesiredStaticImport staticImport = + undesiredStaticImports.get( + symbol.owner.getQualifiedName().toString(), symbol.name.toString()); + if (staticImport != null) { + SuggestedFix.Builder fix = staticImport.fixBuilder(); + fix.prefixWith(node, SuggestedFixes.qualifyType(state, fix, symbol.owner) + '.'); + } + } + + return super.visitIdentifier(node, unused); + } + }.scan(tree, null); + } + + @AutoValue + abstract static class UndesiredStaticImport { + abstract ImportTree importTree(); + + abstract SuggestedFix.Builder fixBuilder(); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StaticImport.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StaticImport.java index c5b3e52427..3adb431a9c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StaticImport.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/StaticImport.java @@ -2,8 +2,10 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM; import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; -import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION; +import static com.google.errorprone.BugPattern.StandardTags.STYLE; import static java.util.Objects.requireNonNull; +import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS; +import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS; import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; import com.google.auto.service.AutoService; @@ -27,35 +29,30 @@ import com.sun.tools.javac.code.Type; import java.util.Optional; -/** - * A {@link BugChecker} that flags methods and constants that can and should be statically imported. - */ +/** A {@link BugChecker} that flags type members that can and should be statically imported. */ +// XXX: This check is closely linked to `NonStaticImport`. Consider merging the two. // XXX: Tricky cases: // - `org.springframework.http.HttpStatus` (not always an improvement, and `valueOf` must // certainly be excluded) // - `com.google.common.collect.Tables` -// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN"}` -// XXX: Also introduce a check that disallows static imports of certain methods. Candidates: -// - `com.google.common.base.Strings` -// - `java.util.Optional.empty` -// - `java.util.Locale.ROOT` -// - `ZoneOffset.ofHours` and other `ofXXX`-style methods. -// - `java.time.Clock`. -// - Several other `java.time` classes. -// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`. +// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN}` @AutoService(BugChecker.class) @BugPattern( summary = "Identifier should be statically imported", link = BUG_PATTERNS_BASE_URL + "StaticImport", linkType = CUSTOM, severity = SUGGESTION, - tags = SIMPLIFICATION) + tags = STYLE) public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher { private static final long serialVersionUID = 1L; /** * Types whose members should be statically imported, unless exempted by {@link - * #STATIC_IMPORT_EXEMPTED_MEMBERS} or {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS}. + * NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS} or {@link + * NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}. + * + *

Types listed here should be mutually exclusive with {@link + * NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_TYPES}. */ @VisibleForTesting static final ImmutableSet STATIC_IMPORT_CANDIDATE_TYPES = @@ -104,8 +101,20 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa "reactor.function.TupleUtils", "tech.picnic.errorprone.bugpatterns.util.MoreTypes"); - /** Type members that should be statically imported. */ - @VisibleForTesting + /** + * Type members that should be statically imported. + * + *

Please note that: + * + *

    + *
  • Types listed by {@link #STATIC_IMPORT_CANDIDATE_TYPES} should be omitted from this + * collection. + *
  • This collection should be mutually exclusive with {@link + * NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS}. + *
  • This collection should not list members contained in {@link + * NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}. + *
+ */ static final ImmutableSetMultimap STATIC_IMPORT_CANDIDATE_MEMBERS = ImmutableSetMultimap.builder() .putAll( @@ -143,55 +152,6 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa .putAll("com.google.common.collect.Comparators", "emptiesFirst", "emptiesLast") .build(); - /** - * Type members that should never be statically imported. - * - *

Identifiers listed by {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS} should be omitted from - * this collection. - */ - // XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any - // method name that could be considered "too vague" or could conceivably mean something else in a - // specific context is left out. - @VisibleForTesting - static final ImmutableSetMultimap STATIC_IMPORT_EXEMPTED_MEMBERS = - ImmutableSetMultimap.builder() - .put("com.google.common.base.Predicates", "contains") - .put("com.mongodb.client.model.Filters", "empty") - .putAll( - "java.util.Collections", - "addAll", - "copy", - "fill", - "list", - "max", - "min", - "nCopies", - "rotate", - "sort", - "swap") - .putAll("java.util.regex.Pattern", "compile", "matches", "quote") - .put("org.springframework.http.MediaType", "ALL") - .build(); - - /** - * Identifiers that should never be statically imported. - * - *

This should be a superset of the identifiers flagged by {@link - * com.google.errorprone.bugpatterns.BadImport}. - */ - @VisibleForTesting - static final ImmutableSet STATIC_IMPORT_EXEMPTED_IDENTIFIERS = - ImmutableSet.of( - "builder", - "create", - "copyOf", - "from", - "getDefaultInstance", - "INSTANCE", - "newBuilder", - "of", - "valueOf"); - /** Instantiates a new {@link StaticImport} instance. */ public StaticImport() {} @@ -229,13 +189,13 @@ private static boolean isCandidateContext(VisitorState state) { private static boolean isCandidate(MemberSelectTree tree) { String identifier = tree.getIdentifier().toString(); - if (STATIC_IMPORT_EXEMPTED_IDENTIFIERS.contains(identifier)) { + if (NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(identifier)) { return false; } Type type = ASTHelpers.getType(tree.getExpression()); return type != null - && !STATIC_IMPORT_EXEMPTED_MEMBERS.containsEntry(type.toString(), identifier); + && !NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type.toString(), identifier); } private static Optional getCandidateSimpleName(StaticImportInfo importInfo) { diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/NonStaticImportTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/NonStaticImportTest.java new file mode 100644 index 0000000000..86ef74d4e6 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/NonStaticImportTest.java @@ -0,0 +1,196 @@ +package tech.picnic.errorprone.bugpatterns; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; +import com.google.errorprone.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +final class NonStaticImportTest { + @Test + void candidateTypesDoNotClash() { + assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES) + .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES); + } + + @Test + void candidateMembersAreNotRedundant() { + assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.keySet()) + .doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES); + + assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.values()) + .doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS); + } + + @Test + void candidateMembersDoNotClash() { + assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.entries()) + .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.entries()); + } + + @Test + void candidateIdentifiersDoNotClash() { + assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS) + .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.values()); + } + + @Test + void identification() { + CompilationTestHelper.newInstance(NonStaticImport.class, getClass()) + .addSourceLines( + "pkg/A.java", + "package pkg;", + "", + "// BUG: Diagnostic contains:", + "import static com.google.common.base.Strings.nullToEmpty;", + "// BUG: Diagnostic contains:", + "import static com.google.common.collect.ImmutableList.copyOf;", + "// BUG: Diagnostic contains:", + "import static java.lang.Integer.MAX_VALUE;", + "// BUG: Diagnostic contains:", + "import static java.lang.Integer.MIN_VALUE;", + "// BUG: Diagnostic contains:", + "import static java.time.Clock.systemUTC;", + "// BUG: Diagnostic contains:", + "import static java.time.Instant.MIN;", + "// BUG: Diagnostic contains:", + "import static java.time.ZoneOffset.SHORT_IDS;", + "import static java.time.ZoneOffset.UTC;", + "// BUG: Diagnostic contains:", + "import static java.util.Collections.min;", + "import static java.util.Locale.ENGLISH;", + "// BUG: Diagnostic contains:", + "import static java.util.Locale.ROOT;", + "// BUG: Diagnostic contains:", + "import static java.util.Optional.empty;", + "import static pkg.A.WithMethodThatIsSelectivelyFlagged.list;", + "", + "import com.google.common.collect.ImmutableList;", + "import com.google.common.collect.ImmutableSet;", + "import java.time.Instant;", + "import java.time.ZoneOffset;", + "import java.util.Locale;", + "import java.util.Map;", + "import pkg.A.Wrapper.ZERO;", + "", + "class A {", + " private Integer MIN_VALUE = 12;", + "", + " void m() {", + " nullToEmpty(null);", + " copyOf(ImmutableList.of());", + " int max = MAX_VALUE;", + " int min = MIN_VALUE;", + " systemUTC();", + " Instant minInstant = MIN;", + " Map shortIds = SHORT_IDS;", + " ZoneOffset utc = UTC;", + " min(ImmutableSet.of());", + " Locale english = ENGLISH;", + " Locale root = ROOT;", + " empty();", + "", + " list();", + " new ZERO();", + " }", + "", + " static final class WithMethodThatIsSelectivelyFlagged {", + " static ImmutableList list() {", + " return ImmutableList.of();", + " }", + " }", + "", + " static final class Wrapper {", + " static final class ZERO {}", + " }", + "}") + .doTest(); + } + + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(NonStaticImport.class, getClass()) + .addInputLines( + "A.java", + "import static com.google.common.collect.ImmutableList.copyOf;", + "import static com.google.common.collect.ImmutableSet.of;", + "import static java.time.Clock.systemUTC;", + "import static java.time.Instant.MAX;", + "import static java.time.Instant.MIN;", + "import static java.util.Collections.min;", + "import static java.util.Locale.ROOT;", + "import static java.util.Optional.empty;", + "", + "import com.google.common.collect.ImmutableList;", + "import com.google.common.collect.ImmutableSet;", + "import java.time.Clock;", + "import java.time.Instant;", + "import java.util.Locale;", + "import java.util.Optional;", + "", + "class A {", + " void m() {", + " systemUTC();", + " Clock.systemUTC();", + "", + " Optional o1 = empty();", + " Optional o2 = Optional.empty();", + "", + " Object l1 = copyOf(ImmutableList.of());", + " Object l2 = ImmutableList.copyOf(ImmutableList.of());", + "", + " Locale lo1 = ROOT;", + " Locale lo2 = Locale.ROOT;", + "", + " Instant i1 = MIN;", + " Instant i2 = MAX;", + "", + " ImmutableSet.of(min(of()));", + " }", + "", + " private static final class WithCustomConstant {", + " private static final Instant MIN = Instant.EPOCH;", + " private static final Instant OTHER = MIN;", + " private static final Instant OTHER_MAX = MAX;", + " }", + "}") + .addOutputLines( + "A.java", + "import com.google.common.collect.ImmutableList;", + "import com.google.common.collect.ImmutableSet;", + "import java.time.Clock;", + "import java.time.Instant;", + "import java.util.Collections;", + "import java.util.Locale;", + "import java.util.Optional;", + "", + "class A {", + " void m() {", + " Clock.systemUTC();", + " Clock.systemUTC();", + "", + " Optional o1 = Optional.empty();", + " Optional o2 = Optional.empty();", + "", + " Object l1 = ImmutableList.copyOf(ImmutableList.of());", + " Object l2 = ImmutableList.copyOf(ImmutableList.of());", + "", + " Locale lo1 = Locale.ROOT;", + " Locale lo2 = Locale.ROOT;", + "", + " Instant i1 = Instant.MIN;", + " Instant i2 = Instant.MAX;", + "", + " ImmutableSet.of(Collections.min(ImmutableSet.of()));", + " }", + "", + " private static final class WithCustomConstant {", + " private static final Instant MIN = Instant.EPOCH;", + " private static final Instant OTHER = MIN;", + " private static final Instant OTHER_MAX = Instant.MAX;", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/StaticImportTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/StaticImportTest.java index d876f41082..bcfb097d39 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/StaticImportTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/StaticImportTest.java @@ -9,21 +9,24 @@ final class StaticImportTest { @Test - void candidateMethodsAreNotRedundant() { - assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet()) - .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES); + void candidateTypesDoNotClash() { + assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES) + .doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES); } @Test - void exemptedMembersAreNotVacuous() { - assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.keySet()) - .isSubsetOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES); + void candidateMembersAreNotRedundant() { + assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet()) + .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES); } @Test - void exemptedMembersAreNotRedundant() { - assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.values()) - .doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_EXEMPTED_IDENTIFIERS); + void candidateMembersDoNotClash() { + assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.entries()) + .doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.entries()); + + assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.values()) + .doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS); } @Test