2222import static com .google .errorprone .matchers .Matchers .staticMethod ;
2323import static java .lang .String .format ;
2424import static java .util .Collections .nCopies ;
25+ import static java .util .regex .Pattern .compile ;
2526import static java .util .stream .Collectors .joining ;
2627
27- import com .google .auto .value .AutoValue ;
2828import com .google .common .collect .ImmutableList ;
2929import com .google .errorprone .BugPattern ;
3030import com .google .errorprone .VisitorState ;
3636import com .sun .source .tree .ExpressionTree ;
3737import com .sun .source .tree .LiteralTree ;
3838import 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