diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 3a37c03444..e62c27d420 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -257,6 +257,16 @@ class JavaParserImpl { this.preview = preview; } + + 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. @@ -321,11 +331,11 @@ class JavaParserImpl { */ 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; + JavaccToken nonToken = getToken(1); + JavaccToken minusToken = getToken(2); + JavaccToken sealedToken = getToken(3); + return nonToken.getEndColumn() == minusToken.getBeginColumn() + && minusToken.getEndColumn() == sealedToken.getBeginColumn(); } return false; } @@ -894,8 +904,8 @@ int Modifiers() #void: | "volatile" { modifiers |= AccessNode.VOLATILE; } | "strictfp" { modifiers |= AccessNode.STRICTFP; } | "default" { modifiers |= AccessNode.DEFAULT; } - | LOOKAHEAD({isKeyword("sealed")}) { modifiers |= AccessNode.SEALED; checkForSealedClassUsage(); } - | LOOKAHEAD({isNonSealedModifier()}) { modifiers |= AccessNode.NON_SEALED; checkForSealedClassUsage(); } + | LOOKAHEAD({isKeyword("sealed")}) { modifiers |= AccessNode.SEALED; } + | LOOKAHEAD({isNonSealedModifier()}) { modifiers |= AccessNode.NON_SEALED; } | Annotation() ) )* @@ -958,7 +968,6 @@ void ImplementsList(): void PermittedSubclasses() #PermitsList: { Token t; - checkForSealedClassUsage(); } { t = { @@ -1721,9 +1730,9 @@ void BlockStatement(): LOOKAHEAD({localTypeDeclLookahead()}) LocalTypeDecl(mods) | { - List annotationsAndChildren = new ArrayList(); + List annotations = new ArrayList(); while (jjtree.peekNode() instanceof ASTAnnotation) { - annotationsAndChildren.add(jjtree.popNode()); + annotations.add((ASTAnnotation) jjtree.popNode()); } } LocalVariableDeclaration() @@ -1732,15 +1741,10 @@ void BlockStatement(): 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); + if (!annotations.isEmpty()) { + Collections.reverse(annotations); + for (ASTAnnotation a : annotations) { + localVarDecl.insertChild(a, 0); } } } @@ -1760,10 +1764,10 @@ void LocalTypeDecl(int mods) #void: { ( LOOKAHEAD() ClassOrInterfaceDeclaration(mods) - | LOOKAHEAD() ClassOrInterfaceDeclaration(mods) { checkForLocalInterfaceOrEnumType(); } - | LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods) { checkForLocalInterfaceOrEnumType(); } - | LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(mods) { checkForLocalInterfaceOrEnumType(); } - | AnnotationTypeDeclaration(mods) { checkForLocalInterfaceOrEnumType(); } + | LOOKAHEAD() ClassOrInterfaceDeclaration(mods) + | LOOKAHEAD({isKeyword("record")}) RecordDeclaration(mods) + | LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(mods) + | AnnotationTypeDeclaration(mods) ) } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java index 228bc15c61..dfb7af5d8d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java @@ -107,10 +107,6 @@ 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. diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java index 178c46c29c..2a80ef0795 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java @@ -12,6 +12,7 @@ import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.internal.util.IteratorUtil; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.NodeStream; +import net.sourceforge.pmd.util.CollectionUtil; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPermitsList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPermitsList.java index 541a0f812c..890e4293d8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPermitsList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPermitsList.java @@ -29,18 +29,13 @@ public final class ASTPermitsList extends AbstractJavaNode implements Iterable R acceptVisitor(JavaVisitor visitor, P data) { return visitor.visit(this, data); } - @Override public Iterator iterator() { - return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class); + return children(ASTClassOrInterfaceType.class).iterator(); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java index 79681561a4..14890cf7db 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java @@ -50,6 +50,11 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode { 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; @@ -515,6 +520,12 @@ public class LanguageLevelChecker { @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); diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTPatternTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTPatternTest.kt index 803dbf4d02..120fc530fe 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTPatternTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTPatternTest.kt @@ -13,12 +13,12 @@ import java.io.IOException class ASTPatternTest : ParserTestSpec({ - parserTest("Test patterns only available on JDK 14+15 (preview)", javaVersions = JavaVersion.values().asList().minus(J14__PREVIEW).minus(J15__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("Pattern Matching for instanceof is only supported with Java 14 Preview and Java 15 Preview") + it.message.shouldContain("Type test patterns in instanceof is a preview feature of JDK") } } } diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java15KotlinTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java15KotlinTest.kt index ea3a79771e..6bc3facde9 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java15KotlinTest.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java15KotlinTest.kt @@ -4,8 +4,7 @@ package net.sourceforge.pmd.lang.java.ast -import io.kotest.matchers.shouldBe -import net.sourceforge.pmd.lang.ast.test.matchNode +import net.sourceforge.pmd.lang.ast.test.shouldBe class Java15KotlinTest: ParserTestSpec( { @@ -21,16 +20,16 @@ class Java15KotlinTest: ParserTestSpec( { child { child { child { - it.isTextBlock shouldBe true - it.escapedStringLiteral shouldBe + it::isTextBlock shouldBe true + it::getEscapedStringLiteral shouldBe "\"\"\"\n" + - " \n" + - " \n" + - "

Hello, world

\n" + - " \n" + - " \n" + - " \"\"\"" - it.textBlockContent shouldBe + " \n" + + " \n" + + "

Hello, world

\n" + + " \n" + + " \n" + + " \"\"\"" + it::getTextBlockContent shouldBe "\n" + " \n" + "

Hello, world

\n" + diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt index 8483594d66..18824813b0 100644 --- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt @@ -48,6 +48,11 @@ enum class JavaVersion : Comparable { companion object { val Latest = values().last() val Earliest = values().first() + + fun except(v1: JavaVersion, vararg versions: JavaVersion) = + values().toList() - v1 - versions + + } }