1616
1717package com .google .errorprone .bugpatterns ;
1818
19+ import static com .google .common .base .Ascii .toUpperCase ;
1920import static com .google .common .base .CaseFormat .UPPER_CAMEL ;
2021import static com .google .common .base .CaseFormat .UPPER_UNDERSCORE ;
22+ import static com .google .common .collect .ImmutableSet .toImmutableSet ;
2123import static com .google .common .collect .Streams .stream ;
2224import static com .google .errorprone .BugPattern .SeverityLevel .ERROR ;
2325import static com .google .errorprone .VisitorState .memoize ;
3941import com .google .auto .value .AutoValue ;
4042import com .google .common .collect .ArrayListMultimap ;
4143import com .google .common .collect .ImmutableMap ;
44+ import com .google .common .collect .ImmutableSet ;
4245import com .google .common .collect .Iterables ;
4346import com .google .common .collect .ListMultimap ;
4447import com .google .errorprone .BugPattern ;
4548import com .google .errorprone .BugPattern .StandardTags ;
49+ import com .google .errorprone .ErrorProneFlags ;
4650import com .google .errorprone .VisitorState ;
4751import com .google .errorprone .bugpatterns .BugChecker .MethodInvocationTreeMatcher ;
4852import com .google .errorprone .fixes .SuggestedFix ;
@@ -105,16 +109,27 @@ && isWithinAutoValueBuilder(symbol, state)
105109 (ExpressionTree ) state .getPath ().getParentPath ().getParentPath ().getLeaf (),
106110 state )));
107111
112+ private final boolean improvements ;
113+
108114 @ Inject
109- RedundantSetterCall () {}
115+ RedundantSetterCall (ErrorProneFlags flags ) {
116+ this .improvements = flags .getBoolean ("RedundantSetterCall:Improvements" ).orElse (true );
117+ }
110118
111119 @ Override
112120 public Description matchMethodInvocation (MethodInvocationTree tree , VisitorState state ) {
113121 if (!TERMINAL_FLUENT_SETTER .matches (tree , state )) {
114122 return Description .NO_MATCH ;
115123 }
124+
125+ var owner = getUpperBound (getType (tree ), state .getTypes ()).tsym .owner ;
126+ boolean isProto = owner != null && isSubtype (owner .type , MESSAGE_LITE .get (state ), state );
127+
116128 ListMultimap <Field , FieldWithValue > setters = ArrayListMultimap .create ();
117- ImmutableMap <String , OneOfField > oneOfSetters = scanForOneOfSetters (getType (tree ), state );
129+ ImmutableMap <String , OneOfField > oneOfSetters =
130+ isProto ? scanForOneOfSetters (getType (tree ), state ) : ImmutableMap .of ();
131+ ImmutableSet <String > fieldNames = isProto ? getFields (owner ) : ImmutableSet .of ();
132+
118133 Type type = ASTHelpers .getReturnType (tree );
119134 for (ExpressionTree current = tree ;
120135 FLUENT_SETTER .matches (current , state );
@@ -133,6 +148,17 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
133148 if (methodName .endsWith ("Builder" )) {
134149 break ;
135150 }
151+ if (improvements && isProto && methodName .startsWith ("set" )) {
152+ String withoutSet = methodName .replaceFirst ("^set" , "" );
153+ if (!fieldNames .contains (toUpperCase (withoutSet ))) {
154+ if (methodName .endsWith ("Value" )) {
155+ methodName = methodName .replaceFirst ("Value$" , "" );
156+ }
157+ if (methodName .endsWith ("Bytes" )) {
158+ methodName = methodName .replaceFirst ("Bytes$" , "" );
159+ }
160+ }
161+ }
136162 for (FieldType fieldType : FieldType .values ()) {
137163 FieldWithValue match = fieldType .match (methodName , method , state );
138164 if (match != null ) {
@@ -158,7 +184,8 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
158184 return Description .NO_MATCH ;
159185 }
160186
161- private ImmutableMap <String , OneOfField > scanForOneOfSetters (Type type , VisitorState state ) {
187+ private static ImmutableMap <String , OneOfField > scanForOneOfSetters (
188+ Type type , VisitorState state ) {
162189 var owner = getUpperBound (type , state .getTypes ()).tsym .owner ;
163190 if (owner == null || isSubtype (owner .type , GENERATED_MESSAGE_LITE .get (state ), state )) {
164191 return ImmutableMap .of ();
@@ -179,9 +206,27 @@ private ImmutableMap<String, OneOfField> scanForOneOfSetters(Type type, VisitorS
179206 return builder .buildOrThrow ();
180207 }
181208
209+ /**
210+ * Returns all the field names in the proto in uppercase with all components concatenated.
211+ *
212+ * <p>This is an odd format, but it works to compare the existence of a field based on a getter,
213+ * given both {@code foo_bar} and {@code fooBar} as field names generate a getter named {@code
214+ * getFooBar} (so we normalise to {@code FOOBAR}).
215+ */
216+ private static ImmutableSet <String > getFields (Symbol proto ) {
217+ return getEnclosedElements (proto ).stream ()
218+ .map (element -> element .getSimpleName ().toString ())
219+ .filter (name -> name .endsWith ("_FIELD_NUMBER" ))
220+ .map (name -> toUpperCase (name .replaceFirst ("_FIELD_NUMBER$" , "" ).replace ("_" , "" )))
221+ .collect (toImmutableSet ());
222+ }
223+
182224 private static final Supplier <Type > GENERATED_MESSAGE_LITE =
183225 memoize (state -> state .getTypeFromString ("com.google.protobuf.GeneratedMessageLite" ));
184226
227+ private static final Supplier <Type > MESSAGE_LITE =
228+ memoize (state -> state .getTypeFromString ("com.google.protobuf.MessageLite" ));
229+
185230 private Description describe (
186231 Field field , Collection <FieldWithValue > locations , VisitorState state ) {
187232 // We flag up all duplicate sets, but only suggest a fix if the setter is given the same
0 commit comments