core/src/main/java/com/google/errorprone/bugpatterns/checkreturnvalue/CanIgnoreReturnValueSuggester.java
@Override
  public Description matchMethod(MethodTree methodTree, VisitorState state) {
    MethodSymbol methodSymbol = getSymbol(methodTree);
    // if the method is static or doesn't have a body, it can't possibly "return this", bail out
    if (methodSymbol.isStatic() || methodTree.getBody() == null) {
      return Description.NO_MATCH;
    }

    // if the method is a proto builder method, bail out
    if (isProtoBuilderSubtype(methodSymbol.owner.type, state)) {
      // TODO(kak): could we just use this instead?
      // ProtoRules.protoBuilders().evaluate(methodSymbol, state).isPresent()
      return Description.NO_MATCH;
    }

    // if the method is already directly annotated w/ @CIRV, bail out
    if (hasAnnotation(methodTree, CIRV, state)) {
      return Description.NO_MATCH;
    }

    // if the enclosing type is already annotated with CIRV, we could theoretically _not_ directly
    // annotate the method but we're likely to discourage annotating types with CIRV: b/229776283

    // if the method is already directly annotated w/ @CRV, bail out
    if (hasAnnotation(methodTree, CRV, state)) {
      // TODO(kak): we might want to actually _remove_ @CRV and add @CIRV in this case!
      return Description.NO_MATCH;
    }

    // if the method is a constructor or has a void/Void return type, bail out
    Tree returnType = methodTree.getReturnType();
    if (returnType == null /* constructors have a null returnType */
        || isVoidType(getType(returnType), state)) {
      // TODO(b/234176673): isVoidType() also flags the `Void` (capital) type; is that desired?
      return Description.NO_MATCH;
    }

    AtomicBoolean allReturnThis = new AtomicBoolean(true);
    AtomicBoolean atLeastOneReturn = new AtomicBoolean(false);

    new TreePathScanner<Void, Void>() {
      @Override
      public Void visitReturn(ReturnTree returnTree, Void unused) {
        atLeastOneReturn.set(true);
        if (!returnsThis(returnTree)) {
          allReturnThis.set(false);
          // once we've set allReturnThis to false, no need to descend further
          return null;
        }
        return super.visitReturn(returnTree, null);
      }

      /** Returns whether or not the given {@link ReturnTree} returns exactly {@code this}. */
      private boolean returnsThis(ReturnTree returnTree) {
        ExpressionTree returnExpression = returnTree.getExpression();
        if (returnExpression instanceof IdentifierTree) {
          if (((IdentifierTree) returnExpression).getName().contentEquals("this")) {
            return true;
          }
        }
        return false;
      }

      @Override
      public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) {
        // don't descend into lambdas
        return null;
      }

      @Override
      public Void visitNewClass(NewClassTree node, Void unused) {
        // don't descend into declarations of anonymous classes
        return null;
      }
    }.scan(state.getPath(), null);

    if (atLeastOneReturn.get() && allReturnThis.get()) {
      SuggestedFix.Builder fix = SuggestedFix.builder();
      String cirvName = qualifyType(state, fix, CIRV);

      // we could add a trailing comment (e.g., @CanIgnoreReturnValue // returns `this`), but all
      // developers will become familiar with these annotations sooner or later
      fix.prefixWith(methodTree, "@" + cirvName + "\n");

      return describeMatch(methodTree, fix.build());
    }
    return Description.NO_MATCH;
  }
// won't "propagate" until the next analysis pass.
won't "propagate" until the next analysis pass.
won ' t " propagate " until the next analysis pass .
@Override
  public Description matchMethod(MethodTree methodTree, VisitorState state) {
    MethodSymbol methodSymbol = getSymbol(methodTree);
    if (methodSymbol.isStatic() || methodTree.getBody() == null) {
      return Description.NO_MATCH;
    }
    if (isProtoBuilderSubtype(methodSymbol.owner.type, state)) {
      return Description.NO_MATCH;
    }
    if (hasAnnotation(methodTree, CIRV, state)) {
      return Description.NO_MATCH;
    }
    if (hasAnnotation(methodTree, CRV, state)) {
      return Description.NO_MATCH;
    }
    Tree returnType = methodTree.getReturnType();
    if (returnType == null /* constructors have a null returnType */
        || isVoidType(getType(returnType), state)) {
      return Description.NO_MATCH;
    }
    AtomicBoolean allReturnThis = new AtomicBoolean(true);
    AtomicBoolean atLeastOneReturn = new AtomicBoolean(false);
    new TreePathScanner<Void, Void>() {
      @Override
      public Void visitReturn(ReturnTree returnTree, Void unused) {
        atLeastOneReturn.set(true);
        if (!returnsThis(returnTree)) {
          allReturnThis.set(false);
          return null;
        }
        return super.visitReturn(returnTree, null);
      }
      /** Returns whether or not the given {@link ReturnTree} returns exactly {@code this}. */
      private boolean returnsThis(ReturnTree returnTree) {
        ExpressionTree returnExpression = returnTree.getExpression();
        if (returnExpression instanceof IdentifierTree) {
          if (((IdentifierTree) returnExpression).getName().contentEquals("this")) {
            return true;
          }
        }
        return false;
      }
      @Override
      public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) {
        return null;
      }
      @Override
      public Void visitNewClass(NewClassTree node, Void unused) {
        return null;
      }
    }.scan(state.getPath(), null);
    if (atLeastOneReturn.get() && allReturnThis.get()) {
      SuggestedFix.Builder fix = SuggestedFix.builder();
      String cirvName = qualifyType(state, fix, CIRV);
      fix.prefixWith(methodTree, "@" + cirvName + "\n");
      return describeMatch(methodTree, fix.build());
    }
    return Description.NO_MATCH;
  }
