19
19
import static com .google .common .base .Preconditions .checkArgument ;
20
20
import static com .google .common .base .Suppliers .memoize ;
21
21
import static com .google .common .collect .ImmutableList .toImmutableList ;
22
+ import static com .google .common .collect .ImmutableSet .toImmutableSet ;
23
+ import static com .google .common .collect .Lists .reverse ;
22
24
import static com .google .common .collect .Multimaps .toMultimap ;
23
25
import static com .google .errorprone .fixes .SuggestedFix .delete ;
26
+ import static com .google .errorprone .fixes .SuggestedFix .postfixWith ;
24
27
import static com .google .errorprone .fixes .SuggestedFix .prefixWith ;
28
+ import static com .google .errorprone .fixes .SuggestedFixes .qualifyStaticImport ;
25
29
import static com .google .errorprone .matchers .Description .NO_MATCH ;
26
30
import static com .google .errorprone .matchers .Matchers .allOf ;
27
31
import static com .google .errorprone .matchers .Matchers .isThrowingFunctionalInterface ;
28
32
import static com .google .errorprone .matchers .Matchers .not ;
29
33
import static com .google .errorprone .matchers .Matchers .parentNode ;
34
+ import static com .google .errorprone .matchers .method .MethodMatchers .staticMethod ;
30
35
import static com .google .errorprone .util .ASTHelpers .enclosingClass ;
31
36
import static com .google .errorprone .util .ASTHelpers .getResultType ;
32
37
import static com .google .errorprone .util .ASTHelpers .getRootAssignable ;
36
41
import static com .google .errorprone .util .ASTHelpers .getUpperBound ;
37
42
import static com .google .errorprone .util .ASTHelpers .isSubtype ;
38
43
import static com .google .errorprone .util .ASTHelpers .isVoidType ;
44
+ import static com .google .errorprone .util .ASTHelpers .matchingMethods ;
45
+ import static com .google .errorprone .util .FindIdentifiers .findAllIdents ;
39
46
import static com .sun .source .tree .Tree .Kind .EXPRESSION_STATEMENT ;
47
+ import static com .sun .source .tree .Tree .Kind .MEMBER_SELECT ;
40
48
import static com .sun .source .tree .Tree .Kind .METHOD_INVOCATION ;
41
49
import static com .sun .source .tree .Tree .Kind .NEW_CLASS ;
50
+ import static com .sun .tools .javac .parser .Tokens .TokenKind .RPAREN ;
42
51
import static java .lang .String .format ;
52
+ import static java .util .stream .IntStream .range ;
53
+ import static java .util .stream .Stream .concat ;
43
54
44
55
import com .google .common .collect .ImmutableList ;
45
56
import com .google .common .collect .ImmutableListMultimap ;
63
74
import com .sun .source .tree .LambdaExpressionTree ;
64
75
import com .sun .source .tree .MemberReferenceTree ;
65
76
import com .sun .source .tree .MemberReferenceTree .ReferenceMode ;
77
+ import com .sun .source .tree .MemberSelectTree ;
66
78
import com .sun .source .tree .MethodInvocationTree ;
67
79
import com .sun .source .tree .MethodTree ;
68
80
import com .sun .source .tree .NewClassTree ;
81
93
import java .util .Queue ;
82
94
import java .util .Set ;
83
95
import java .util .function .Supplier ;
96
+ import java .util .stream .Stream ;
84
97
import javax .lang .model .element .Name ;
85
98
import javax .lang .model .type .TypeKind ;
86
99
@@ -256,6 +269,64 @@ final ImmutableList<Fix> fixesAtCallSite(ExpressionTree invocationTree, VisitorS
256
269
* Luckily, they're not a ton harder to include than plain code comments would be.
257
270
*/
258
271
ImmutableMap .Builder <String , SuggestedFix > fixes = ImmutableMap .builder ();
272
+ if (MOCKITO_VERIFY .matches (invocationTree , state )) {
273
+ ExpressionTree maybeCallToMock =
274
+ ((MethodInvocationTree ) invocationTree ).getArguments ().get (0 );
275
+ if (maybeCallToMock .getKind () == METHOD_INVOCATION ) {
276
+ ExpressionTree maybeMethodSelectOnMock =
277
+ ((MethodInvocationTree ) maybeCallToMock ).getMethodSelect ();
278
+ if (maybeMethodSelectOnMock .getKind () == MEMBER_SELECT ) {
279
+ MemberSelectTree maybeSelectOnMock = (MemberSelectTree ) maybeMethodSelectOnMock ;
280
+ // For this suggestion, we want to move the closing parenthesis:
281
+ // verify(foo .bar())
282
+ // ^ v
283
+ // +------+
284
+ //
285
+ // The result is:
286
+ // verify(foo).bar()
287
+ //
288
+ // TODO(cpovirk): Suggest this only if `foo` looks like an actual mock object.
289
+ SuggestedFix .Builder fix = SuggestedFix .builder ();
290
+ fix .postfixWith (maybeSelectOnMock .getExpression (), ")" );
291
+ int closingParen =
292
+ reverse (state .getOffsetTokensForNode (invocationTree )).stream ()
293
+ .filter (t -> t .kind () == RPAREN )
294
+ .findFirst ()
295
+ .get ()
296
+ .pos ();
297
+ fix .replace (closingParen , closingParen + 1 , "" );
298
+ fixes .put (
299
+ format ("Verify that %s was called" , maybeSelectOnMock .getIdentifier ()), fix .build ());
300
+ }
301
+ }
302
+ }
303
+ if (resultType != null && resultType .getKind () == TypeKind .BOOLEAN ) {
304
+ // Fix by calling either assertThat(...).isTrue() or verify(...).
305
+ if (state .errorProneOptions ().isTestOnlyTarget ()) {
306
+ SuggestedFix .Builder fix = SuggestedFix .builder ();
307
+ fix .prefixWith (
308
+ invocationTree ,
309
+ qualifyStaticImport ("com.google.common.truth.Truth.assertThat" , fix , state ) + "(" )
310
+ .postfixWith (invocationTree , ").isTrue()" );
311
+ fixes .put ("Assert that the result is true" , fix .build ());
312
+ } else {
313
+ SuggestedFix .Builder fix = SuggestedFix .builder ();
314
+ fix .prefixWith (
315
+ invocationTree ,
316
+ qualifyStaticImport ("com.google.common.base.Verify.verify" , fix , state ) + "(" )
317
+ .postfixWith (invocationTree , ")" );
318
+ fixes .put ("Insert a runtime check that the result is true" , fix .build ());
319
+ }
320
+ } else if (resultType != null
321
+ // By looking for any isTrue() method, we handle not just Truth but also AssertJ.
322
+ && matchingMethods (
323
+ NAME_OF_IS_TRUE .get (state ),
324
+ m -> m .getParameters ().isEmpty (),
325
+ resultType ,
326
+ state .getTypes ())
327
+ .anyMatch (m -> true )) {
328
+ fixes .put ("Assert that the result is true" , postfixWith (invocationTree , ".isTrue()" ));
329
+ }
259
330
if (identifierExpr != null
260
331
&& symbol != null
261
332
&& !symbol .name .contentEquals ("this" )
@@ -265,9 +336,31 @@ final ImmutableList<Fix> fixesAtCallSite(ExpressionTree invocationTree, VisitorS
265
336
"Assign result back to variable" ,
266
337
prefixWith (invocationTree , state .getSourceForNode (identifierExpr ) + " = " ));
267
338
}
339
+ /*
340
+ * TODO(cpovirk): Suggest returning the value from the enclosing method where possible... *if*
341
+ * we can find a good heuristic. We could consider "Is the return type a protobuf" and/or "Is
342
+ * this a constructor call or build() call?"
343
+ */
344
+ if (parent .getKind () == EXPRESSION_STATEMENT
345
+ && !constantExpressions .constantExpression (invocationTree , state ).isPresent ()) {
346
+ ImmutableSet <String > identifiersInScope =
347
+ findAllIdents (state ).stream ().map (v -> v .name .toString ()).collect (toImmutableSet ());
348
+ concat (Stream .of ("unused" ), range (2 , 10 ).mapToObj (i -> "unused" + i ))
349
+ // TODO(b/72928608): Handle even local variables declared *later* within this scope.
350
+ // TODO(b/250568455): Also check whether we have suggested this name before in this scope.
351
+ .filter (n -> !identifiersInScope .contains (n ))
352
+ .findFirst ()
353
+ .ifPresent (
354
+ n ->
355
+ fixes .put (
356
+ "Suppress error by assigning to a variable" ,
357
+ prefixWith (parent , format ("var %s = " , n ))));
358
+ }
268
359
if (parent .getKind () == EXPRESSION_STATEMENT ) {
269
360
if (constantExpressions .constantExpression (invocationTree , state ).isPresent ()) {
270
361
fixes .put ("Delete call" , delete (parent ));
362
+ } else {
363
+ fixes .put ("Delete call and any side effects" , delete (parent ));
271
364
}
272
365
}
273
366
return fixes .buildOrThrow ().entrySet ().stream ()
@@ -523,4 +616,10 @@ public Description matchReturn(ReturnTree tree, VisitorState state) {
523
616
}
524
617
return NO_MATCH ;
525
618
}
619
+
620
+ private static final Matcher <ExpressionTree > MOCKITO_VERIFY =
621
+ staticMethod ().onClass ("org.mockito.Mockito" ).named ("verify" );
622
+
623
+ private static final com .google .errorprone .suppliers .Supplier <com .sun .tools .javac .util .Name >
624
+ NAME_OF_IS_TRUE = VisitorState .memoize (state -> state .getName ("isTrue" ));
526
625
}
0 commit comments