Skip to content

Commit ea7b968

Browse files
cushonError Prone Team
authored and
Error Prone Team
committed
Support Pattern.split in StringSplitter
Fixes https://github.com/google/error-prone/pull/1683/files PiperOrigin-RevId: 350827009
1 parent 83ad9b6 commit ea7b968

File tree

3 files changed

+99
-38
lines changed

3 files changed

+99
-38
lines changed

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

+47-28
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
import static com.google.common.collect.Iterables.getOnlyElement;
2121
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
2222
import static com.google.errorprone.matchers.Description.NO_MATCH;
23+
import static com.google.errorprone.matchers.Matchers.anyOf;
2324
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
2425
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
26+
import static com.google.errorprone.util.ASTHelpers.getType;
27+
import static com.google.errorprone.util.ASTHelpers.isSubtype;
2528
import static com.google.errorprone.util.Regexes.convertRegexToLiteral;
2629
import static java.lang.String.format;
2730

@@ -46,6 +49,7 @@
4649
import com.sun.source.util.TreePath;
4750
import com.sun.source.util.TreePathScanner;
4851
import com.sun.tools.javac.code.Symbol.VarSymbol;
52+
import com.sun.tools.javac.code.Type;
4953
import java.util.ArrayList;
5054
import java.util.List;
5155
import java.util.Objects;
@@ -59,10 +63,15 @@
5963
public class StringSplitter extends BugChecker implements MethodInvocationTreeMatcher {
6064

6165
private static final Matcher<ExpressionTree> MATCHER =
62-
instanceMethod()
63-
.onExactClass("java.lang.String")
64-
.named("split")
65-
.withParameters("java.lang.String");
66+
anyOf(
67+
instanceMethod()
68+
.onExactClass("java.lang.String")
69+
.named("split")
70+
.withParameters("java.lang.String"),
71+
instanceMethod()
72+
.onExactClass("java.util.regex.Pattern")
73+
.named("split")
74+
.withParameters("java.lang.CharSequence"));
6675

6776
@Override
6877
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
@@ -78,20 +87,14 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
7887
}
7988

