Merge branch 'master' into 7.0.x

This commit is contained in:
Clément Fournier
2020-08-22 19:48:49 +02:00
61 changed files with 2536 additions and 289 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
@ -226,11 +234,13 @@ options {
PARSER_BEGIN(JavaParserImpl)
package net.sourceforge.pmd.lang.java.ast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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;
@ -282,9 +292,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).image.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")) {
Token nonToken = getToken(1);
Token minusToken = getToken(2);
Token sealedToken = getToken(3);
return nonToken.endColumn + 1 == minusToken.beginColumn
&& minusToken.endColumn + 1 == sealedToken.beginColumn;
}
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.image.equals("enum")
|| isRecordTypeSupported() && next.kind == IDENTIFIER && next.image.equals("record");
}
/**
@ -828,6 +894,8 @@ int Modifiers() #void:
| "volatile" { modifiers |= AccessNode.VOLATILE; }
| "strictfp" { modifiers |= AccessNode.STRICTFP; }
| "default" { modifiers |= AccessNode.DEFAULT; }
| LOOKAHEAD({isKeyword("sealed")}) <IDENTIFIER> { modifiers |= AccessNode.SEALED; checkForSealedClassUsage(); }
| LOOKAHEAD({isNonSealedModifier()}) <IDENTIFIER> <MINUS> <IDENTIFIER> { modifiers |= AccessNode.NON_SEALED; checkForSealedClassUsage(); }
| Annotation()
)
)*
@ -867,6 +935,7 @@ void ClassOrInterfaceDeclaration(int modifiers):
[ TypeParameters() ]
[ ExtendsList() ]
[ ImplementsList() ]
[ LOOKAHEAD({isKeyword("permits")}) PermittedSubclasses() ]
ClassOrInterfaceBody()
}
@ -886,6 +955,21 @@ void ImplementsList():
( "," (TypeAnnotation())* ClassOrInterfaceType() )*
}
void PermittedSubclasses() #PermitsList:
{
Token t;
checkForSealedClassUsage();
}
{
t = <IDENTIFIER> {
if (!"permits".equals(t.image)) {
throw new ParseException("ERROR: expecting permits");
}
}
(TypeAnnotation())* ClassOrInterfaceType()
( "," (TypeAnnotation())* ClassOrInterfaceType() )*
}
void EnumDeclaration(int modifiers):
{
@ -1623,36 +1707,64 @@ void Block() :
}
void BlockStatement():
{}
{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<Node> annotationsAndChildren = new ArrayList<Node>();
while (jjtree.peekNode() instanceof ASTAnnotation) {
annotationsAndChildren.add(jjtree.popNode());
}
}
LocalVariableDeclaration()
{
ASTLocalVariableDeclaration localVarDecl = (ASTLocalVariableDeclaration) jjtree.peekNode();
if ((mods & AccessNode.FINAL) == AccessNode.FINAL) {
localVarDecl.setFinal(true);
}
if (!annotationsAndChildren.isEmpty()) {
Collections.reverse(annotationsAndChildren);
for (int i = 0; i < localVarDecl.getNumChildren(); i++) {
annotationsAndChildren.add(localVarDecl.getChild(i));
}
for (int i = 0; i < annotationsAndChildren.size(); i++) {
Node child = annotationsAndChildren.get(i);
child.jjtSetParent(localVarDecl);
localVarDecl.jjtAddChild(child, i);
}
}
}
";"
)
| LOOKAHEAD({classModifierLookahead() || localTypeDeclLookahead()})
mods=Modifiers()
LocalTypeDecl(mods)
| LOOKAHEAD(Type() <IDENTIFIER>)
LocalVariableDeclaration() ";"
|
Statement()
|
// we don't need to lookahead further here
// the ambiguity between start of local class and local variable decl
// is already handled in the lookahead guarding LocalVariableDeclaration above.
LocalClassDecl()
}
void LocalClassDecl() #void:
{int mods = 0;}
void LocalTypeDecl(int mods) #void:
{}
{
// 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
mods=Modifiers() ClassOrInterfaceDeclaration(mods)
(
LOOKAHEAD(<CLASS>) ClassOrInterfaceDeclaration(mods)
| LOOKAHEAD(<INTERFACE>) ClassOrInterfaceDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
| AnnotationTypeDeclaration(mods) { checkForLocalInterfaceOrEnumType(); }
)
}
/*

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

@ -28,6 +28,10 @@ public class ASTAnnotationTypeDeclaration extends AbstractAnyTypeDeclaration {
return TypeKind.ANNOTATION;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
@Override
public NodeStream<ASTAnyTypeBodyDeclaration> getDeclarations() {

View File

@ -107,6 +107,10 @@ public interface ASTAnyTypeDeclaration extends TypeNode, JavaQualifiableNode, Ac
return getFirstChildOfType(ASTRecordComponentList.class);
}
/**
* Returns true if this type is declared locally, e.g. in the context of a method block.
*/
boolean isLocal();
/**
* The kind of type this node declares.

View File

@ -7,6 +7,7 @@ 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.annotation.InternalApi;
import net.sourceforge.pmd.internal.util.IteratorUtil;
import net.sourceforge.pmd.lang.ast.Node;
@ -100,4 +101,23 @@ public class ASTClassOrInterfaceDeclaration extends AbstractAnyTypeDeclaration {
return it == null ? Collections.emptyList() : IteratorUtil.toList(it.iterator());
}
@Experimental
public List<ASTClassOrInterfaceType> getPermittedSubclasses() {
ASTPermitsList permitted = getFirstChildOfType(ASTPermitsList.class);
return permitted == null
? Collections.<ASTClassOrInterfaceType>emptyList()
: CollectionUtil.toList(permitted.iterator());
}
@Experimental
public boolean isSealed() {
int modifiers = getModifiers();
return (modifiers & AccessNode.SEALED) == AccessNode.SEALED;
}
@Experimental
public boolean isNonSealed() {
int modifiers = getModifiers();
return (modifiers & AccessNode.NON_SEALED) == AccessNode.NON_SEALED;
}
}

View File

@ -28,6 +28,10 @@ public class ASTEnumDeclaration extends AbstractAnyTypeDeclaration {
return TypeKind.ENUM;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
@Override
public NodeStream<ASTAnyTypeBodyDeclaration> getDeclarations() {

View File

@ -12,7 +12,6 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.annotation.InternalApi;
public class ASTLiteral extends AbstractJavaTypeNode {
@ -261,10 +260,7 @@ public class ASTLiteral extends AbstractJavaTypeNode {
* Returns the content of the text block after normalizing line endings to LF,
* removing incidental white space surrounding the text block and interpreting
* escape sequences.
*
* <p>Note: This is a Java 14 Preview Feature.
*/
@Experimental
public String getTextBlockContent() {
if (!isTextBlock()) {
return getImage();

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,46 @@
/**
* 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);
}
ASTPermitsList(JavaParser p, int id) {
super(p, id);
}
@Override
public Object jjtAccept(JavaParserVisitor visitor, Object data) {
return visitor.visit(this, data);
}
@Override
public Iterator<ASTClassOrInterfaceType> iterator() {
return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class);
}
}

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

@ -8,7 +8,7 @@ package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* 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).
*
* <pre class="grammar">
*

View File

@ -10,7 +10,7 @@ import java.util.Iterator;
import net.sourceforge.pmd.annotation.Experimental;
/**
* 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

@ -8,7 +8,7 @@ package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.annotation.Experimental;
/**
* This defines a compact constructor for a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 preview feature).
* This defines a compact constructor for a {@linkplain ASTRecordDeclaration RecordDeclaration} (JDK 14 and JDK 15 preview feature).
*
* <pre class="grammar">
*

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">
@ -26,7 +26,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 {
@ -54,9 +54,20 @@ public final class ASTRecordDeclaration extends AbstractAnyTypeDeclaration {
return isNested();
}
@NonNull
@Override
public ASTRecordComponentList getRecordComponents() {
public boolean isFinal() {
// A record is implicitly final
return true;
}
@Override
public boolean isLocal() {
return getParent() instanceof ASTBlockStatement;
}
@Override
public @NonNull ASTRecordComponentList getRecordComponents() {
return getFirstChildOfType(ASTRecordComponentList.class);
}
}

View File

@ -197,6 +197,11 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim
return true;
}
if (getParent() instanceof ASTRecordComponent) {
// the field corresponding to this record component is declared final
return true;
}
if (getParent() instanceof ASTFormalParameter) {
// This accounts for exception parameters too for now
return ((ASTFormalParameter) getParent()).isFinal();

View File

@ -23,6 +23,8 @@ public interface AccessNode extends JavaNode {
int VOLATILE = 0x0200;
int STRICTFP = 0x1000;
int DEFAULT = 0x2000;
int SEALED = 0x4000;
int NON_SEALED = 0x8000;
int getModifiers();

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,91 +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.Test;
import net.sourceforge.pmd.lang.ast.ParseException;
import net.sourceforge.pmd.lang.java.JavaParsingHelper;
public class Java13Test {
private final JavaParsingHelper java12 =
JavaParsingHelper.WITH_PROCESSING.withDefaultVersion("12")
.withResourceContext(Java13Test.class, "jdkversiontests/java13/");
private final JavaParsingHelper java13p = java12.withDefaultVersion("13-preview");
@Test
public void testSwitchExpressions() {
ASTCompilationUnit compilationUnit = java13p.parseResource("SwitchExpressions.java");
ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class);
Assert.assertEquals(4, switchExpression.getNumChildren());
Assert.assertTrue(switchExpression.getChild(0) instanceof ASTExpression);
Assert.assertEquals(3, switchExpression.findChildrenOfType(ASTSwitchLabeledRule.class).size());
Assert.assertEquals(1, switchExpression.findChildrenOfType(ASTSwitchLabeledBlock.class).size());
Assert.assertEquals(1, switchExpression.findDescendantsOfType(ASTYieldStatement.class).size());
ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class);
Assert.assertEquals(Integer.TYPE, yieldStatement.getType());
}
@Test
public void testSwitchExpressionsYield() {
ASTCompilationUnit compilationUnit = java13p.parseResource("SwitchExpressionsYield.java");
ASTSwitchExpression switchExpression = compilationUnit.getFirstDescendantOfType(ASTSwitchExpression.class);
Assert.assertEquals(11, switchExpression.getNumChildren());
Assert.assertTrue(switchExpression.getChild(0) instanceof ASTExpression);
Assert.assertEquals(5, switchExpression.findChildrenOfType(ASTSwitchLabel.class).size());
ASTYieldStatement yieldStatement = switchExpression.getFirstDescendantOfType(ASTYieldStatement.class);
Assert.assertEquals("SwitchExpressionsBreak.SIX", yieldStatement.getImage());
Assert.assertTrue(yieldStatement.getChild(0) instanceof ASTExpression);
ASTLocalVariableDeclaration localVar = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class)
.get(1);
ASTVariableDeclarator localVarDecl = localVar.getFirstChildOfType(ASTVariableDeclarator.class);
Assert.assertEquals(Integer.TYPE, localVarDecl.getType());
Assert.assertEquals(Integer.TYPE, switchExpression.getType());
}
@Test(expected = ParseException.class)
public void testSwitchExpressionsBeforeJava13() {
java12.parseResource("SwitchExpressions.java");
}
@Test
public void testTextBlocks() {
ASTCompilationUnit compilationUnit = java13p.parseResource("TextBlocks.java");
List<ASTLiteral> literals = compilationUnit.findDescendantsOfType(ASTLiteral.class);
Assert.assertEquals(10, literals.size());
for (int i = 0; i < 8; i++) {
ASTLiteral 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

@ -27,15 +27,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);
}
@ -85,11 +82,6 @@ public class Java14Test {
@Test
public void checkYieldConditionalBehaviour() {
checkYieldStatements(java13p);
}
@Test
public void checkYieldConditionalBehaviourJ14() {
checkYieldStatements(java14);
}
@ -140,7 +132,6 @@ public class Java14Test {
@Test
public void multipleCaseLabels() {
multipleCaseLabels(java13p);
multipleCaseLabels(java14);
multipleCaseLabels(java14p);
}
@ -156,7 +147,6 @@ public class Java14Test {
@Test
public void switchRules() {
switchRules(java13p);
switchRules(java14);
switchRules(java14p);
}
@ -184,7 +174,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

@ -6,22 +6,24 @@ package net.sourceforge.pmd.lang.java.ast
import io.kotest.matchers.string.shouldContain
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.values().asList().minus(J14__PREVIEW).minus(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("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview")
}
}
}
parserTest("Test simple patterns", javaVersion = J14__PREVIEW) {
parserTest("Test simple patterns", javaVersions = listOf(J14__PREVIEW, J15__PREVIEW)) {
importedTypes += IOException::class.java

View File

@ -0,0 +1,45 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.ast
import io.kotest.matchers.shouldBe
import net.sourceforge.pmd.lang.ast.test.matchNode
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.escapedStringLiteral shouldBe
"\"\"\"\n" +
" <html> \n" +
" <body>\n" +
" <p>Hello, world</p> \n" +
" </body> \n" +
" </html> \n" +
" \"\"\""
it.textBlockContent shouldBe
"<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>\n"
}
}
}
}
}
})

View File

@ -23,8 +23,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()

View File

@ -1,41 +0,0 @@
/**
*
*
* @see <a href="http://openjdk.java.net/jeps/354">JEP 354: Switch Expressions (Preview)</a>
*/
public class SwitchExpressions {
private enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
public static void main(String[] args) {
Day day = Day.FRIDAY;
int j = switch (day) {
case MONDAY -> 0;
case TUESDAY -> 1;
default -> {
int k = day.toString().length();
int result = f(k);
yield result;
}
};
System.out.printf("j = %d%n", j);
String s = "Bar";
int result = switch (s) {
case "Foo":
yield 1;
case "Bar":
yield 2;
default:
System.out.println("Neither Foo nor Bar, hmmm...");
yield 0;
};
System.out.printf("result = %d%n", result);
}
private static int f(int k) {
return k+1;
}
}

View File

@ -1,36 +0,0 @@
/**
*
* @see <a href="https://openjdk.java.net/jeps/325">JEP 325: Switch Expressions (Preview)</a>
*/
public class SwitchExpressionsYield {
private static final int MONDAY = 1;
private static final int TUESDAY = 2;
private static final int WEDNESDAY = 3;
private static final int THURSDAY = 4;
private static final int FRIDAY = 5;
private static final int SATURDAY = 6;
private static final int SUNDAY = 7;
private static final int SIX = 6;
public static void main(String[] args) {
int day = FRIDAY;
var numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY: yield SwitchExpressionsBreak.SIX;
case TUESDAY : yield 7;
case THURSDAY, SATURDAY : yield 8;
case WEDNESDAY : yield 9;
default : {
int k = day * 2;
int result = f(k);
yield result;
}
};
System.out.printf("NumLetters: %d%n", numLetters);
}
private static int f(int k) {
return k*3;
}
}

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