8223967: Implement Text Blocks (Preview) in the Java compiler

Reviewed-by: vromero, jlahoda, abuckley
This commit is contained in:
Jim Laskey 2019-06-06 12:24:44 -03:00
parent 9c6c6eae1d
commit 08e1ece107
14 changed files with 791 additions and 17 deletions

View File

@ -274,6 +274,11 @@ public class Lint
*/
STATIC("static"),
/**
* Warn about issues relating to use of text blocks
*/
TEXT_BLOCKS("text-blocks"),
/**
* Warn about issues relating to use of try blocks (i.e. try-with-resources)
*/

View File

@ -167,7 +167,8 @@ public class Preview {
public boolean isPreview(Feature feature) {
if (feature == Feature.SWITCH_EXPRESSION ||
feature == Feature.SWITCH_MULTIPLE_CASE_LABELS ||
feature == Feature.SWITCH_RULE)
feature == Feature.SWITCH_RULE ||
feature == Feature.TEXT_BLOCKS)
return true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'

View File

@ -188,7 +188,8 @@ public enum Source {
IMPORT_ON_DEMAND_OBSERVABLE_PACKAGES(JDK1_2, JDK8),
SWITCH_MULTIPLE_CASE_LABELS(JDK13, Fragments.FeatureMultipleCaseLabels, DiagKind.PLURAL),
SWITCH_RULE(JDK13, Fragments.FeatureSwitchRules, DiagKind.PLURAL),
SWITCH_EXPRESSION(JDK13, Fragments.FeatureSwitchExpressions, DiagKind.PLURAL);
SWITCH_EXPRESSION(JDK13, Fragments.FeatureSwitchExpressions, DiagKind.PLURAL),
TEXT_BLOCKS(JDK13, Fragments.FeatureTextBlocks, DiagKind.PLURAL);
enum DiagKind {
NORMAL,

View File

@ -25,15 +25,22 @@
package com.sun.tools.javac.parser;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Lint.LintCategory;
import com.sun.tools.javac.code.Preview;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
import com.sun.tools.javac.util.JCDiagnostic.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.CharBuffer;
import java.util.HashSet;
import java.util.Set;
import static com.sun.tools.javac.parser.Tokens.*;
import static com.sun.tools.javac.util.LayoutCharacters.*;
@ -84,8 +91,21 @@ public class JavaTokenizer {
*/
protected UnicodeReader reader;
/** Should the string stripped of indentation?
*/
protected boolean shouldStripIndent;
/** Should the string's escapes be translated?
*/
protected boolean shouldTranslateEscapes;
protected ScannerFactory fac;
// The set of lint options currently in effect. It is initialized
// from the context, and then is set/reset as needed by Attr as it
// visits all the various parts of the trees during attribution.
protected Lint lint;
private static final boolean hexFloatsWork = hexFloatsWork();
private static boolean hexFloatsWork() {
try {
@ -121,6 +141,7 @@ public class JavaTokenizer {
this.source = fac.source;
this.preview = fac.preview;
this.reader = reader;
this.lint = fac.lint;
}
protected void checkSourceLevel(int pos, Feature feature) {
@ -150,6 +171,11 @@ public class JavaTokenizer {
errPos = pos;
}
protected void lexWarning(LintCategory lc, int pos, JCDiagnostic.Warning key) {
DiagnosticPosition dp = new SimpleDiagnosticPosition(pos) ;
log.warning(lc, dp, key);
}
/** Read next character in character or string literal and copy into sbuf.
*/
private void scanLitChar(int pos) {
@ -200,6 +226,309 @@ public class JavaTokenizer {
}
}
/** Read next character in character or string literal and copy into sbuf
* without translating escapes. Used by text blocks to preflight verify
* escapes sequences.
*/
private void scanLitCharRaw(int pos) {
if (reader.ch == '\\') {
if (reader.peekChar() == '\\' && !reader.isUnicode()) {
reader.skipChar();
reader.putChar('\\', false);
reader.putChar('\\', true);
} else {
reader.putChar('\\', true);
switch (reader.ch) {
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
char leadch = reader.ch;
reader.putChar(true);
if ('0' <= reader.ch && reader.ch <= '7') {
reader.putChar(true);
if (leadch <= '3' && '0' <= reader.ch && reader.ch <= '7') {
reader.putChar(true);
}
}
break;
// Effectively list of valid escape sequences.
case 'b':
case 't':
case 'n':
case 'f':
case 'r':
case '\'':
case '\"':
case '\\':
reader.putChar(true); break;
default:
lexError(reader.bp, Errors.IllegalEscChar);
}
}
} else if (reader.bp != reader.buflen) {
reader.putChar(true);
}
}
/** Interim access to String methods used to support text blocks.
* Required to handle bootstrapping with pre-text block jdks.
* Could be reworked in the 'next' jdk.
*/
static class TextBlockSupport {
/** Reflection method to remove incidental indentation.
*/
private static final Method stripIndent;
/** Reflection method to translate escape sequences.
*/
private static final Method translateEscapes;
/** true if stripIndent and translateEscapes are available in the bootstrap jdk.
*/
private static final boolean hasSupport;
/** Get a string method via refection or null if not available.
*/
private static Method getStringMethodOrNull(String name) {
try {
return String.class.getMethod(name);
} catch (Exception ex) {
// Method not available, return null.
}
return null;
}
static {
// Get text block string methods.
stripIndent = getStringMethodOrNull("stripIndent");
translateEscapes = getStringMethodOrNull("translateEscapes");
// true if stripIndent and translateEscapes are available in the bootstrap jdk.
hasSupport = stripIndent != null && translateEscapes != null;
}
/** Return true if stripIndent and translateEscapes are available in the bootstrap jdk.
*/
static boolean hasSupport() {
return hasSupport;
}
/** Return the leading whitespace count (indentation) of the line.
*/
private static int indent(String line) {
return line.length() - line.stripLeading().length();
}
enum WhitespaceChecks {
INCONSISTENT,
TRAILING
};
/** Check that the use of white space in content is not problematic.
*/
static Set<WhitespaceChecks> checkWhitespace(String string) {
// Start with empty result set.
Set<WhitespaceChecks> checks = new HashSet<>();
// No need to check empty strings.
if (string.isEmpty()) {
return checks;
}
// Maximum common indentation.
int outdent = 0;
// No need to check indentation if opting out (last line is empty.)
char lastChar = string.charAt(string.length() - 1);
boolean optOut = lastChar == '\n' || lastChar == '\r';
// Split string based at line terminators.
String[] lines = string.split("\\R");
int length = lines.length;
// Extract last line.
String lastLine = lines[length - 1];
if (!optOut) {
// Prime with the last line indentation (may be blank.)
outdent = indent(lastLine);
for (String line : lines) {
// Blanks lines have no influence (last line accounted for.)
if (!line.isBlank()) {
outdent = Integer.min(outdent, indent(line));
if (outdent == 0) {
break;
}
}
}
}
// Last line is representative.
String start = lastLine.substring(0, outdent);
for (String line : lines) {
// Fail if a line does not have the same indentation.
if (!line.isBlank() && !line.startsWith(start)) {
// Mix of different white space
checks.add(WhitespaceChecks.INCONSISTENT);
}
// Line has content even after indent is removed.
if (outdent < line.length()) {
// Is the last character a white space.
lastChar = line.charAt(line.length() - 1);
if (Character.isWhitespace(lastChar)) {
// Has trailing white space.
checks.add(WhitespaceChecks.TRAILING);
}
}
}
return checks;
}
/** Invoke String::stripIndent through reflection.
*/
static String stripIndent(String string) {
try {
string = (String)stripIndent.invoke(string);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return string;
}
/** Invoke String::translateEscapes through reflection.
*/
static String translateEscapes(String string) {
try {
string = (String)translateEscapes.invoke(string);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return string;
}
}
/** Test for EOLN.
*/
private boolean isEOLN() {
return reader.ch == LF || reader.ch == CR;
}
/** Test for CRLF.
*/
private boolean isCRLF() {
return reader.ch == CR && reader.peekChar() == LF;
}
/** Count and skip repeated occurances of the specified character.
*/
private int countChar(char ch, int max) {
int count = 0;
for ( ; count < max && reader.bp < reader.buflen && reader.ch == ch; count++) {
reader.scanChar();
}
return count;
}
/** Scan a string literal or text block.
*/
private void scanString(int pos) {
// Clear flags.
shouldStripIndent = false;
shouldTranslateEscapes = false;
// Check if text block string methods are present.
boolean hasTextBlockSupport = TextBlockSupport.hasSupport();
// Track the end of first line for error recovery.
int firstEOLN = -1;
// Attempt to scan for up to 3 double quotes.
int openCount = countChar('\"', 3);
switch (openCount) {
case 1: // Starting a string literal.
break;
case 2: // Starting an empty string literal.
// Start again but only consume one quote.
reader.reset(pos);
openCount = countChar('\"', 1);
break;
case 3: // Starting a text block.
// Check if preview feature is enabled for text blocks.
checkSourceLevel(pos, Feature.TEXT_BLOCKS);
// Only proceed if text block string methods are present.
if (hasTextBlockSupport) {
// Indicate that the final string should have incidental indentation removed.
shouldStripIndent = true;
// Verify the open delimiter sequence.
boolean hasOpenEOLN = false;
while (reader.bp < reader.buflen && Character.isWhitespace(reader.ch)) {
hasOpenEOLN = isEOLN();
if (hasOpenEOLN) {
break;
}
reader.scanChar();
}
// Error if the open delimiter sequence not is """<Whitespace>*<LineTerminator>.
if (!hasOpenEOLN) {
lexError(reader.bp, Errors.IllegalTextBlockOpen);
return;
}
// Skip line terminator.
int start = reader.bp;
if (isCRLF()) {
reader.scanChar();
}
reader.scanChar();
processLineTerminator(start, reader.bp);
} else {
// No text block string methods are present, so reset and treat like string literal.
reader.reset(pos);
openCount = countChar('\"', 1);
}
break;
}
// While characters are available.
while (reader.bp < reader.buflen) {
// If possible close delimiter sequence.
if (reader.ch == '\"') {
// Check to see if enough double quotes are present.
int closeCount = countChar('\"', openCount);
if (openCount == closeCount) {
// Good result.
tk = Tokens.TokenKind.STRINGLITERAL;
return;
}
// False alarm, add double quotes to string buffer.
reader.repeat('\"', closeCount);
} else if (isEOLN()) {
// Line terminator in string literal is an error.
// Fall out to unclosed string literal error.
if (openCount == 1) {
break;
}
// Add line terminator to string buffer.
int start = reader.bp;
if (isCRLF()) {
reader.scanChar();
}
reader.putChar('\n', true);
processLineTerminator(start, reader.bp);
// Record first line terminator for error recovery.
if (firstEOLN == -1) {
firstEOLN = reader.bp;
}
} else if (reader.ch == '\\') {
// Handle escape sequences.
if (hasTextBlockSupport) {
// Indicate that the final string should have escapes translated.
shouldTranslateEscapes = true;
// Validate escape sequence and add to string buffer.
scanLitCharRaw(pos);
} else {
// Translate escape sequence and add result to string buffer.
scanLitChar(pos);
}
} else {
// Add character to string buffer.
reader.putChar(true);
}
}
// String ended without close delimiter sequence.
lexError(pos, openCount == 1 ? Errors.UnclosedStrLit : Errors.UnclosedTextBlock);
if (firstEOLN != -1) {
// Reset recovery position to point after open delimiter sequence.
reader.reset(firstEOLN);
}
}
private void scanDigits(int pos, int digitRadix) {
char saveCh;
int savePos;
@ -624,7 +953,7 @@ public class JavaTokenizer {
lexError(pos, Errors.EmptyCharLit);
reader.scanChar();
} else {
if (reader.ch == CR || reader.ch == LF)
if (isEOLN())
lexError(pos, Errors.IllegalLineEndInCharLit);
scanLitChar(pos);
if (reader.ch == '\'') {
@ -636,17 +965,9 @@ public class JavaTokenizer {
}
break loop;
case '\"':
reader.scanChar();
while (reader.ch != '\"' && reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen)
scanLitChar(pos);
if (reader.ch == '\"') {
tk = TokenKind.STRINGLITERAL;
reader.scanChar();
} else {
lexError(pos, Errors.UnclosedStrLit);
}
scanString(pos);
break loop;
default:
default:
if (isSpecial(reader.ch)) {
scanOperator();
} else {
@ -695,7 +1016,34 @@ public class JavaTokenizer {
switch (tk.tag) {
case DEFAULT: return new Token(tk, pos, endPos, comments);
case NAMED: return new NamedToken(tk, pos, endPos, name, comments);
case STRING: return new StringToken(tk, pos, endPos, reader.chars(), comments);
case STRING: {
// Get characters from string buffer.
String string = reader.chars();
// If a text block.
if (shouldStripIndent) {
// Verify that the incidental indentation is consistent.
if (lint.isEnabled(LintCategory.TEXT_BLOCKS)) {
Set<TextBlockSupport.WhitespaceChecks> checks =
TextBlockSupport.checkWhitespace(string);
if (checks.contains(TextBlockSupport.WhitespaceChecks.INCONSISTENT)) {
lexWarning(LintCategory.TEXT_BLOCKS, pos,
Warnings.InconsistentWhiteSpaceIndentation);
}
if (checks.contains(TextBlockSupport.WhitespaceChecks.TRAILING)) {
lexWarning(LintCategory.TEXT_BLOCKS, pos,
Warnings.TrailingWhiteSpaceWillBeRemoved);
}
}
// Remove incidental indentation.
string = TextBlockSupport.stripIndent(string);
}
// Translate escape sequences if present.
if (shouldTranslateEscapes) {
string = TextBlockSupport.translateEscapes(string);
}
// Build string token.
return new StringToken(tk, pos, endPos, string, comments);
}
case NUMERIC: return new NumericToken(tk, pos, endPos, reader.chars(), radix, comments);
default: throw new AssertionError();
}

View File

@ -27,6 +27,7 @@ package com.sun.tools.javac.parser;
import java.nio.CharBuffer;
import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Preview;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.util.Context;
@ -59,6 +60,7 @@ public class ScannerFactory {
final Source source;
final Preview preview;
final Tokens tokens;
final Lint lint;
/** Create a new scanner factory. */
protected ScannerFactory(Context context) {
@ -68,6 +70,7 @@ public class ScannerFactory {
this.source = Source.instance(context);
this.preview = Preview.instance(context);
this.tokens = Tokens.instance(context);
this.lint = Lint.instance(context);
}
public Scanner newScanner(CharSequence input, boolean keepDocComments) {

View File

@ -154,6 +154,21 @@ public class UnicodeReader {
return new String(sbuf, 0, sp);
}
/** Add 'count' copies of the character 'ch' to the string buffer.
*/
protected void repeat(char ch, int count) {
for ( ; 0 < count; count--) {
putChar(ch, false);
}
}
/** Reset the scan buffer pointer to 'pos'.
*/
protected void reset(int pos) {
bp = pos - 1;
scanChar();
}
/** Convert unicode escape; bp points to initial '\' character
* (Spec 3.3).
*/

View File

@ -623,6 +623,15 @@ compiler.err.illegal.initializer.for.type=\
compiler.err.illegal.line.end.in.char.lit=\
illegal line end in character literal
compiler.err.illegal.text.block.open=\
illegal text block open delimiter sequence, missing line terminator
compiler.warn.inconsistent.white.space.indentation=\
inconsistent white space indentation
compiler.warn.trailing.white.space.will.be.removed=\
trailing white space will be removed
compiler.err.illegal.nonascii.digit=\
illegal non-ASCII digit
@ -1244,6 +1253,9 @@ compiler.err.unclosed.comment=\
compiler.err.unclosed.str.lit=\
unclosed string literal
compiler.err.unclosed.text.block=\
unclosed text block
# 0: string
compiler.err.unsupported.encoding=\
unsupported encoding: {0}
@ -2859,6 +2871,9 @@ compiler.misc.feature.static.intf.method.invoke=\
compiler.misc.feature.private.intf.methods=\
private interface methods
compiler.misc.feature.text.blocks=\
text blocks
compiler.misc.feature.multiple.case.labels=\
multiple case labels

View File

@ -243,6 +243,9 @@ javac.opt.Xlint.desc.serial=\
javac.opt.Xlint.desc.static=\
Warn about accessing a static member using an instance.
javac.opt.Xlint.desc.text-blocks=\
Warn about inconsistent white space characters in text block indentation.
javac.opt.Xlint.desc.try=\
Warn about issues relating to use of try blocks (i.e. try-with-resources).

View File

@ -0,0 +1,173 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8223967
* @summary Unit tests for Text Block language changes
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* @build toolbox.ToolBox toolbox.JavacTask
* @run main TextBlockAPI
*/
import toolbox.JavacTask;
import toolbox.JavaTask;
import toolbox.Task;
import toolbox.ToolBox;
public class TextBlockAPI {
private static ToolBox TOOLBOX = new ToolBox();
private final static String JDK_VERSION = Integer.toString(Runtime.version().feature());
public static void main(String... args) {
test1();
test2();
test3();
test4();
}
/*
* Check that correct/incorrect syntax is properly detected
*/
static void test1() {
for (String lineterminators : new String[] { "\n", "\r", "\r\n" })
for (String whitespace : new String[] { "", " ", "\t", "\u2002" })
for (String content : new String[] { "a", "ab", "abc", "\u2022", "*".repeat(1000), "*".repeat(10000) }) {
String code =
"public class CorrectTest {\n" +
" public static void main(String... args) {\n" +
" String xxx = " +
"\"\"\"" + whitespace + lineterminators +
content +
"\"\"\";\n" +
" }\n" +
"}\n";
compPass(code);
}
}
/*
* Check that use of \u0022 is properly detected
*/
static void test2() {
compPass("public class UnicodeDelimiterTest {\n" +
" public static void main(String... args) {\n" +
" String xxx = \\u0022\\u0022\\u0022\nabc\n\\u0022\\u0022\\u0022;\n" +
" }\n" +
"}\n");
}
/*
* Check edge cases of text blocks as last token
*/
static void test3() {
compFail("public class EndTest {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc\"\"\"");
compFail("public class TwoQuoteClose {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc\"\"");
compFail("public class OneQuoteClose {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc\"");
compFail("public class NoClose {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc");
compFail("public class ZeroTerminator {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc\\u0000");
compFail("public class NonBreakingSpace {\n" +
" public static void main(String... args) {\n" +
" String xxx = \"\"\"\nabc\\u001A");
}
/*
* Check line terminator translation
*/
static void test4() {
String[] terminators = new String[] { "\n", "\r\n", "\r" };
for (String terminator : terminators) {
String code = "public class LineTerminatorTest {" + terminator +
" public static void main(String... args) {" + terminator +
" String s =" + terminator +
"\"\"\"" + terminator +
"abc" + terminator +
"\"\"\";" + terminator +
" System.out.println(s.equals(\"abc\\n\"));" + terminator +
" }" + terminator +
"}" + terminator;
new JavacTask(TOOLBOX)
.sources(code)
.classpath(".")
.options("--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
.run();
String output = new JavaTask(TOOLBOX)
.vmOptions("--enable-preview")
.classpath(".")
.classArgs("LineTerminatorTest")
.run()
.writeAll()
.getOutput(Task.OutputKind.STDOUT);
if (!output.contains("true")) {
throw new RuntimeException("Error detected");
}
}
}
/*
* Test source for successful compile.
*/
static void compPass(String source) {
String output = new JavacTask(TOOLBOX)
.sources(source)
.classpath(".")
.options("--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
if (output.contains("compiler.err")) {
throw new RuntimeException("Error detected");
}
}
/*
* Test source for unsuccessful compile and specific error.
*/
static void compFail(String source) {
String errors = new JavacTask(TOOLBOX)
.sources(source)
.classpath(".")
.options("-XDrawDiagnostics", "--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
.run(Task.Expect.FAIL)
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
if (!errors.contains("compiler.err")) {
throw new RuntimeException("No error detected");
}
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8223967
* @summary Unit tests for Text Block language changes
* @compile --enable-preview -source ${jdk.version} -encoding utf8 TextBlockLang.java
* @run main/othervm --enable-preview TextBlockLang
*/
public class TextBlockLang {
public static void main(String... args) {
test1();
}
/*
* Test basic string functionality.
*/
static void test1() {
EQ("""
""", "");
EQ("""
abc
""", "abc\n");
EQ("""
""", "\n");
EQ("""
"
""", "\"\n");
EQ("""
""
""", "\"\"\n");
EQ("""
\"""
""", "\"\"\"\n");
EQ("""
"\""
""", "\"\"\"\n");
EQ("""
""\"
""", "\"\"\"\n");
EQ("""
\r
""", "\r\n");
EQ("""
\u2022
""", "\u2022\n");
EQ("""
""", "\u2022\n");
LENGTH("""
abc
""", 4);
}
/*
* Raise an exception if the string is not the expected length.
*/
static void LENGTH(String string, int length) {
if (string == null || string.length() != length) {
System.err.println("Failed LENGTH");
System.err.println(string + " " + length);
throw new RuntimeException("Failed LENGTH");
}
}
/*
* Raise an exception if the two input strings are not equal.
*/
static void EQ(String input, String expected) {
if (input == null || expected == null || !expected.equals(input)) {
System.err.println("Failed EQ");
System.err.println();
System.err.println("Input:");
System.err.println(input.replaceAll(" ", "."));
System.err.println();
System.err.println("Expected:");
System.err.println(expected.replaceAll(" ", "."));
throw new RuntimeException();
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.text.blocks
// key: compiler.err.unclosed.text.block
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class TextBlockCloseDelimiter {
String m() {
return """
;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.text.blocks
// key: compiler.err.illegal.text.block.open
// options: --enable-preview -source ${jdk.version} -Xlint:preview
class TextBlockOpenDelimiter {
String m() {
return """xxxx
""";
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.warn.preview.feature.use.plural
// key: compiler.misc.feature.text.blocks
// key: compiler.warn.inconsistent.white.space.indentation
// key: compiler.warn.trailing.white.space.will.be.removed
// options: --enable-preview -source ${jdk.version} -Xlint:preview,text-blocks
class TextBlockWhitespace {
String m() {
return """
\u0009\u0009\u0009\u0009tab indentation
\u0020\u0020\u0020\u0020space indentation and trailing space\u0020
\u0020\u0020\u0020\u0020""";
}
}

View File

@ -37,8 +37,8 @@ public class WhitespaceTest {
}
void run() throws Exception {
test("-v", "java.lang.String");
test("-XDtab:1", "-v", "java.lang.String");
test("-v", "java.lang.Object");
test("-XDtab:1", "-v", "java.lang.Object");
String testClasses = System.getProperty("test.classes");
for (int i = 10; i < 40; i++)