22
22
import static com .google .errorprone .matchers .Matchers .staticMethod ;
23
23
import static java .lang .String .format ;
24
24
import static java .util .Collections .nCopies ;
25
+ import static java .util .regex .Pattern .compile ;
25
26
import static java .util .stream .Collectors .joining ;
26
27
27
- import com .google .auto .value .AutoValue ;
28
28
import com .google .common .collect .ImmutableList ;
29
29
import com .google .errorprone .BugPattern ;
30
30
import com .google .errorprone .VisitorState ;
36
36
import com .sun .source .tree .ExpressionTree ;
37
37
import com .sun .source .tree .LiteralTree ;
38
38
import com .sun .source .tree .MethodInvocationTree ;
39
- import java .util .regex .Pattern ;
40
39
41
40
/** A BugPattern; see the summary. */
42
41
@ BugPattern (
@@ -49,39 +48,37 @@ public final class LenientFormatStringValidation extends BugChecker
49
48
50
49
@ Override
51
50
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 )));
83
80
}
84
- return NO_MATCH ;
81
+ return builder . build () ;
85
82
}
86
83
87
84
private static int occurrences (String haystack , String needle ) {
@@ -97,43 +94,43 @@ private static int occurrences(String haystack, String needle) {
97
94
}
98
95
}
99
96
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
+
101
106
private static final ImmutableList <LenientFormatMethod > METHODS =
102
107
ImmutableList .of (
103
- LenientFormatMethod . create (
108
+ new LenientFormatMethod (
104
109
staticMethod ()
105
110
.onClass ("com.google.common.base.Preconditions" )
106
- .withNameMatching (Pattern . compile ("^check.*" )),
111
+ .withNameMatching (compile ("^check.*" )),
107
112
1 ),
108
- LenientFormatMethod . create (
113
+ new LenientFormatMethod (
109
114
staticMethod ()
110
115
.onClass ("com.google.common.base.Verify" )
111
- .withNameMatching (Pattern . compile ("^verify.*" )),
116
+ .withNameMatching (compile ("^verify.*" )),
112
117
1 ),
113
- LenientFormatMethod . create (
118
+ new LenientFormatMethod (
114
119
staticMethod ().onClass ("com.google.common.base.Strings" ).named ("lenientFormat" ), 0 ),
115
- LenientFormatMethod . create (
120
+ new LenientFormatMethod (
116
121
staticMethod ().onClass ("com.google.common.truth.Truth" ).named ("assertWithMessage" ),
117
122
0 ),
118
- LenientFormatMethod . create (
123
+ new LenientFormatMethod (
119
124
instanceMethod ().onDescendantOf ("com.google.common.truth.Subject" ).named ("check" ), 0 ),
120
- LenientFormatMethod . create (
125
+ new LenientFormatMethod (
121
126
instanceMethod ()
122
127
.onDescendantOf ("com.google.common.truth.StandardSubjectBuilder" )
123
128
.named ("withMessage" ),
124
129
0 ));
125
130
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 ) {}
139
136
}
0 commit comments