Merge branch '7.0.x' into java-grammar

This commit is contained in:
Clément Fournier
2020-08-22 21:22:41 +02:00
56 changed files with 2516 additions and 169 deletions

View File

@ -185,7 +185,7 @@ Example:
* [apex](pmd_rules_apex.html) (Salesforce Apex)
* [java](pmd_rules_java.html)
* Supported Versions: 1.3, 1.4, 1.5, 5, 1.6, 6, 1.7, 7, 1.8, 8, 9, 1.9, 10, 1.10, 11, 12,
13, 13-preview, 14 (default), 14-preview
13, 14, 14-preview, 15 (default), 15-preview
* [ecmascript](pmd_rules_ecmascript.html) (JavaScript)
* [jsp](pmd_rules_jsp.html)
* [modelica](pmd_rules_modelica.html)

View File

@ -19,6 +19,24 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### Java 15 Support
This release of PMD brings support for Java 15. PMD can parse [Text Blocks](https://openjdk.java.net/jeps/378)
which have been promoted to be a standard language feature of Java.
PMD also supports [Pattern Matching for instanceof](https://openjdk.java.net/jeps/375),
[Records](https://openjdk.java.net/jeps/384), and [Sealed Classes](https://openjdk.java.net/jeps/360).
Note: The Pattern Matching for instanceof, Records, and Sealed Classes are all preview language features of OpenJDK 15
and are not enabled by default. In order to
analyze a project with PMD that uses these language features, you'll need to enable it via the environment
variable `PMD_JAVA_OPTS` and select the new language version `15-preview`:
export PMD_JAVA_OPTS=--enable-preview
./run.sh pmd -language java -version 15-preview ...
Note: Support for Java 13 preview language features have been removed. The version "13-preview" is no longer available.
#### Changes in how tab characters are handled
In the past, tab characters in source files has been handled differently in different languages by PMD.
@ -65,6 +83,8 @@ See also [[all] Ensure PMD/CPD uses tab width of 1 for tabs consistently #2656](
* [#2653](https://github.com/pmd/pmd/issues/2653): \[lang-test] Upgrade kotlintest to Kotest
* [#2656](https://github.com/pmd/pmd/pull/2656): \[all] Ensure PMD/CPD uses tab width of 1 for tabs consistently
* [#2690](https://github.com/pmd/pmd/pull/2690): \[core] Fix java7 compatibility
* java
* [#2646](https://github.com/pmd/pmd/issues/2646): \[java] Support JDK 15
* java-bestpractices
* [#2471](https://github.com/pmd/pmd/issues/2471): \[java] New Rule: AvoidReassigningCatchVariables
* [#2663](https://github.com/pmd/pmd/issues/2663): \[java] NoClassDefFoundError on upgrade from 6.25.0 to 6.26.0

View File

@ -1,4 +1,12 @@
/**
* Remove support for Java 13 preview language features.
* Promote text blocks as a permanent language features with Java 15.
* Support Pattern Matching for instanceof with Java 15 Preview.
* Support Records with Java 15 Preview.
* Support Local Records with Java 15 Preview.
* Support Sealed Classes with Java 15 Preview.
* Andreas Dangel 08/2020
*====================================================================
* Add support for record types introduced as a preview language
* feature with Java 14. See JEP 359.
* Andreas Dangel 02/2020
@ -227,6 +235,7 @@ PARSER_BEGIN(JavaParserImpl)
package net.sourceforge.pmd.lang.java.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@ -234,6 +243,7 @@ import java.util.Map;
import net.sourceforge.pmd.lang.ast.CharStream;
import net.sourceforge.pmd.lang.ast.GenericToken;
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.TokenMgrError;
import net.sourceforge.pmd.lang.ast.Node;
@ -260,6 +270,16 @@ class JavaParserImpl {
throw new ParseException("Line " + line + ", Column " + col + ": " + message);
}
private boolean isRecordTypeSupported() {
return (jdkVersion == 14 || jdkVersion == 15) && preview;
}
private boolean isSealedClassSupported() {
return jdkVersion == 15 && preview;
}
/**
* Keeps track during tree construction, whether we are currently building a switch label.
* A switch label must not contain a LambdaExpression.
@ -285,9 +305,65 @@ class JavaParserImpl {
/**
* Semantic lookahead to check if the next identifier is a
* specific restricted keyword.
*
* <p>Restricted keywords are:
* var, yield, record, sealed, permits, "non" + "-" + "sealed"
*
* <p>enum and assert is used like restricted keywords, as they were not keywords
* in the early java versions.
*/
private boolean isKeyword(String keyword) {
return getToken(1).kind == IDENTIFIER && getToken(1).getImage().equals(keyword);
private boolean isKeyword(String image) {
return isKeyword(1, image);
}
private boolean isKeyword(int index, String image) {
Token token = getToken(index);
return token.kind == IDENTIFIER && token.image.equals(image);
}
private boolean isToken(int index, int kind) {
return getToken(index).kind == kind;
}
/**
* Semantic lookahead which matches "non-sealed".
*
* <p>"non-sealed" cannot be a token, for several reasons:
* It is only a keyword with java15 preview+, it consists actually
* of several separate tokens, which are valid on their own.
*/
private boolean isNonSealedModifier() {
if (isKeyword(1, "non") && isToken(2, MINUS) && isKeyword(3, "sealed")) {
JavaccToken nonToken = getToken(1);
JavaccToken minusToken = getToken(2);
JavaccToken sealedToken = getToken(3);
return nonToken.getEndColumn() == minusToken.getBeginColumn()
&& minusToken.getEndColumn() == sealedToken.getBeginColumn();
}
return false;
}
private boolean classModifierLookahead() {
Token next = getToken(1);
return next.kind == AT
|| next.kind == PUBLIC
|| next.kind == PROTECTED
|| next.kind == PRIVATE
|| next.kind == ABSTRACT
|| next.kind == STATIC
|| next.kind == FINAL
|| next.kind == STRICTFP
|| isSealedClassSupported() && isKeyword("sealed")
|| isSealedClassSupported() && isNonSealedModifier();
}
private boolean localTypeDeclLookahead() {
Token next = getToken(1);
return next.kind == CLASS
|| isRecordTypeSupported() && next.kind == INTERFACE
|| isRecordTypeSupported() && next.kind == AT && isToken(2, INTERFACE)
|| isRecordTypeSupported() && next.kind == IDENTIFIER && next.getImage().equals("enum")
|| isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("record");
}
/**
@ -875,6 +951,8 @@ void ModifierList():
| "volatile" { modifiers.add(JModifier.VOLATILE); }
| "strictfp" { modifiers.add(JModifier.STRICTFP); }
| "default" { modifiers.add(JModifier.DEFAULT); }
| LOOKAHEAD({isKeyword("sealed")}) <IDENTIFIER> { modifiers |= AccessNode.SEALED; }
| LOOKAHEAD({isNonSealedModifier()}) <IDENTIFIER> <MINUS> <IDENTIFIER> { modifiers |= AccessNode.NON_SEALED; }
| Annotation()
)
)*
@ -906,6 +984,7 @@ void ClassOrInterfaceDeclaration():
[ TypeParameters() ]
[ ExtendsList() ]
[ ImplementsList() ]
[ LOOKAHEAD({isKeyword("permits")}) PermittedSubclasses() ]
ClassOrInterfaceBody()
}
@ -923,6 +1002,20 @@ void ImplementsList():
( "," AnnotatedClassOrInterfaceType() )*
}
void PermittedSubclasses() #PermitsList:
{
Token t;
}
{
t = <IDENTIFIER> {
if (!"permits".equals(t.image)) {
throw new ParseException("ERROR: expecting permits");
}
}
(TypeAnnotation())* ClassOrInterfaceType()
( "," (TypeAnnotation())* ClassOrInterfaceType() )*
}
void EnumDeclaration():
{
JavaccToken t;
@ -2069,13 +2162,45 @@ void Block() :
}
void BlockStatement() #void:
{}
{int mods = 0;}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
|
LOOKAHEAD(( "final" | Annotation() )* Type() <IDENTIFIER>)
LocalVariableDeclaration() ";" {
| LOOKAHEAD( "@" | "final" )
// this eagerly parses all modifiers and annotations. After that, either a local type declaration
// or a local variable declaration follows.
// This allows more modifiers for local variables than actually allowed
// and the annotations for local variables need to be moved in the AST down again.
mods=Modifiers()
(
LOOKAHEAD({localTypeDeclLookahead()}) LocalTypeDecl(mods)
|
{
List<ASTAnnotation> annotations = new ArrayList<ASTAnnotation>();
while (jjtree.peekNode() instanceof ASTAnnotation) {
annotations.add((ASTAnnotation) jjtree.popNode());
}
}
LocalVariableDeclaration()
{
ASTLocalVariableDeclaration localVarDecl = (ASTLocalVariableDeclaration) jjtree.peekNode();
if ((mods & AccessNode.FINAL) == AccessNode.FINAL) {
localVarDecl.setFinal(true);
}
if (!annotations.isEmpty()) {
Collections.reverse(annotations);
for (ASTAnnotation a : annotations) {
localVarDecl.insertChild(a, 0);
}
}
}
";"
)
| LOOKAHEAD({classModifierLookahead() || localTypeDeclLookahead()})
mods=Modifiers()
LocalTypeDecl(mods)
| LOOKAHEAD(Type() <IDENTIFIER>)
LocalVariableDeclaration() ";" {
// make it so that the LocalVariableDeclaration's last token is the semicolon
AbstractJavaNode top = (AbstractJavaNode) jjtree.peekNode();
top.setLastToken(getToken(0));
@ -2084,25 +2209,21 @@ void BlockStatement() #void:
// we need to lookahead until the "class" token,
// because a method ref may be annotated
// -> so Expression, and hence Statement, may start with "@"
LOOKAHEAD(ModifierList() "class") LocalClassDecl()
LOOKAHEAD(ModifierList() "class") LocalTypeDecl()
|
Statement()
}
void LocalClassDecl() #LocalClassStatement:
void LocalTypeDecl(int mods) #LocalClassStatement:
{}
{
// this preserves the modifiers of the local class.
// it allows for modifiers that are forbidden for local classes,
// but anyway we are *not* checking modifiers for incompatibilities
// anywhere else in this grammar (and indeed the production Modifiers
// accepts any modifier explicitly for the purpose of forgiving modifier errors,
// and reporting them later if needed --see its documentation).
// In particular, it unfortunately allows local class declarations to start
// with a "default" modifier, which introduces an ambiguity with default
// switch labels. This is guarded by a custom lookahead around SwitchLabel
ModifierList() ClassOrInterfaceDeclaration()
(
LOOKAHEAD(<CLASS>) ClassOrInterfaceDeclaration(mods)
| LOOKAHEAD(<INTERFACE>) ClassOrInterfaceDeclaration(mods)
| LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods)
| LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(mods)
| AnnotationTypeDeclaration(mods)
)
}
/*

View File

@ -28,9 +28,10 @@ public class JavaLanguageModule extends BaseLanguageModule {
addVersion("11", new JavaLanguageHandler(11));
addVersion("12", new JavaLanguageHandler(12));
addVersion("13", new JavaLanguageHandler(13));
addVersion("13-preview", new JavaLanguageHandler(13, true));
addDefaultVersion("14", new JavaLanguageHandler(14)); // 14 is the default
addVersion("14", new JavaLanguageHandler(14));
addVersion("14-preview", new JavaLanguageHandler(14, true));
addDefaultVersion("15", new JavaLanguageHandler(15)); // 15 is the default
addVersion("15-preview", new JavaLanguageHandler(15, true));
}
}

View File

@ -4,7 +4,12 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.Collections;
import java.util.List;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.util.CollectionUtil;
/**
@ -67,4 +72,10 @@ public final class ASTClassOrInterfaceDeclaration extends AbstractAnyTypeDeclara
return extendsList == null ? null : extendsList.iterator().next();
}
@Experimental
public List<ASTClassOrInterfaceType> getPermittedSubclasses() {
return ASTList.orEmpty(children(ASTPermitsList.class));
}
}

View File

@ -27,7 +27,6 @@ public interface ASTLiteral extends ASTPrimaryExpression {
return this instanceof ASTStringLiteral;
}
/**
* Returns true if this is a {@linkplain ASTCharLiteral character literal}.
*/

View File

@ -8,7 +8,7 @@ import net.sourceforge.pmd.annotation.Experimental;
/**
* A pattern (for pattern matching constructs like {@link ASTInstanceOfExpression InstanceOfExpression}).
* This is a JDK 14 preview feature and is subject to change.
* This is a JDK 14 and JDK 15 preview feature and is subject to change.
*
* <p>This interface will be implemented by all forms of patterns. For
* now, only type test patterns are supported. Record deconstruction

View File

@ -0,0 +1,41 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.Iterator;
import net.sourceforge.pmd.annotation.Experimental;
/**
* Represents the {@code permits} clause of a (sealed) class declaration.
*
* <p>This is a Java 15 Preview feature.
*
* <p>See https://openjdk.java.net/jeps/360
*
* <pre class="grammar">
*
* PermittedSubclasses ::= "permits" (TypeAnnotation)* ClassOrInterfaceType
* ( "," (TypeAnnotation)* ClassOrInterfaceType )*
* </pre>
*/
@Experimental
public final class ASTPermitsList extends AbstractJavaNode implements Iterable<ASTClassOrInterfaceType> {
ASTPermitsList(int id) {
super(id);
}
@Override
protected <P, R> R acceptVisitor(JavaVisitor<? super P, ? extends R> visitor, P data) {
return visitor.visit(this, data);
}
@Override
public Iterator<ASTClassOrInterfaceType> iterator() {
return children(ASTClassOrInterfaceType.class).iterator();
}
}

View File

@ -8,7 +8,7 @@ package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* Defines the body of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 preview feature).
* Defines the body of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 and JDK 15 preview feature).
* This can contain additional methods and or constructors.
*
* <pre class="grammar">

View File

@ -11,7 +11,7 @@ import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
/**
* Defines a single component of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 preview feature).
* Defines a single component of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 and JDK 15 preview feature).
*
* <p>The varargs ellipsis {@code "..."} is parsed as an {@linkplain ASTArrayTypeDim array dimension}
* in the type node.

View File

@ -11,7 +11,7 @@ import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AllChildrenAreOfType
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
/**
* Defines the state description of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 preview feature).
* Defines the state description of a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 and JDK 15 preview feature).
*
* <pre class="grammar">
*

View File

@ -10,7 +10,7 @@ import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
/**
* This defines a compact constructor for a {@link ASTRecordDeclaration RecordDeclaration}
* (JDK 14 preview feature). Compact constructors implicitly declares formal
* (JDK 14 and JDK 15 preview feature). Compact constructors implicitly declares formal
* parameters corresponding to the record component list. These can be
* fetched from {@link #getSymbol()}.
*

View File

@ -12,7 +12,7 @@ import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.ast.NodeStream;
/**
* A record declaration is a special data class type (JDK 14 preview feature).
* A record declaration is a special data class type (JDK 14 and JDK 15 preview feature).
* This is a {@linkplain Node#isFindBoundary() find boundary} for tree traversal methods.
*
* <pre class="grammar">
@ -27,7 +27,7 @@ import net.sourceforge.pmd.lang.ast.NodeStream;
*
* </pre>
*
* @see <a href="https://openjdk.java.net/jeps/359">JEP 359: Records (Preview)</a>
* @see <a href="https://openjdk.java.net/jeps/384">JEP 384: Records (Second Preview)</a>
*/
@Experimental
public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {

View File

@ -50,6 +50,11 @@ abstract class AbstractJavaNode extends AbstractJjtreeNode<AbstractJavaNode, Jav
super.addChild(child, index);
}
@Override // override to make it accessible to tests that build nodes (which have been removed on java-grammar)
protected void insertChild(AbstractJavaNode child, int index) {
super.insertChild(child, index);
}
@Override
protected void removeChildAtIndex(int childIndex) {
super.removeChildAtIndex(childIndex);

View File

@ -15,6 +15,7 @@ import net.sourceforge.pmd.internal.util.IteratorUtil;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
import net.sourceforge.pmd.lang.java.ast.ASTAssertStatement;
import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
@ -44,6 +45,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters;
import net.sourceforge.pmd.lang.java.ast.ASTTypeTestPattern;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
import net.sourceforge.pmd.lang.java.ast.AccessNode;
import net.sourceforge.pmd.lang.java.ast.JModifier;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.ast.JavaVisitorBase;
@ -116,13 +118,16 @@ public class LanguageLevelChecker<T> {
SWITCH_EXPRESSIONS(12, 13, true),
SWITCH_RULES(12, 13, true),
TEXT_BLOCK_LITERALS(13, 14, false),
TEXT_BLOCK_LITERALS(13, 14, true),
YIELD_STATEMENTS(13, 13, true),
/** \s */
SPACE_STRING_ESCAPES(14, 14, false),
RECORD_DECLARATIONS(14, 14, false),
TYPE_TEST_PATTERNS_IN_INSTANCEOF(14, 14, false);
SPACE_STRING_ESCAPES(14, 14, true),
RECORD_DECLARATIONS(14, 15, false),
TYPE_TEST_PATTERNS_IN_INSTANCEOF(14, 15, false),
SEALED_CLASSES(15, 15, false),
; // SUPPRESS CHECKSTYLE enum trailing semi is awesome
private final int minPreviewVersion;
@ -473,6 +478,12 @@ public class LanguageLevelChecker<T> {
@Override
public Void visit(ASTAnyTypeDeclaration node, T data) {
if ((node.getModifiers() & (AccessNode.SEALED | AccessNode.NON_SEALED)) != 0) {
check(node, PreviewFeature.SEALED_CLASSES, data);
} else if (node.isLocal() && node.getTypeKind() != TypeKind.CLASS) {
check(node, PreviewFeature.SEALED_CLASSES, data);
}
String simpleName = node.getSimpleName();
if ("var".equals(simpleName)) {
check(node, ReservedIdentifiers.VAR_AS_A_TYPE_NAME, data);

View File

@ -40,7 +40,7 @@ public class PMDASMVisitor extends ClassVisitor {
public List<String> innerClasses;
public PMDASMVisitor(String outerName) {
super(Opcodes.ASM7);
super(Opcodes.ASM9);
this.outerName = outerName;
}
@ -181,7 +181,7 @@ public class PMDASMVisitor extends ClassVisitor {
private PMDASMVisitor parent;
PMDFieldVisitor(PMDASMVisitor visitor) {
super(Opcodes.ASM5);
super(Opcodes.ASM9);
parent = visitor;
}
@ -196,7 +196,7 @@ public class PMDASMVisitor extends ClassVisitor {
private PMDASMVisitor parent;
PMDAnnotationVisitor(PMDASMVisitor visitor) {
super(Opcodes.ASM5);
super(Opcodes.ASM9);
parent = visitor;
}
@ -228,7 +228,7 @@ public class PMDASMVisitor extends ClassVisitor {
private PMDASMVisitor parent;
PMDSignatureVisitor(PMDASMVisitor visitor) {
super(Opcodes.ASM5);
super(Opcodes.ASM9);
this.parent = visitor;
}
@ -292,7 +292,7 @@ public class PMDASMVisitor extends ClassVisitor {
private PMDASMVisitor parent;
PMDMethodVisitor(PMDASMVisitor visitor) {
super(Opcodes.ASM5);
super(Opcodes.ASM9);
parent = visitor;
}

View File

@ -26,8 +26,8 @@ public class LanguageVersionDiscovererTest {
File javaFile = new File("/path/to/MyClass.java");
LanguageVersion languageVersion = discoverer.getDefaultLanguageVersionForFile(javaFile);
assertEquals("LanguageVersion must be Java 14 !",
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14"), languageVersion);
assertEquals("LanguageVersion must be Java 15 !",
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("15"), languageVersion);
}
/**
@ -48,7 +48,7 @@ public class LanguageVersionDiscovererTest {
public void testLanguageVersionDiscoverer() {
PMDConfiguration configuration = new PMDConfiguration();
LanguageVersionDiscoverer languageVersionDiscoverer = configuration.getLanguageVersionDiscoverer();
assertEquals("Default Java version", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14"),
assertEquals("Default Java version", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("15"),
languageVersionDiscoverer
.getDefaultLanguageVersion(LanguageRegistry.getLanguage(JavaLanguageModule.NAME)));
configuration

View File

@ -52,6 +52,10 @@ public class LanguageVersionTest extends AbstractLanguageVersionTest {
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14"), },
{ JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "14-preview",
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("14-preview"), },
{ JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "15",
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("15"), },
{ JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "15-preview",
LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("15-preview"), },
// this one won't be found: case sensitive!
{ "JAVA", "JAVA", "1.7", null, },

View File

@ -1,52 +0,0 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
@Ignore("those tests depend on type resolution")
public class Java13Test {
private final JavaParsingHelper java12 =
JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("12")
.withResourceContext(getClass(), "jdkversiontests/java13/");
private final JavaParsingHelper java13p = java12.withDefaultVersion("13-preview");
@Test
public void testTextBlocks() {
ASTCompilationUnit compilationUnit = java13p.parseResource("TextBlocks.java");
List<ASTStringLiteral> literals = compilationUnit.findDescendantsOfType(ASTStringLiteral.class);
Assert.assertEquals(10, literals.size());
for (int i = 0; i < 8; i++) {
ASTStringLiteral literal = literals.get(i);
Assert.assertTrue(literal.isTextBlock());
}
Assert.assertEquals("\"\"\"\n"
+ " <html>\n"
+ " <body>\n"
+ " <p>Hello, world</p>\n"
+ " </body>\n"
+ " </html>\n"
+ " \"\"\"",
literals.get(0).getImage());
Assert.assertFalse(literals.get(8).isTextBlock());
Assert.assertTrue(literals.get(9).isTextBlock());
}
@Test(expected = ParseException.class)
public void testTextBlocksBeforeJava13() {
java12.parseResource("TextBlocks.java");
}
}

View File

@ -29,15 +29,12 @@ public class Java14Test {
private final JavaParsingHelper java14p = java14.withDefaultVersion("14-preview");
private final JavaParsingHelper java13 = java14.withDefaultVersion("13");
private final JavaParsingHelper java13p = java14.withDefaultVersion("13-preview");
/**
* Tests switch expressions with yield.
* The switch expressions have no changed between java 13-preview and 14, so behave exactly the same.
*/
@Test
public void switchExpressions() {
parseAndCheckSwitchExpression(java13p);
parseAndCheckSwitchExpression(java14);
parseAndCheckSwitchExpression(java14p);
}
@ -87,11 +84,6 @@ public class Java14Test {
@Test
public void checkYieldConditionalBehaviour() {
checkYieldStatements(java13p);
}
@Test
public void checkYieldConditionalBehaviourJ14() {
checkYieldStatements(java14);
}
@ -142,7 +134,6 @@ public class Java14Test {
@Test
public void multipleCaseLabels() {
multipleCaseLabels(java13p);
multipleCaseLabels(java14);
multipleCaseLabels(java14p);
}
@ -158,7 +149,6 @@ public class Java14Test {
@Test
public void switchRules() {
switchRules(java13p);
switchRules(java14);
switchRules(java14p);
}
@ -186,7 +176,6 @@ public class Java14Test {
@Test
public void simpleSwitchExpressions() {
simpleSwitchExpressions(java13p);
simpleSwitchExpressions(java14);
simpleSwitchExpressions(java14p);
}

View File

@ -0,0 +1,134 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper;
import net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest;
import net.sourceforge.pmd.lang.ast.test.RelevantAttributePrinter;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
public class Java15PreviewTreeDumpTest extends BaseTreeDumpTest {
private final JavaParsingHelper java15p =
JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("15-preview")
.withResourceContext(Java15PreviewTreeDumpTest.class, "jdkversiontests/java15p/");
private final JavaParsingHelper java15 = java15p.withDefaultVersion("15");
public Java15PreviewTreeDumpTest() {
super(new RelevantAttributePrinter(), ".java");
}
@Override
public BaseParsingHelper<?, ?> getParser() {
return java15p;
}
@Test
public void patternMatchingInstanceof() {
doTest("PatternMatchingInstanceof");
// extended tests for type resolution etc.
ASTCompilationUnit compilationUnit = java15p.parseResource("PatternMatchingInstanceof.java");
List<ASTInstanceOfExpression> instanceOfExpressions = compilationUnit.findDescendantsOfType(ASTInstanceOfExpression.class);
for (ASTInstanceOfExpression expr : instanceOfExpressions) {
ASTVariableDeclaratorId variable = expr.getChild(1).getFirstChildOfType(ASTVariableDeclaratorId.class);
Assert.assertEquals(String.class, variable.getType());
// Note: these variables are not part of the symbol table
// See ScopeAndDeclarationFinder#visit(ASTVariableDeclaratorId, Object)
Assert.assertNull(variable.getNameDeclaration());
}
}
@Test(expected = ParseException.class)
public void patternMatchingInstanceofBeforeJava15PreviewShouldFail() {
java15.parseResource("PatternMatchingInstanceof.java");
}
@Test
public void recordPoint() {
doTest("Point");
// extended tests for type resolution etc.
ASTCompilationUnit compilationUnit = java15p.parseResource("Point.java");
ASTRecordDeclaration recordDecl = compilationUnit.getFirstDescendantOfType(ASTRecordDeclaration.class);
List<ASTRecordComponent> components = recordDecl.getFirstChildOfType(ASTRecordComponentList.class)
.findChildrenOfType(ASTRecordComponent.class);
Assert.assertNull(components.get(0).getVarId().getNameDeclaration().getAccessNodeParent());
Assert.assertEquals(Integer.TYPE, components.get(0).getVarId().getNameDeclaration().getType());
Assert.assertEquals("int", components.get(0).getVarId().getNameDeclaration().getTypeImage());
}
@Test(expected = ParseException.class)
public void recordPointBeforeJava15PreviewShouldFail() {
java15.parseResource("Point.java");
}
@Test(expected = ParseException.class)
public void recordCtorWithThrowsShouldFail() {
java15p.parse(" record R {"
+ " R throws IOException {}"
+ " }");
}
@Test(expected = ParseException.class)
public void recordMustNotExtend() {
java15p.parse("record RecordEx(int x) extends Number { }");
}
@Test
public void innerRecords() {
doTest("Records");
}
@Test(expected = ParseException.class)
public void recordIsARestrictedIdentifier() {
java15p.parse("public class record {}");
}
@Test
public void localRecords() {
doTest("LocalRecords");
}
@Test(expected = ParseException.class)
public void sealedClassBeforeJava15Preview() {
java15.parseResource("geometry/Shape.java");
}
@Test
public void sealedClass() {
doTest("geometry/Shape");
}
@Test
public void nonSealedClass() {
doTest("geometry/Square");
}
@Test(expected = ParseException.class)
public void sealedInterfaceBeforeJava15Preview() {
java15.parseResource("expression/Expr.java");
}
@Test
public void sealedInterface() {
doTest("expression/Expr");
}
@Test
public void localInterfaceAndEnums() {
doTest("LocalInterfacesAndEnums");
}
@Test(expected = ParseException.class)
public void localInterfacesAndEnumsBeforeJava15PreviewShouldFail() {
java15.parseResource("LocalInterfacesAndEnums.java");
}
}

View File

@ -0,0 +1,52 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast;
import org.junit.Test;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.ast.test.BaseParsingHelper;
import net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest;
import net.sourceforge.pmd.lang.ast.test.RelevantAttributePrinter;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
public class Java15TreeDumpTest extends BaseTreeDumpTest {
private final JavaParsingHelper java15 =
JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("15")
.withResourceContext(Java15TreeDumpTest.class, "jdkversiontests/java15/");
private final JavaParsingHelper java15p = java15.withDefaultVersion("15-preview");
private final JavaParsingHelper java14 = java15.withDefaultVersion("14");
public Java15TreeDumpTest() {
super(new RelevantAttributePrinter(), ".java");
}
@Override
public BaseParsingHelper<?, ?> getParser() {
return java15;
}
@Test
public void textBlocks() {
doTest("TextBlocks");
java15p.parseResource("TextBlocks.java"); // make sure we can parse it with preview as well
}
@Test(expected = net.sourceforge.pmd.lang.ast.ParseException.class)
public void textBlocksBeforeJava15ShouldFail() {
java14.parseResource("TextBlocks.java");
}
@Test(expected = ParseException.class)
public void stringEscapeSequenceShouldFail() {
java14.parse("class Foo { String s =\"a\\sb\"; }");
}
@Test
public void sealedAndNonSealedIdentifiers() {
doTest("NonSealedIdentifier");
java15p.parseResource("NonSealedIdentifier.java"); // make sure we can parse it with preview as well
}
}

View File

@ -7,22 +7,24 @@ package net.sourceforge.pmd.lang.java.ast
import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.shouldBe
import net.sourceforge.pmd.lang.ast.test.shouldBe
import net.sourceforge.pmd.lang.java.ast.JavaVersion
import net.sourceforge.pmd.lang.java.ast.JavaVersion.J14__PREVIEW
import net.sourceforge.pmd.lang.java.ast.JavaVersion.J15__PREVIEW
import java.io.IOException
class ASTPatternTest : ParserTestSpec({
parserTest("Test patterns only available on JDK 14 (preview)", javaVersions = !J14__PREVIEW) {
parserTest("Test patterns only available on JDK 14+15 (preview)", javaVersions = JavaVersion.except(J14__PREVIEW, J15__PREVIEW)) {
inContext(ExpressionParsingCtx) {
"obj instanceof Class c" should throwParseException {
it.message.shouldContain("Type test patterns in instanceof is a preview feature of JDK 14, you should select your language version accordingly")
it.message.shouldContain("Type test patterns in instanceof is a preview feature of JDK")
}
}
}
parserTest("Test simple patterns", javaVersion = J14__PREVIEW) {
parserTest("Test simple patterns", javaVersions = listOf(J14__PREVIEW, J15__PREVIEW)) {
importedTypes += IOException::class.java
inContext(ExpressionParsingCtx) {

View File

@ -0,0 +1,44 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast
import net.sourceforge.pmd.lang.ast.test.shouldBe
class Java15KotlinTest: ParserTestSpec( {
// Note: More tests are in ASTLiteralTest.
parserTest("textBlocks", javaVersions = JavaVersion.J15..JavaVersion.Latest) {
("\"\"\"\n" +
" <html> \n" +
" <body>\n" +
" <p>Hello, world</p> \n" +
" </body> \n" +
" </html> \n" +
" \"\"\"") should matchExpr<ASTExpression> {
child<ASTPrimaryExpression> {
child<ASTPrimaryPrefix> {
child<ASTLiteral> {
it::isTextBlock shouldBe true
it::getEscapedStringLiteral shouldBe
"\"\"\"\n" +
" <html> \n" +
" <body>\n" +
" <p>Hello, world</p> \n" +
" </body> \n" +
" </html> \n" +
" \"\"\""
it::getTextBlockContent shouldBe
"<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>\n"
}
}
}
}
}
})

View File

@ -28,8 +28,9 @@ import java.beans.PropertyDescriptor
enum class JavaVersion : Comparable<JavaVersion> {
J1_3, J1_4, J1_5, J1_6, J1_7, J1_8, J9, J10, J11,
J12,
J13, J13__PREVIEW,
J14, J14__PREVIEW;
J13,
J14, J14__PREVIEW,
J15, J15__PREVIEW;
/** Name suitable for use with e.g. [JavaParsingHelper.parse] */
val pmdName: String = name.removePrefix("J").replaceFirst("__", "-").replace('_', '.').toLowerCase()
@ -55,9 +56,10 @@ enum class JavaVersion : Comparable<JavaVersion> {
val Latest = values().last()
val Earliest = values().first()
fun except(vararg versions: JavaVersion) = values().toList() - versions
fun except(versions: List<JavaVersion>) = values().toList() - versions
fun except(v1: JavaVersion, vararg versions: JavaVersion) =
values().toList() - v1 - versions
fun except(versions: List<JavaVersion>) = values().toList() - versions
}
}

View File

@ -1,55 +0,0 @@
/**
* @see <a href="http://openjdk.java.net/jeps/355">JEP 355: Text Blocks (Preview)</a>
*/
public class TextBlocks {
public static void main(String[] args) {
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
System.out.println(html);
String season = """
winter"""; // the six characters w i n t e r
String period = """
winter
"""; // the seven characters w i n t e r LF
String greeting =
"""
Hi, "Bob"
"""; // the ten characters H i , SP " B o b " LF
String salutation =
"""
Hi,
"Bob"
"""; // the eleven characters H i , LF SP " B o b " LF
String empty = """
"""; // the empty string (zero length)
String quote = """
"
"""; // the two characters " LF
String backslash = """
\\
"""; // the two characters \ LF
String normalStringLiteral = "test";
String code =
"""
String text = \"""
A text block inside a text block
\""";
""";
}
}

View File

@ -0,0 +1,15 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
public class NonSealedIdentifier {
public static void main(String[] args) {
int result = 0;
int non = 1;
// sealed is a valid identifier name in both Java15 and Java15 Preview
int sealed = 2;
// non-sealed is a valid subtraction expression in both Java15 and Java15 Preview
result = non-sealed;
System.out.println(result);
}
}

View File

@ -0,0 +1,76 @@
+- CompilationUnit[@PackageName = "", @declarationsAreInDefaultPackage = true]
+- TypeDeclaration[]
+- ClassOrInterfaceDeclaration[@Abstract = false, @BinaryName = "NonSealedIdentifier", @Default = false, @Final = false, @Image = "NonSealedIdentifier", @Interface = false, @Local = false, @Modifiers = 1, @Native = false, @Nested = false, @NonSealed = false, @PackagePrivate = false, @Private = false, @Protected = false, @Public = true, @Sealed = false, @SimpleName = "NonSealedIdentifier", @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeKind = TypeKind.CLASS, @Volatile = false]
+- ClassOrInterfaceBody[@AnonymousInnerClass = false, @EnumChild = false]
+- ClassOrInterfaceBodyDeclaration[@AnonymousInnerClass = false, @EnumChild = false, @Kind = DeclarationKind.METHOD]
+- MethodDeclaration[@Abstract = false, @Arity = 1, @Default = false, @Final = false, @InterfaceMember = false, @Kind = MethodLikeKind.METHOD, @MethodName = "main", @Modifiers = 17, @Name = "main", @Native = false, @PackagePrivate = false, @Private = false, @Protected = false, @Public = true, @Static = true, @Strictfp = false, @Synchronized = false, @SyntacticallyAbstract = false, @SyntacticallyPublic = true, @Transient = false, @Void = true, @Volatile = false]
+- ResultType[@Void = true, @returnsArray = false]
+- MethodDeclarator[@Image = "main", @ParameterCount = 1]
| +- FormalParameters[@ParameterCount = 1, @Size = 1]
| +- FormalParameter[@Abstract = false, @Array = true, @ArrayDepth = 1, @Default = false, @ExplicitReceiverParameter = false, @Final = false, @Modifiers = 0, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeInferred = false, @Varargs = false, @Volatile = false]
| +- Type[@Array = true, @ArrayDepth = 1, @ArrayType = true, @TypeImage = "String"]
| | +- ReferenceType[@Array = true, @ArrayDepth = 1]
| | +- ClassOrInterfaceType[@AnonymousClass = false, @Array = true, @ArrayDepth = 1, @Image = "String", @ReferenceToClassSameCompilationUnit = false]
| +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = true, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = true, @Image = "args", @LambdaParameter = false, @LocalVariable = false, @Name = "args", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "args"]
+- Block[@containsComment = false]
+- BlockStatement[@Allocation = false]
| +- LocalVariableDeclaration[@Abstract = false, @Array = false, @ArrayDepth = 0, @Default = false, @Final = false, @Modifiers = 0, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeInferred = false, @VariableName = "result", @Volatile = false]
| +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "int"]
| | +- PrimitiveType[@Array = false, @ArrayDepth = 0, @Boolean = false, @Image = "int"]
| +- VariableDeclarator[@Initializer = true, @Name = "result"]
| +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "result", @LambdaParameter = false, @LocalVariable = true, @Name = "result", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "result"]
| +- VariableInitializer[]
| +- Expression[@StandAlonePrimitive = true]
| +- PrimaryExpression[]
| +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| +- Literal[@CharLiteral = false, @DoubleLiteral = false, @EscapedStringLiteral = "0", @FloatLiteral = false, @Image = "0", @IntLiteral = true, @LongLiteral = false, @SingleCharacterStringLiteral = false, @StringLiteral = false, @TextBlock = false, @TextBlockContent = "0", @ValueAsDouble = NaN, @ValueAsFloat = NaN, @ValueAsInt = 0, @ValueAsLong = 0]
+- BlockStatement[@Allocation = false]
| +- LocalVariableDeclaration[@Abstract = false, @Array = false, @ArrayDepth = 0, @Default = false, @Final = false, @Modifiers = 0, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeInferred = false, @VariableName = "non", @Volatile = false]
| +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "int"]
| | +- PrimitiveType[@Array = false, @ArrayDepth = 0, @Boolean = false, @Image = "int"]
| +- VariableDeclarator[@Initializer = true, @Name = "non"]
| +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "non", @LambdaParameter = false, @LocalVariable = true, @Name = "non", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "non"]
| +- VariableInitializer[]
| +- Expression[@StandAlonePrimitive = true]
| +- PrimaryExpression[]
| +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| +- Literal[@CharLiteral = false, @DoubleLiteral = false, @EscapedStringLiteral = "1", @FloatLiteral = false, @Image = "1", @IntLiteral = true, @LongLiteral = false, @SingleCharacterStringLiteral = false, @StringLiteral = false, @TextBlock = false, @TextBlockContent = "1", @ValueAsDouble = NaN, @ValueAsFloat = NaN, @ValueAsInt = 1, @ValueAsLong = 1]
+- BlockStatement[@Allocation = false]
| +- LocalVariableDeclaration[@Abstract = false, @Array = false, @ArrayDepth = 0, @Default = false, @Final = false, @Modifiers = 0, @Native = false, @PackagePrivate = true, @Private = false, @Protected = false, @Public = false, @Static = false, @Strictfp = false, @Synchronized = false, @Transient = false, @TypeInferred = false, @VariableName = "sealed", @Volatile = false]
| +- Type[@Array = false, @ArrayDepth = 0, @ArrayType = false, @TypeImage = "int"]
| | +- PrimitiveType[@Array = false, @ArrayDepth = 0, @Boolean = false, @Image = "int"]
| +- VariableDeclarator[@Initializer = true, @Name = "sealed"]
| +- VariableDeclaratorId[@Array = false, @ArrayDepth = 0, @ArrayType = false, @ExceptionBlockParameter = false, @ExplicitReceiverParameter = false, @Field = false, @Final = false, @FormalParameter = false, @Image = "sealed", @LambdaParameter = false, @LocalVariable = true, @Name = "sealed", @PatternBinding = false, @ResourceDeclaration = false, @TypeInferred = false, @VariableName = "sealed"]
| +- VariableInitializer[]
| +- Expression[@StandAlonePrimitive = true]
| +- PrimaryExpression[]
| +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| +- Literal[@CharLiteral = false, @DoubleLiteral = false, @EscapedStringLiteral = "2", @FloatLiteral = false, @Image = "2", @IntLiteral = true, @LongLiteral = false, @SingleCharacterStringLiteral = false, @StringLiteral = false, @TextBlock = false, @TextBlockContent = "2", @ValueAsDouble = NaN, @ValueAsFloat = NaN, @ValueAsInt = 2, @ValueAsLong = 2]
+- BlockStatement[@Allocation = false]
| +- Statement[]
| +- StatementExpression[]
| +- PrimaryExpression[]
| | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| | +- Name[@Image = "result"]
| +- AssignmentOperator[@Compound = false, @Image = "="]
| +- Expression[@StandAlonePrimitive = false]
| +- AdditiveExpression[@Image = "-", @Operator = "-"]
| +- PrimaryExpression[]
| | +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| | +- Name[@Image = "non"]
| +- PrimaryExpression[]
| +- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| +- Name[@Image = "sealed"]
+- BlockStatement[@Allocation = false]
+- Statement[]
+- StatementExpression[]
+- PrimaryExpression[]
+- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
| +- Name[@Image = "System.out.println"]
+- PrimarySuffix[@ArgumentCount = 1, @Arguments = true, @ArrayDereference = false]
+- Arguments[@ArgumentCount = 1, @Size = 1]
+- ArgumentList[@Size = 1]
+- Expression[@StandAlonePrimitive = false]
+- PrimaryExpression[]
+- PrimaryPrefix[@SuperModifier = false, @ThisModifier = false]
+- Name[@Image = "result"]

View File

@ -0,0 +1,119 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* Text Blocks are a permanent language feature with JDK 15.
*
* @see <a href="https://openjdk.java.net/jeps/378>JEP 378: Text Blocks</a>
*/
public class TextBlocks {
public static void main(String[] args) throws Exception {
// note: there is trailing whitespace!!
String html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
System.out.println(html);
String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";
System.out.println(query);
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
function hello() {
print('"Hello, world"');
}
hello();
""");
// Escape sequences
String htmlWithEscapes = """
<html>\r
<body>\r
<p>Hello, world</p>\r
</body>\r
</html>\r
""";
System.out.println(htmlWithEscapes);
String season = """
winter"""; // the six characters w i n t e r
String period = """
winter
"""; // the seven characters w i n t e r LF
String greeting =
"""
Hi, "Bob"
"""; // the ten characters H i , SP " B o b " LF
String salutation =
"""
Hi,
"Bob"
"""; // the eleven characters H i , LF SP " B o b " LF
String empty = """
"""; // the empty string (zero length)
String quote = """
"
"""; // the two characters " LF
String backslash = """
\\
"""; // the two characters \ LF
String normalStringLiteral = "test";
String code =
"""
String text = \"""
A text block inside a text block
\""";
""";
// new escape sequences
String text = """
Lorem ipsum dolor sit amet, consectetur adipiscing \
elit, sed do eiusmod tempor incididunt ut labore \
et dolore magna aliqua.\
""";
System.out.println(text);
String colors = """
red \s
green\s
blue \s
""";
System.out.println(colors);
// empty new line as first content
String emptyLine = """
test
""";
System.out.println(emptyLine.replaceAll("\n", "<LF>"));
// backslash escapes
String bs = """
\\test
""";
System.out.println(bs.replaceAll("\n", "<LF>"));
}
}

Some files were not shown because too many files have changed in this diff Show More