Skip to content

Commit 2ce9632

Browse files
graememorganError Prone Team
authored and
Error Prone Team
committed
Strip the quotation marks from the source code when reconstructing the literal.
My comment was a lie. This does obviously matter, because it's important to work out if the \s is at the end of the entire string. PiperOrigin-RevId: 688949631
1 parent 99a0d9d commit 2ce9632

File tree

2 files changed

+63
-25
lines changed

2 files changed

+63
-25
lines changed

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

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
2020
import static com.google.errorprone.matchers.Description.NO_MATCH;
2121
import static com.google.errorprone.util.SourceVersion.supportsTextBlocks;
22-
import static java.util.stream.Collectors.joining;
2322

2423
import com.google.errorprone.BugPattern;
2524
import com.google.errorprone.VisitorState;
@@ -56,32 +55,38 @@ public Description matchLiteral(LiteralTree tree, VisitorState state) {
5655
// Tokenize the source to make sure we omit comments. Desugaring of "a" + "b" into a single
5756
// string literal happens really early in compilation, and we want to ensure we don't match
5857
// on any "\s" in comments.
59-
// This is quite ugly in that we end up with a string that looks like "foo""bar", i.e.
60-
// including the quotes, but that doesn't matter for this check.
6158
var tokens = ErrorProneTokens.getTokens(source, state.context);
62-
var literal =
63-
tokens.stream()
64-
.filter(t -> t.kind().equals(TokenKind.STRINGLITERAL))
65-
.map(t -> source.substring(t.pos(), t.endPos()))
66-
.collect(joining());
67-
boolean seenEscape = false;
68-
for (int i = 0; i < literal.length(); ++i) {
69-
switch (literal.charAt(i)) {
70-
case '\n':
71-
seenEscape = false;
72-
break;
73-
case '\\':
74-
i++;
75-
if (literal.charAt(i) == 's') {
76-
seenEscape = true;
59+
for (var token : tokens) {
60+
if (!token.kind().equals(TokenKind.STRINGLITERAL)) {
61+
continue;
62+
}
63+
var sourceWithQuotes = source.substring(token.pos(), token.endPos());
64+
boolean isBlockLiteral = sourceWithQuotes.startsWith("\"\"\"");
65+
int quoteSize = isBlockLiteral ? 3 : 1;
66+
var literal = sourceWithQuotes.substring(quoteSize, sourceWithQuotes.length() - quoteSize);
67+
boolean seenEscape = false;
68+
for (int i = 0; i < literal.length(); ++i) {
69+
switch (literal.charAt(i)) {
70+
case '\n':
71+
seenEscape = false;
72+
break;
73+
case '\\':
74+
i++;
75+
if (literal.charAt(i) == 's') {
76+
seenEscape = true;
77+
break;
78+
}
79+
// fall through
80+
default:
81+
if (seenEscape) {
82+
return describeMatch(tree);
83+
}
7784
break;
78-
}
79-
// fall through
80-
default:
81-
if (seenEscape) {
82-
return describeMatch(tree);
83-
}
84-
break;
85+
}
86+
}
87+
// Catch _trailing_ \s at the end of non-block literals.
88+
if (seenEscape && !isBlockLiteral) {
89+
return describeMatch(tree);
8590
}
8691
}
8792
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,37 @@ class Test {
140140
}""")
141141
.doTest();
142142
}
143+
144+
@Test
145+
public void atEndOfString_noFinding() {
146+
assume().that(Runtime.version().feature()).isAtLeast(14);
147+
148+
testHelper
149+
.addSourceLines(
150+
"Test.class",
151+
"""
152+
class Test {
153+
private static final String FOO =
154+
\"""
155+
foo
156+
bar\\s\""";
157+
}
158+
""")
159+
.doTest();
160+
}
161+
162+
@Test
163+
public void escapedSpaceAtEndOfString() {
164+
assume().that(Runtime.version().feature()).isAtLeast(14);
165+
166+
testHelper
167+
.addSourceLines(
168+
"Test.class",
169+
"""
170+
class Test {
171+
// BUG: Diagnostic contains:
172+
private static final String FOO = "foo\\s";
173+
}""")
174+
.doTest();
175+
}
143176
}

0 commit comments

Comments
 (0)