@ Override public Description matchMethod ( MethodTree methodTree , VisitorState state ) { MethodSymbol methodSymbol = getSymbol ( methodTree ) ; if ( methodSymbol . isStatic ( ) || methodTree . getBody ( ) == null ) { return Description . NO_MATCH ; } if ( isProtoBuilderSubtype ( methodSymbol . owner . type , state ) ) { return Description . NO_MATCH ; } if ( hasAnnotation ( methodTree , CIRV , state ) ) { return Description . NO_MATCH ; } if ( hasAnnotation ( methodTree , CRV , state ) ) { return Description . NO_MATCH ; } Tree returnType = methodTree . getReturnType ( ) ; if ( returnType == null /* constructors have a null returnType */ || isVoidType ( getType ( returnType ) , state ) ) { return Description . NO_MATCH ; } AtomicBoolean allReturnThis = new AtomicBoolean ( true ) ; AtomicBoolean atLeastOneReturn = new AtomicBoolean ( false ) ; new TreePathScanner < Void , Void > ( ) { @ Override public Void visitReturn ( ReturnTree returnTree , Void unused ) { atLeastOneReturn . set ( true ) ; if ( ! returnsThis ( returnTree ) ) { allReturnThis . set ( false ) ; return null ; } return super . visitReturn ( returnTree , null ) ; } /** Returns whether or not the given {@link ReturnTree} returns exactly {@code this}. */ private boolean returnsThis ( ReturnTree returnTree ) { ExpressionTree returnExpression = returnTree . getExpression ( ) ; if ( returnExpression instanceof IdentifierTree ) { if ( ( ( IdentifierTree ) returnExpression ) . getName ( ) . contentEquals ( "this" ) ) { return true ; } } return false ; } @ Override public Void visitLambdaExpression ( LambdaExpressionTree node , Void unused ) { return null ; } @ Override public Void visitNewClass ( NewClassTree node , Void unused ) { return null ; } } . scan ( state . getPath ( ) , null ) ; if ( atLeastOneReturn . get ( ) && allReturnThis . get ( ) ) { SuggestedFix . Builder fix = SuggestedFix . builder ( ) ; String cirvName = qualifyType ( state , fix , CIRV ) ; fix . prefixWith ( methodTree , "@" + cirvName + "\n" ) ; return describeMatch ( methodTree , fix . build ( ) ) ; } return Description . NO_MATCH ; }
won't "propagate" until the next analysis pass.
won ' t " propagate " until the next analysis pass .
// if the method is static or doesn't have a body, it can't possibly "return this", bail out // if the method is a proto builder method, bail out // TODO(kak): could we just use this instead? // ProtoRules.protoBuilders().evaluate(methodSymbol, state).isPresent() // if the method is already directly annotated w/ @CIRV, bail out // if the enclosing type is already annotated with CIRV, we could theoretically _not_ directly // annotate the method but we're likely to discourage annotating types with CIRV: b/229776283 // if the method is already directly annotated w/ @CRV, bail out // TODO(kak): we might want to actually _remove_ @CRV and add @CIRV in this case! // if the method is a constructor or has a void/Void return type, bail out // TODO(b/234176673): isVoidType() also flags the `Void` (capital) type; is that desired? // once we've set allReturnThis to false, no need to descend further // don't descend into lambdas // don't descend into declarations of anonymous classes // we could add a trailing comment (e.g., @CanIgnoreReturnValue // returns `this`), but all // developers will become familiar with these annotations sooner or later