diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java b/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java index 4ce757066b7..7bbbbccfa47 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/UnusedVariable.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; @@ -35,6 +36,8 @@ import static com.google.errorprone.util.ASTHelpers.isStatic; import static com.google.errorprone.util.ASTHelpers.isSubtype; import static com.google.errorprone.util.ASTHelpers.shouldKeep; +import static com.google.errorprone.util.MoreAnnotations.asStrings; +import static com.google.errorprone.util.MoreAnnotations.getAnnotationValue; import static com.google.errorprone.util.SideEffectAnalysis.hasSideEffect; import static com.sun.source.tree.Tree.Kind.POSTFIX_DECREMENT; import static com.sun.source.tree.Tree.Kind.POSTFIX_INCREMENT; @@ -245,7 +248,8 @@ public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState s // appropriate fixes for them. ListMultimap usageSites = variableFinder.usageSites; - FilterUsedVariables filterUsedVariables = new FilterUsedVariables(unusedElements, usageSites); + FilterUsedVariables filterUsedVariables = + new FilterUsedVariables(state, unusedElements, usageSites); filterUsedVariables.scan(state.getPath(), null); // Keeps track of whether a symbol was _ever_ used (between reassignments). @@ -816,6 +820,8 @@ private static final class FilterUsedVariables extends TreePathScanner unusedElements; private final ListMultimap usageSites; @@ -828,7 +834,10 @@ private static final class FilterUsedVariables extends TreePathScanner declarationSites; private FilterUsedVariables( - Map unusedElements, ListMultimap usageSites) { + VisitorState state, + Map unusedElements, + ListMultimap usageSites) { + this.state = state; this.unusedElements = unusedElements; this.usageSites = usageSites; this.declarationSites = ImmutableMap.copyOf(unusedElements); @@ -1066,6 +1075,40 @@ public Void visitMethodInvocation(MethodInvocationTree tree, Void unused) { inMethodCall--; return null; } + + @Override + public Void visitMethod(final MethodTree node, final Void unused) { + handleFieldSource(node); + return super.visitMethod(node, unused); + } + + /** + * If a method is annotated with @FieldSource, the annotation value refers to a field that is + * used reflectively to supply test parameters, so that field should not be considered unused. + */ + private void handleFieldSource(MethodTree tree) { + MethodSymbol sym = getSymbol(tree); + Name name = ORG_JUNIT_JUPITER_PARAMS_PROVIDER_FIELDSOURCE.get(state); + sym.getRawAttributes().stream() + .filter(a -> a.type.tsym.getQualifiedName().equals(name)) + .findAny() + // get the annotation value array as a set of Names + .flatMap(a -> getAnnotationValue(a, "value")) + .map(y -> asStrings(y).map(state::getName).map(Name::toString).collect(toImmutableSet())) + // remove all potentially unused methods referenced by the @FieldSource + .ifPresent( + referencedNames -> + unusedElements + .entrySet() + .removeIf( + e -> { + Symbol unusedSym = e.getKey(); + String simpleName = unusedSym.getSimpleName().toString(); + return referencedNames.contains(simpleName) + || referencedNames.contains( + unusedSym.owner.getQualifiedName() + "#" + simpleName); + })); + } } @AutoValue @@ -1103,4 +1146,9 @@ private static UnusedSpec of( private static final Supplier PARCELABLE_CREATOR = VisitorState.memoize(state -> state.getTypeFromString("android.os.Parcelable.Creator")); + + private static final Supplier + ORG_JUNIT_JUPITER_PARAMS_PROVIDER_FIELDSOURCE = + VisitorState.memoize( + state -> state.getName("org.junit.jupiter.params.provider.FieldSource")); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/UnusedVariableTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/UnusedVariableTest.java index bc8fe4515c5..a46e2004668 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/UnusedVariableTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/UnusedVariableTest.java @@ -1922,4 +1922,32 @@ public void foo(int xs) {} .expectUnchanged() .doTest(); } + + @Test + public void fieldSource() { + helper + .addSourceLines( + "FieldSource.java", + """ + package org.junit.jupiter.params.provider; + + public @interface FieldSource { + String[] value(); + } + """) + .addSourceLines( + "Test.java", + """ + import java.util.List; + import org.junit.jupiter.params.provider.FieldSource; + + class Test { + @FieldSource("parameters") + void test() {} + + private static final List parameters = List.of("apple", "banana"); + } + """) + .doTest(); + } }