Skip to content

Commit 0223abb

Browse files
graememorganError Prone Team
authored and
Error Prone Team
committed
Support @LenientFormatString in LenientFormatStringValidation.
PiperOrigin-RevId: 748674542
1 parent cb7dfaf commit 0223abb

File tree

1 file changed

+53
-56
lines changed

1 file changed

+53
-56
lines changed

core/src/main/java/com/google/errorprone/bugpatterns/LenientFormatStringValidation.java

+53-56
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
import static com.google.errorprone.matchers.Matchers.staticMethod;
2323
import static java.lang.String.format;
2424
import static java.util.Collections.nCopies;
25+
import static java.util.regex.Pattern.compile;
2526
import static java.util.stream.Collectors.joining;
2627

27-
import com.google.auto.value.AutoValue;
2828
import com.google.common.collect.ImmutableList;
2929
import com.google.errorprone.BugPattern;
3030
import com.google.errorprone.VisitorState;
@@ -36,7 +36,6 @@
3636
import com.sun.source.tree.ExpressionTree;
3737
import com.sun.source.tree.LiteralTree;
3838
import com.sun.source.tree.MethodInvocationTree;
39-
import java.util.regex.Pattern;
4039

4140
/** A BugPattern; see the summary. */
4241
@BugPattern(
@@ -49,39 +48,37 @@ public final class LenientFormatStringValidation extends BugChecker
4948

5049
@Override
5150
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
52-
for (LenientFormatMethod method : METHODS) {
53-
if (!method.matcher().matches(tree, state)) {
54-
continue;
55-
}
56-
var args = tree.getArguments();
57-
if (args.size() <= method.formatStringPosition()) {
58-
continue;
59-
}
60-
ExpressionTree formatStringArgument = args.get(method.formatStringPosition());
61-
Object formatString = ASTHelpers.constValue(formatStringArgument);
62-
if (!(formatString instanceof String string)) {
63-
continue;
64-
}
65-
int expected = occurrences(string, "%s");
66-
int actual = args.size() - method.formatStringPosition() - 1;
67-
if (expected == actual) {
68-
continue;
69-
}
70-
var builder =
71-
buildDescription(tree)
72-
.setMessage(format("Expected %s positional arguments, but saw %s", expected, actual));
73-
if (expected < actual) {
74-
String extraArgs =
75-
nCopies(actual - expected, "%s").stream().collect(joining(", ", " (", ")"));
76-
int endPos = state.getEndPosition(formatStringArgument);
77-
builder.addFix(
78-
formatStringArgument instanceof LiteralTree
79-
? SuggestedFix.replace(endPos - 1, endPos, extraArgs + "\"")
80-
: SuggestedFix.postfixWith(formatStringArgument, format("+ \"%s\"", extraArgs)));
81-
}
82-
return builder.build();
51+
int formatStringPosition = getFormatStringPosition(tree, state);
52+
if (formatStringPosition < 0) {
53+
return NO_MATCH;
54+
}
55+
var args = tree.getArguments();
56+
if (args.size() <= formatStringPosition) {
57+
return NO_MATCH;
58+
}
59+
ExpressionTree formatStringArgument = args.get(formatStringPosition);
60+
Object formatString = ASTHelpers.constValue(formatStringArgument);
61+
if (!(formatString instanceof String string)) {
62+
return NO_MATCH;
63+
}
64+
int expected = occurrences(string, "%s");
65+
int actual = args.size() - formatStringPosition - 1;
66+
if (expected == actual) {
67+
return NO_MATCH;
68+
}
69+
var builder =
70+
buildDescription(tree)
71+
.setMessage(format("Expected %s positional arguments, but saw %s", expected, actual));
72+
if (expected < actual) {
73+
String extraArgs =
74+
nCopies(actual - expected, "%s").stream().collect(joining(", ", " (", ")"));
75+
int endPos = state.getEndPosition(formatStringArgument);
76+
builder.addFix(
77+
formatStringArgument instanceof LiteralTree
78+
? SuggestedFix.replace(endPos - 1, endPos, extraArgs + "\"")
79+
: SuggestedFix.postfixWith(formatStringArgument, format("+ \"%s\"", extraArgs)));
8380
}
84-
return NO_MATCH;
81+
return builder.build();
8582
}
8683

8784
private static int occurrences(String haystack, String needle) {
@@ -97,43 +94,43 @@ private static int occurrences(String haystack, String needle) {
9794
}
9895
}
9996

100-
// TODO(ghm): Consider replacing this with an annotation-based approach (@LenientFormatString?)
97+
private static int getFormatStringPosition(ExpressionTree tree, VisitorState state) {
98+
for (LenientFormatMethod method : METHODS) {
99+
if (method.matcher().matches(tree, state)) {
100+
return method.formatStringPosition;
101+
}
102+
}
103+
return -1;
104+
}
105+
101106
private static final ImmutableList<LenientFormatMethod> METHODS =
102107
ImmutableList.of(
103-
LenientFormatMethod.create(
108+
new LenientFormatMethod(
104109
staticMethod()
105110
.onClass("com.google.common.base.Preconditions")
106-
.withNameMatching(Pattern.compile("^check.*")),
111+
.withNameMatching(compile("^check.*")),
107112
1),
108-
LenientFormatMethod.create(
113+
new LenientFormatMethod(
109114
staticMethod()
110115
.onClass("com.google.common.base.Verify")
111-
.withNameMatching(Pattern.compile("^verify.*")),
116+
.withNameMatching(compile("^verify.*")),
112117
1),
113-
LenientFormatMethod.create(
118+
new LenientFormatMethod(
114119
staticMethod().onClass("com.google.common.base.Strings").named("lenientFormat"), 0),
115-
LenientFormatMethod.create(
120+
new LenientFormatMethod(
116121
staticMethod().onClass("com.google.common.truth.Truth").named("assertWithMessage"),
117122
0),
118-
LenientFormatMethod.create(
123+
new LenientFormatMethod(
119124
instanceMethod().onDescendantOf("com.google.common.truth.Subject").named("check"), 0),
120-
LenientFormatMethod.create(
125+
new LenientFormatMethod(
121126
instanceMethod()
122127
.onDescendantOf("com.google.common.truth.StandardSubjectBuilder")
123128
.named("withMessage"),
124129
0));
125130

126-
@AutoValue
127-
abstract static class LenientFormatMethod {
128-
abstract Matcher<ExpressionTree> matcher();
129-
130-
/** Position of the format string; we assume every argument afterwards is a format argument. */
131-
abstract int formatStringPosition();
132-
133-
public static LenientFormatMethod create(
134-
Matcher<ExpressionTree> matcher, int formatStringPosition) {
135-
return new AutoValue_LenientFormatStringValidation_LenientFormatMethod(
136-
matcher, formatStringPosition);
137-
}
138-
}
131+
/**
132+
* @param formatStringPosition position of the format string; we assume every argument afterwards
133+
* is a format argument.
134+
*/
135+
private record LenientFormatMethod(Matcher<ExpressionTree> matcher, int formatStringPosition) {}
139136
}

0 commit comments

Comments
 (0)