16
16
17
17
package com .google .errorprone .bugpatterns ;
18
18
19
+ import static com .google .common .collect .ImmutableList .toImmutableList ;
19
20
import static com .google .errorprone .BugPattern .SeverityLevel .WARNING ;
21
+ import static com .google .errorprone .matchers .Matchers .instanceMethod ;
20
22
import static com .google .errorprone .matchers .Matchers .staticMethod ;
23
+ import static com .google .errorprone .util .ASTHelpers .getReceiver ;
21
24
25
+ import com .google .common .collect .ImmutableList ;
22
26
import com .google .errorprone .BugPattern ;
23
27
import com .google .errorprone .VisitorState ;
24
28
import com .google .errorprone .bugpatterns .BugChecker .MethodInvocationTreeMatcher ;
@@ -42,12 +46,30 @@ public final class StringFormatWithLiteral extends BugChecker
42
46
private static final Matcher <ExpressionTree > STRING_FORMAT_METHOD_MATCHER =
43
47
staticMethod ().onClass ("java.lang.String" ).named ("format" );
44
48
49
+ private static final Matcher <ExpressionTree > FORMATTED =
50
+ instanceMethod ().onExactClass ("java.lang.String" ).named ("formatted" );
51
+
45
52
private static final Pattern SPECIFIER_ALLOW_LIST_REGEX = Pattern .compile ("%(d|s|S|c|b|B)" );
46
53
47
54
@ Override
48
55
public Description matchMethodInvocation (MethodInvocationTree tree , VisitorState state ) {
49
- if (STRING_FORMAT_METHOD_MATCHER .matches (tree , state ) && shouldRefactorStringFormat (tree )) {
50
- return describeMatch (tree , refactor (tree ));
56
+ if (STRING_FORMAT_METHOD_MATCHER .matches (tree , state )) {
57
+ ImmutableList <ExpressionTree > arguments =
58
+ tree .getArguments ().stream ().skip (1 ).collect (toImmutableList ());
59
+ if (shouldRefactorStringFormat (tree .getArguments ().get (0 ), arguments )) {
60
+ return describeMatch (
61
+ tree ,
62
+ SuggestedFix .replace (
63
+ tree , getFormattedUnifiedString (tree .getArguments ().get (0 ), arguments )));
64
+ }
65
+ }
66
+ if (FORMATTED .matches (tree , state )) {
67
+ if (shouldRefactorStringFormat (getReceiver (tree ), tree .getArguments ())) {
68
+ return describeMatch (
69
+ tree ,
70
+ SuggestedFix .replace (
71
+ tree , getFormattedUnifiedString (getReceiver (tree ), tree .getArguments ())));
72
+ }
51
73
}
52
74
return Description .NO_MATCH ;
53
75
}
@@ -57,15 +79,15 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
57
79
* string included). Format strings (first argument) as variables or constants are excluded from
58
80
* refactoring. The refactoring also has an allowlist of "non trivial" formatting specifiers. This
59
81
* is done since there are some instances where the String.format() invocation is justified even
60
- * with
82
+ * with a CONSTANT but non-literal format string.
61
83
*/
62
- private static boolean shouldRefactorStringFormat (MethodInvocationTree tree ) {
63
- if (!tree .getArguments ().stream ()
64
- .allMatch (argumentTree -> argumentTree instanceof LiteralTree )) {
84
+ private static boolean shouldRefactorStringFormat (
85
+ ExpressionTree formatString , List <? extends ExpressionTree > arguments ) {
86
+ if (!(formatString instanceof LiteralTree )
87
+ || !arguments .stream ().allMatch (argumentTree -> argumentTree instanceof LiteralTree )) {
65
88
return false ;
66
89
}
67
- LiteralTree formatString = (LiteralTree ) tree .getArguments ().get (0 );
68
- return onlyContainsSpecifiersInAllowList ((String ) formatString .getValue ());
90
+ return onlyContainsSpecifiersInAllowList ((String ) ((LiteralTree ) formatString ).getValue ());
69
91
}
70
92
71
93
private static boolean onlyContainsSpecifiersInAllowList (String formatString ) {
@@ -74,29 +96,18 @@ private static boolean onlyContainsSpecifiersInAllowList(String formatString) {
74
96
return !noSpecifierFormatBase .contains ("%" );
75
97
}
76
98
77
- private static SuggestedFix refactor (MethodInvocationTree tree ) {
78
- return SuggestedFix .replace (
79
- tree , getFormattedUnifiedString (getFormatString (tree ), tree .getArguments ()));
80
- }
81
-
82
99
/**
83
100
* Formats the string originally on the String.format to be a unified string with all the literal
84
101
* parameters, when available.
85
102
*/
86
103
private static String getFormattedUnifiedString (
87
- String formatString , List <? extends ExpressionTree > arguments ) {
104
+ ExpressionTree formatString , List <? extends ExpressionTree > arguments ) {
88
105
String unescapedFormatString =
89
106
String .format (
90
- formatString ,
107
+ ( String ) (( LiteralTree ) formatString ). getValue () ,
91
108
arguments .stream ()
92
- .skip (1 ) // skip the format string argument.
93
- .map (literallTree -> ((LiteralTree ) literallTree ).getValue ())
109
+ .map (literalTree -> ((LiteralTree ) literalTree ).getValue ())
94
110
.toArray (Object []::new ));
95
111
return '"' + SourceCodeEscapers .javaCharEscaper ().escape (unescapedFormatString ) + '"' ;
96
112
}
97
-
98
- private static String getFormatString (MethodInvocationTree tree ) {
99
- LiteralTree formatStringTree = (LiteralTree ) tree .getArguments ().get (0 );
100
- return formatStringTree .getValue ().toString ();
101
- }
102
113
}
0 commit comments