8089
public Optional<Fix> buildFix(MethodInvocationTree tree, VisitorState state) {
81-
Tree arg = getOnlyElement(tree.getArguments());
82-
String methodAndArgument = getMethodAndArgument(arg, state);
90+
ExpressionTree arg = getOnlyElement(tree.getArguments());
8391
Tree parent = state.getPath().getParentPath().getLeaf();
8492
if (parent instanceof EnhancedForLoopTree
8593
&& ((EnhancedForLoopTree) parent).getExpression().equals(tree)) {
8694
// fix for `for (... : s.split(...)) {}` -> `for (... : Splitter.on(...).split(s)) {}`
8795
return Optional.of(
8896
replaceWithSplitter(
89-
SuggestedFix.builder(),
90-
tree,
91-
methodAndArgument,
92-
state,
93-
"split",
94-
/* mutableList= */ false)
97+
SuggestedFix.builder(), tree, arg, state, "split", /* mutableList= */ false)
9598
.build());
9699
}
97100
if (parent instanceof ArrayAccessTree) {
@@ -115,9 +118,7 @@ public Optional<Fix> buildFix(MethodInvocationTree tree, VisitorState state) {
115118
state.getEndPosition(arrayAccessTree),
116119
")");
117120
return Optional.of(
118-
replaceWithSplitter(
119-
fix, tree, methodAndArgument, state, "split", /* mutableList= */ false)
120-
.build());
121+
replaceWithSplitter(fix, tree, arg, state, "split", /* mutableList= */ false).build());
121122
}
122123
// If the result of split is assigned to a variable, try to fix all uses of the variable in the
123124
// enclosing method. If we don't know how to fix any of them, bail out.
@@ -218,12 +219,12 @@ public Boolean visitMemberSelect(MemberSelectTree tree, Void unused) {
218219
if (!isImplicitlyTyped) {
219220
fix.replace(varType, "List<String>").addImport("java.util.List");
220221
}
221-
replaceWithSplitter(fix, tree, methodAndArgument, state, "splitToList", needsMutableList[0]);
222+
replaceWithSplitter(fix, tree, arg, state, "splitToList", needsMutableList[0]);
222223
} else {
223224
if (!isImplicitlyTyped) {
224225
fix.replace(varType, "Iterable<String>");
225226
}
226-
replaceWithSplitter(fix, tree, methodAndArgument, state, "split", needsMutableList[0]);
227+
replaceWithSplitter(fix, tree, arg, state, "split", needsMutableList[0]);
227228
}
228229
return Optional.of(fix.build());
229230
}
@@ -258,24 +259,42 @@ private static String getMethodAndArgument(Tree origArg, VisitorState state) {
258259
private SuggestedFix.Builder replaceWithSplitter(
259260
SuggestedFix.Builder fix,
260261
MethodInvocationTree tree,
261-
String methodAndArgument,
262+
ExpressionTree arg,
262263
VisitorState state,
263264
String splitMethod,
264265
boolean mutableList) {
265266
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
266267
if (mutableList) {
267268
fix.addImport("java.util.ArrayList");
268269
}
269-
return fix.addImport("com.google.common.base.Splitter")
270-
.prefixWith(
271-
receiver,
272-
String.format(
273-
"%sSplitter.%s.%s(",
274-
(mutableList ? "new ArrayList<>(" : ""), methodAndArgument, splitMethod))
275-
.replace(
276-
state.getEndPosition(receiver),
277-
state.getEndPosition(tree),
278-
(mutableList ? ")" : "") + ")");
270+
fix.addImport("com.google.common.base.Splitter");
271+
Type receiverType = getType(receiver);
272+
if (isSubtype(receiverType, state.getSymtab().stringType, state)) {
273+
String methodAndArgument = getMethodAndArgument(arg, state);
274+
return fix.prefixWith(
275+
receiver,
276+
String.format(
277+
"%sSplitter.%s.%s(",
278+
(mutableList ? "new ArrayList<>(" : ""), methodAndArgument, splitMethod))
279+
.replace(
280+
state.getEndPosition(receiver),
281+
state.getEndPosition(tree),
282+
(mutableList ? ")" : "") + ")");
283+
}
284+
if (isSubtype(receiverType, state.getTypeFromString("java.util.regex.Pattern"), state)) {
285+
return fix.prefixWith(
286+
receiver, String.format("%sSplitter.on(", (mutableList ? "new ArrayList<>(" : "")))
287+
.postfixWith(receiver, ")")
288+
.replace(
289+
/* startPos= */ state.getEndPosition(receiver),
290+
/* endPos= */ getStartPosition(arg),
291+
String.format(".%s(", splitMethod))
292+
.replace(
293+
state.getEndPosition(arg),
294+
state.getEndPosition(tree),
295+
(mutableList ? ")" : "") + ")");
296+
}
297+
throw new AssertionError(receiver);
279298
}
280299

281300
private TreePath findEnclosing(VisitorState state) {

core/src/test/java/com/google/errorprone/bugpatterns/StringSplitterTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -438,4 +438,44 @@ public void noSplitterOnClassPath() {
438438
.setArgs("-cp", ":")
439439
.doTest(TestMode.TEXT_MATCH);
440440
}
441+
442+
@Test
443+
public void patternSplit() {
444+
testHelper
445+
.addInputLines(
446+
"Test.java",
447+
"import java.util.regex.Pattern;",
448+
"class Test {",
449+
" void f() {",
450+
" String x = Pattern.compile(\"\").split(\"c\")[0];",
451+
" for (String s : Pattern.compile(\"\").split(\":\")) {}",
452+
" String[] xs = Pattern.compile(\"c\").split(\"\");",
453+
" xs[0] = null;",
454+
" System.err.println(xs[0]);",
455+
" String[] pieces = Pattern.compile(\":\").split(\"\");",
456+
" for (int i = 0; i < pieces.length; i++) {}",
457+
" }",
458+
"}")
459+
.addOutputLines(
460+
"Test.java",
461+
"import com.google.common.base.Splitter;",
462+
"import com.google.common.collect.Iterables;",
463+
"import java.util.ArrayList;",
464+
"import java.util.List;",
465+
"import java.util.regex.Pattern;",
466+
"",
467+
"class Test {",
468+
" void f() {",
469+
" String x = Iterables.get(Splitter.on(Pattern.compile(\"\")).split(\"c\"), 0);",
470+
" for (String s : Splitter.on(Pattern.compile(\"\")).split(\":\")) {}",
471+
" List<String> xs ="
472+
+ " new ArrayList<>(Splitter.on(Pattern.compile(\"c\")).splitToList(\"\"));",
473+
" xs.set(0, null);",
474+
" System.err.println(xs.get(0));",
475+
" List<String> pieces = Splitter.on(Pattern.compile(\":\")).splitToList(\"\");",
476+
" for (int i = 0; i < pieces.size(); i++) {}",
477+
" }",
478+
"}")
479+
.doTest(TestMode.TEXT_MATCH);
480+
}
441481
}

docs/bugpattern/StringSplitter.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
`String.split(String)` has surprising behaviour. For example, consider the
2-
following puzzler from
1+
`String.split(String)` and `Pattern.split(CharSequence) have surprising
2+
behaviour. For example, consider the following puzzler from
33
http://konigsberg.blogspot.com/2009/11/final-thoughts-java-puzzler-splitting.html:
44

55
```java
@@ -11,13 +11,13 @@ The result is `[""]` and `[]`!
1111

1212
More examples:
1313

14-
input | `input.split(":")` | `Splitter.on(':').split(input)`
15-
-------- | ------------------- | -------------------------------
16-
`""` | `[""]` | `[""]`
17-
`":"` | `[]` | `["", ""]`
18-
`":::"` | `[]` | `["", "", "", ""]`
19-
`"a:::"` | `["a"]` | `["a", "", "", ""]`
20-
`":::b"` | `["", "", "", "b"]` | `["", "", "", "b"]`
14+
input | `input.split(":")` | `Pattern.compile(":").split(input) | `Splitter.on(':').split(input)`
15+
-------- | ------------------- | ---------------------------------- | -------------------------------
16+
`""` | `[""]` | `[""]` | `[""]`
17+
`":"` | `[]` | `[]` | `["", ""]`
18+
`":::"` | `[]` | `[]` | `["", "", "", ""]`
19+
`"a:::"` | `["a"]` | `["a"]` | `["a", "", "", ""]`
20+
`":::b"` | `["", "", "", "b"]` | `["", "", "", "b"]` | `["", "", "", "b"]`
2121

2222
Prefer either:
2323

@@ -27,7 +27,9 @@ Prefer either:
2727
handling of empty strings and the trimming of whitespace with `trimResults`
2828
and `omitEmptyStrings`.
2929

30-
* [`String.split(String, int)`](https://docs.oracle.com/javase/9/docs/api/java/lang/String.html#split-java.lang.String-int-)
30+
* [`String.split(String, int)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#split\(java.lang.String,int\))
31+
or
32+
[`Pattern.split(CharSequence, int)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#split\(java.lang.String,int\))
3133
and setting an explicit 'limit' to `-1` to match the behaviour of
3234
`Splitter`.
3335

0 commit comments

Comments
 (0)