8223967: Implement Text Blocks (Preview) in the Java compiler
Reviewed-by: vromero, jlahoda, abuckley
This commit is contained in:
parent
9c6c6eae1d
commit
08e1ece107
@ -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)
|
||||
*/
|
||||
|
@ -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'
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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).
|
||||
*/
|
||||
|
@ -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
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
173
test/langtools/tools/javac/TextBlockAPI.java
Normal file
173
test/langtools/tools/javac/TextBlockAPI.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
105
test/langtools/tools/javac/TextBlockLang.java
Normal file
105
test/langtools/tools/javac/TextBlockLang.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 """
|
||||
;
|
||||
}
|
||||
}
|
@ -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
|
||||
""";
|
||||
}
|
||||
}
|
@ -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""";
|
||||
}
|
||||
}
|
@ -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++)
|
||||
|
Loading…
Reference in New Issue
Block a user