mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
5 Commits
v0.18.0
...
Kamil-Gaba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301ec17f6d | ||
|
|
83da373fe3 | ||
|
|
88cabab74c | ||
|
|
63d351c54a | ||
|
|
26a04b9b6d |
@@ -0,0 +1,111 @@
|
||||
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.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.List;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.mockito.Mockito;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags multiple usages of {@link Mockito#verifyNoInteractions} in favor
|
||||
* of one call with varargs.
|
||||
*
|
||||
* <p>Multiple calls of {@link Mockito#verifyNoInteractions} can make the code more verbose than
|
||||
* necessary. Instead of multiple calls, because {@link Mockito#verifyNoInteractions} accepts
|
||||
* varargs, one call should be preferred.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer one call to `verifyNoInteractions(varargs...)` over multiple calls",
|
||||
link = BUG_PATTERNS_BASE_URL + "MockitoVerifyNoInteractionsUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class MockitoVerifyNoInteractionsUsage extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> VERIFY_NO_INTERACTIONS =
|
||||
staticMethod().onClass("org.mockito.Mockito").named("verifyNoInteractions");
|
||||
|
||||
/** Instantiates a new {@link MockitoVerifyNoInteractionsUsage} instance. */
|
||||
public MockitoVerifyNoInteractionsUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!TEST_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
ImmutableList<MethodInvocationTree> verifyNoInteractionsInvocations =
|
||||
getVerifyNoInteractionsInvocations(tree, state);
|
||||
if (verifyNoInteractionsInvocations.size() < 2) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
String combinedArgument =
|
||||
verifyNoInteractionsInvocations.stream()
|
||||
.map(MethodInvocationTree::getArguments)
|
||||
.flatMap(List::stream)
|
||||
.map(Object::toString)
|
||||
.collect(joining(", "));
|
||||
|
||||
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
|
||||
MethodInvocationTree lastInvocation =
|
||||
verifyNoInteractionsInvocations.get(verifyNoInteractionsInvocations.size() - 1);
|
||||
verifyNoInteractionsInvocations.forEach(
|
||||
invocationTree -> {
|
||||
if (!invocationTree.equals(lastInvocation)) {
|
||||
fixBuilder.replace(
|
||||
ASTHelpers.getStartPosition(invocationTree),
|
||||
state.getEndPosition(invocationTree) + 1,
|
||||
"");
|
||||
}
|
||||
});
|
||||
|
||||
String callAsString = SourceCode.treeToString(lastInvocation, state);
|
||||
fixBuilder.replace(
|
||||
lastInvocation,
|
||||
callAsString.startsWith("Mockito.")
|
||||
? "Mockito.verifyNoInteractions(" + combinedArgument + ")"
|
||||
: "verifyNoInteractions(" + combinedArgument + ")");
|
||||
|
||||
return describeMatch(tree, fixBuilder.build());
|
||||
}
|
||||
|
||||
private static ImmutableList<MethodInvocationTree> getVerifyNoInteractionsInvocations(
|
||||
MethodTree methodTree, VisitorState state) {
|
||||
ImmutableList.Builder<MethodInvocationTree> invocationTreeBuilder = ImmutableList.builder();
|
||||
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public @Nullable Void visitMethodInvocation(
|
||||
MethodInvocationTree node, @Nullable Void unused) {
|
||||
if (VERIFY_NO_INTERACTIONS.matches(node, state)) {
|
||||
invocationTreeBuilder.add(node);
|
||||
}
|
||||
return super.visitMethodInvocation(node, unused);
|
||||
}
|
||||
}.scan(methodTree, null);
|
||||
|
||||
return invocationTreeBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class MockitoVerifyNoInteractionsUsageTest {
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
MockitoVerifyNoInteractionsUsage.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(MockitoVerifyNoInteractionsUsage.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void a() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void b() {",
|
||||
" Object mock = mock(Object.class);",
|
||||
" verifyNoInteractions(mock);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void c() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1, mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void d() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void e() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void f() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock2, mock3);",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock4);",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock5);",
|
||||
" }",
|
||||
"",
|
||||
" void testNonTestMethod() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.addSourceLines(
|
||||
"B.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.mockito.Mockito;",
|
||||
"",
|
||||
"class B {",
|
||||
" private static void verifyNoInteractions(Object... objects) {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void a() {",
|
||||
" Object mock = mock(Object.class);",
|
||||
" verifyNoInteractions(mock);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void b() {",
|
||||
" Object mock = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void c() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1, mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void d() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void e() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" void f() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" Mockito.verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void g() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock1);",
|
||||
" Mockito.verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"",
|
||||
" @Test",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void h() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock2, mock3);",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock4);",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock5);",
|
||||
" }",
|
||||
"",
|
||||
" void testNonTestMethod() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock1);",
|
||||
" Mockito.verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceSequentialCalls() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
"",
|
||||
" verifyNoInteractions(mock1, mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceNonSequentialCalls() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
"",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock1, mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceComplexCalls() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock2, mock3);",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
" verifyNoInteractions(mock4);",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock5);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.verifyNoInteractions;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
"",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
"",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock2, mock3, mock4, mock5);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceStaticCalls() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.mockito.Mockito;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock1);",
|
||||
" Mockito.verifyNoInteractions(mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.mockito.Mockito;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
"",
|
||||
" Mockito.verifyNoInteractions(mock1, mock2);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replaceComplexStaticCalls() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.mockito.Mockito;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static void verifyNoInteractions(Object... objects) {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock2, mock3);",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock4);",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock5);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static org.assertj.core.api.Assertions.assertThat;",
|
||||
"import static org.mockito.Mockito.mock;",
|
||||
"import static org.mockito.Mockito.when;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.mockito.Mockito;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static void verifyNoInteractions(Object... objects) {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void m() {",
|
||||
" Object mock1 = mock(Object.class);",
|
||||
" Object mock2 = mock(Object.class);",
|
||||
" when(mock1.toString()).thenReturn(\"test\");",
|
||||
" Object mock3 = mock(Object.class);",
|
||||
"",
|
||||
" Object mock4 = mock(Object.class);",
|
||||
" String str = mock1.toString();",
|
||||
" Object mock5 = mock(Object.class);",
|
||||
" Mockito.verifyNoInteractions(mock2, mock3, mock4);",
|
||||
" assertThat(str).isEqualTo(\"test\");",
|
||||
" verifyNoInteractions(mock5);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user