29
29
import static com .google .errorprone .util .ASTHelpers .stringContainsComments ;
30
30
import static com .google .errorprone .util .MoreAnnotations .getValue ;
31
31
import static com .google .errorprone .util .SideEffectAnalysis .hasSideEffect ;
32
+ import static java .lang .String .format ;
32
33
import static java .util .stream .Collectors .joining ;
33
34
34
35
import com .google .auto .value .AutoValue ;
49
50
import com .google .errorprone .fixes .SuggestedFixes ;
50
51
import com .google .errorprone .matchers .Description ;
51
52
import com .google .errorprone .util .MoreAnnotations ;
53
+ import com .google .errorprone .util .OperatorPrecedence ;
54
+ import com .sun .source .tree .AssignmentTree ;
52
55
import com .sun .source .tree .ExpressionStatementTree ;
53
56
import com .sun .source .tree .ExpressionTree ;
54
57
import com .sun .source .tree .IdentifierTree ;
55
58
import com .sun .source .tree .MemberReferenceTree ;
59
+ import com .sun .source .tree .MemberSelectTree ;
56
60
import com .sun .source .tree .MethodInvocationTree ;
57
61
import com .sun .source .tree .NewClassTree ;
58
62
import com .sun .source .tree .Tree ;
63
+ import com .sun .source .tree .UnaryTree ;
64
+ import com .sun .source .tree .VariableTree ;
65
+ import com .sun .source .util .TreePath ;
59
66
import com .sun .source .util .TreeScanner ;
60
67
import com .sun .tools .javac .code .Attribute ;
61
68
import com .sun .tools .javac .code .Symbol .MethodSymbol ;
65
72
import com .sun .tools .javac .tree .JCTree ;
66
73
import java .util .ArrayList ;
67
74
import java .util .List ;
75
+ import java .util .Objects ;
68
76
import java .util .Optional ;
69
77
import java .util .function .BiConsumer ;
70
78
import java .util .regex .Matcher ;
@@ -384,7 +392,12 @@ public int replaceTree(JCTree oldtree, JCTree newtree) {
384
392
})
385
393
.applyReplacements (replacementFixes .build ());
386
394
387
- fixBuilder .replace (replacementStart , replacementEnd , fixedReplacement );
395
+ fixBuilder .replace (
396
+ replacementStart ,
397
+ replacementEnd ,
398
+ inliningRequiresParentheses (state .getPath (), replacementExpression )
399
+ ? format ("(%s)" , fixedReplacement )
400
+ : fixedReplacement );
388
401
389
402
return maybeCheckFixCompiles (tree , state , fixBuilder .build (), api );
390
403
}
@@ -397,6 +410,73 @@ private static List<? extends ExpressionTree> getArguments(Tree tree) {
397
410
};
398
411
}
399
412
413
+ /**
414
+ * Checks whether an expression requires parentheses when substituting in.
415
+ *
416
+ * <p>{@code treePath} is the original path including the old tree at the tip; {@code replacement}
417
+ * is the proposed replacement tree.
418
+ *
419
+ * <p>This was originally from {@link com.google.errorprone.util.ASTHelpers#requiresParentheses}
420
+ * but is heavily specialised for this use case.
421
+ */
422
+ private static boolean inliningRequiresParentheses (
423
+ TreePath treePath , ExpressionTree replacement ) {
424
+ var originalExpression = treePath .getLeaf ();
425
+ var parent = treePath .getParentPath ().getLeaf ();
426
+
427
+ Optional <OperatorPrecedence > replacementPrecedence =
428
+ OperatorPrecedence .optionallyFrom (replacement .getKind ());
429
+ Optional <OperatorPrecedence > parentPrecedence =
430
+ OperatorPrecedence .optionallyFrom (parent .getKind ());
431
+ if (replacementPrecedence .isPresent () && parentPrecedence .isPresent ()) {
432
+ return parentPrecedence .get ().isHigher (replacementPrecedence .get ());
433
+ }
434
+
435
+ // There are some locations, based on the parent path, where we never want to parenthesise.
436
+ // This list is likely not exhaustive.
437
+ switch (parent .getKind ()) {
438
+ case RETURN , EXPRESSION_STATEMENT -> {
439
+ return false ;
440
+ }
441
+ case VARIABLE -> {
442
+ if (Objects .equals (((VariableTree ) parent ).getInitializer (), originalExpression )) {
443
+ return false ;
444
+ }
445
+ }
446
+ case ASSIGNMENT -> {
447
+ if (((AssignmentTree ) parent ).getExpression ().equals (originalExpression )) {
448
+ return false ;
449
+ }
450
+ }
451
+ case METHOD_INVOCATION , NEW_CLASS -> {
452
+ if (getArguments (parent ).contains (originalExpression )) {
453
+ return false ;
454
+ }
455
+ }
456
+ default -> {
457
+ // continue below
458
+ }
459
+ }
460
+ switch (replacement .getKind ()) {
461
+ case IDENTIFIER ,
462
+ MEMBER_SELECT ,
463
+ METHOD_INVOCATION ,
464
+ ARRAY_ACCESS ,
465
+ PARENTHESIZED ,
466
+ NEW_CLASS ,
467
+ MEMBER_REFERENCE -> {
468
+ return false ;
469
+ }
470
+ default -> {
471
+ // continue below
472
+ }
473
+ }
474
+ if (replacement instanceof UnaryTree ) {
475
+ return parent instanceof MemberSelectTree ;
476
+ }
477
+ return true ;
478
+ }
479
+
400
480
private Description maybeCheckFixCompiles (
401
481
ExpressionTree tree , VisitorState state , SuggestedFix fix , Api api ) {
402
482
if (checkFixCompiles && fix .getImportsToAdd ().isEmpty ()) {
@@ -489,7 +569,7 @@ final String message() {
489
569
490
570
/** Returns {@code FullyQualifiedClassName#methodName}. */
491
571
final String methodId () {
492
- return String . format ("%s#%s" , className (), methodName ());
572
+ return format ("%s#%s" , className (), methodName ());
493
573
}
494
574
495
575
/**
@@ -498,7 +578,7 @@ final String methodId() {
498
578
*/
499
579
final String shortName () {
500
580
String humanReadableClassName = className ().replaceFirst (packageName () + "." , "" );
501
- return String . format ("`%s.%s()`" , humanReadableClassName , methodName ());
581
+ return format ("`%s.%s()`" , humanReadableClassName , methodName ());
502
582
}
503
583
504
584
/** Returns the simple class name (e.g., {@code ClassName}). */
0 commit comments