com.google.errorprone.bugpatterns.ProtoBuilderReturnValueIgnored Maven / Gradle / Ivy
/*
* Copyright 2021 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static com.google.errorprone.util.ASTHelpers.getReceiver;
import static com.google.errorprone.util.ASTHelpers.streamReceivers;
import static com.google.errorprone.util.SideEffectAnalysis.hasSideEffect;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.type.DescendantOf;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
/** Highlights cases where a proto's build method has its return value ignored. */
@BugPattern(
summary =
"Unnecessary call to proto's #build() method. If you don't consume the return value of "
+ "#build(), the result is discarded and the only effect is to verify that all "
+ "required fields are set, which can be expressed more directly with "
+ "#isInitialized().",
severity = ERROR)
public final class ProtoBuilderReturnValueIgnored extends AbstractReturnValueIgnored {
private static final Matcher BUILDER =
instanceMethod()
.onDescendantOf("com.google.protobuf.MessageLite.Builder")
.namedAnyOf("build", "buildPartial");
public static final Matcher MATCHER =
allOf(
BUILDER,
// Don't match expressions beginning with a newBuilder call; these should be covered by
// ModifiedButNotUsed.
ProtoBuilderReturnValueIgnored::doesNotTerminateInNewBuilder);
private static final Matcher ROOT_INVOCATIONS_TO_IGNORE =
anyOf(
staticMethod()
.onClass(
new DescendantOf(Suppliers.typeFromString("com.google.protobuf.MessageLite")))
.namedAnyOf("newBuilder"),
instanceMethod()
.onDescendantOf("com.google.protobuf.MessageLite")
.namedAnyOf("toBuilder"));
@Override
public Matcher specializedMatcher() {
return MATCHER;
}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Description description = super.matchMethodInvocation(tree, state);
if (description.equals(Description.NO_MATCH)) {
return Description.NO_MATCH;
}
return buildDescription(tree).addAllFixes(generateFixes(tree, state)).build();
}
private static ImmutableList generateFixes(
MethodInvocationTree tree, VisitorState state) {
SuggestedFix.Builder simpleFixBuilder =
SuggestedFix.builder()
.setShortDescription(
"Remove the call to #build. Note that this will not validate the presence "
+ "of required fields.");
ExpressionTree receiver = getReceiver(tree);
if (receiver instanceof IdentifierTree || receiver instanceof MemberSelectTree) {
simpleFixBuilder.replace(state.getPath().getParentPath().getLeaf(), "");
if (hasSideEffect(receiver)) {
simpleFixBuilder.setShortDescription(
"Remove the call to #build. Note that this will not validate the presence "
+ "of required fields, and removes any side effects of the receiver expression.");
}
} else {
simpleFixBuilder.replace(state.getEndPosition(receiver), state.getEndPosition(tree), "");
}
SuggestedFix simpleFix = simpleFixBuilder.build();
SuggestedFix withRequiredFieldCheck =
SuggestedFix.builder()
.setShortDescription(
"Replace the call to #build with an explicit check that required "
+ "fields are present.")
.addStaticImport("com.google.common.base.Preconditions.checkState")
.prefixWith(tree, "checkState(")
.replace(
state.getEndPosition(receiver), state.getEndPosition(tree), ".isInitialized())")
.build();
return ImmutableList.of(simpleFix, withRequiredFieldCheck);
}
private static boolean doesNotTerminateInNewBuilder(ExpressionTree tree, VisitorState state) {
return streamReceivers(tree).noneMatch(t -> ROOT_INVOCATIONS_TO_IGNORE.matches(t, state));
}
}