Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -230,23 +230,41 @@ private static ImmutableList<String> stringComponents(
// adjust for leading and trailing double quotes
String text = input.substring(getStartPosition(tree) + 1, getEndPosition(tree, unit) - 1);
int start = 0;
for (int idx = 0; idx < text.length(); idx++) {
int next;
for (int idx = 0; idx < text.length(); idx = next) {
int splitAt;
int length;
if (CharMatcher.whitespace().matches(text.charAt(idx))) {
// continue below
} else if (hasEscapedWhitespaceAt(text, idx) != -1) {
// continue below
// split before whitespace
splitAt = idx;
next = idx + 1;
} else if ((length = hasEscapedWhitespaceAt(text, idx)) != -1) {
// split before escaped whitespace
splitAt = idx;
next = idx + length;
} else if (hasEscapedNewlineAt(text, idx) != -1) {
int length;
while ((length = hasEscapedNewlineAt(text, idx)) != -1) {
idx += length;
// Split after a run of escaped newlines, rather than before it, so the newlines end the
// preceding piece. `reflow` ends a line after a piece ending in an escaped newline, so
// where possible each line of the wrapped literal corresponds to a line of its value.
next = idx;
while ((length = hasEscapedNewlineAt(text, next)) != -1) {
next += length;
}
splitAt = next;
} else {
// Skip over the entire escape sequence, so the second character of an escape like `\\` is
// not mistaken for the start of another escape. Octal and Unicode escapes don't need
// special handling: after the backslash they only contain letters and digits, which are
// never split points.
next = idx + (text.charAt(idx) == '\\' ? 2 : 1);
continue;
}
piece.append(text, start, idx);
result.add(piece.toString());
piece = new StringBuilder();
start = idx;
if (splitAt > start || piece.length() > 0) {
piece.append(text, start, splitAt);
result.add(piece.toString());
piece = new StringBuilder();
start = splitAt;
}
}
if (piece.length() > 0) {
result.add(piece.toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,34 +118,6 @@ public void formatDiagnostics_positionOutOfRange() {
.isEqualTo("T.java:1:0: error: oops" + System.lineSeparator());
}

@Test
public void stringWrappingProducesInvalidCode() throws Exception {
// Reflowing this string splits the escaped backslash in `\\t`, which produces an unterminated
// string literal several lines past the end of the original input.
String input =
"""
class T {
String s = "%s%s\\\\tend";
}
"""
.formatted("word ".repeat(100), "x".repeat(95));

StringWriter stdout = new StringWriter();
StringWriter stderr = new StringWriter();
Main main = new Main(new PrintWriter(stdout, true), new PrintWriter(stderr, true), System.in);

Path tmpdir = testFolder.newFolder().toPath();
Path path = tmpdir.resolve("T.java");
Files.writeString(path, input);

int result = main.format(path.toString());
assertThat(stdout.toString()).isEmpty();
assertThat(stderr.toString())
.contains(
"it would produce code that does not parse (9:13: error: unclosed string literal)");
assertThat(result).isEqualTo(1);
}

@Test
public void oneFileParseError() throws Exception {
String one = "class One {\n";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.junit.Assume.assumeTrue;

import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
Expand Down Expand Up @@ -106,4 +107,85 @@ class T {
}
""");
}

// https://github.com/google/google-java-format/issues/1186
@Test
public void escapedBackslashBeforeT() throws Exception {
String input =
"""
class T {
String t =
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\\\\t";
}
""";
String output =
"""
class T {
String t =
"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\\\\t";
}
""";
assertThat(new Formatter().formatSourceAndFixImports(input)).isEqualTo(output);
}

// https://github.com/google/google-java-format/issues/1439
@Test
public void escapedBackslashes() throws Exception {
String input =
"""
class T {
String s = "%s xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\\\text{C} \\\\n \\\\\\\\";
}
"""
.formatted("word ".repeat(20));
String output =
"""
class T {
String s =
"word word word word word word word word word word word word word word word word word word"
+ " word word "
+ " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\\\\text{C} \\\\n"
+ " \\\\\\\\";
}
""";
assertThat(new Formatter().formatSourceAndFixImports(input)).isEqualTo(output);
}

@Test
public void escapesRandomized() throws Exception {
// The source text of string literal characters and escape sequences, including escaped
// backslashes followed by characters that would otherwise form escapes, and octal and Unicode
// escapes.
String[] atoms = {
"a",
"bb",
" ",
"\\\\",
"\\t",
"\\n",
"\\r\\n",
"\\\"",
"\\\\t",
"\\\\n",
"\\\\\\t",
"\\0",
"\\12",
"\\377",
"\\s",
"\\u0041",
"\\\\u0041"
};
Random random = new Random(42);
Formatter formatter = new Formatter();
for (int i = 0; i < 200; i++) {
StringBuilder literal = new StringBuilder();
while (literal.length() < 150) {
literal.append(atoms[random.nextInt(atoms.length)]);
}
String input = "class T {\n String s = \"" + literal + "\";\n}\n";
// StringWrapper checks that its output parses and has the same AST as its input. (This uses
// `unused` rather than `_` since google-java-format still supports building with JDK 21.)
String unused = StringWrapper.wrap(40, formatter.formatSource(input), formatter);
}
}
}
Loading