diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index 1ba6dd9fd3..01a4e86375 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -537,13 +537,40 @@ public class JavaParser {
* specific restricted keyword.
*
*
Restricted keywords are:
- * var, yield, record, sealed, permits
+ * var, yield, record, sealed, permits, "non" + "-" + "sealed"
*
*
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".
+ *
+ *
"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;
}
/**
@@ -1024,14 +1051,6 @@ TOKEN :
| < GT: ">" >
}
-// Note: New token need to be added at the very end in order to
-// keep (binary) compatibility with the generated token ids
-// see JavaParserConstants
-TOKEN :
-{
- < NON_SEALED: "non-sealed" >
-}
-
/*****************************************
* THE JAVA LANGUAGE GRAMMAR STARTS HERE *
*****************************************/
@@ -1104,7 +1123,7 @@ int Modifiers() #void:
| "strictfp" { modifiers |= AccessNode.STRICTFP; }
| "default" { modifiers |= AccessNode.DEFAULT; checkForBadDefaultImplementationUsage(); }
| LOOKAHEAD({isKeyword("sealed")}) { modifiers |= AccessNode.SEALED; checkForSealedClassUsage(); }
- | "non-sealed" { modifiers |= AccessNode.NON_SEALED; checkForSealedClassUsage(); }
+ | LOOKAHEAD({isNonSealedModifier()}) { modifiers |= AccessNode.NON_SEALED; checkForSealedClassUsage(); }
| Annotation()
)
)*
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15Test.java
index 003c5072cc..867504ebd8 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15Test.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java15Test.java
@@ -71,4 +71,9 @@ public class Java15Test {
java14.parse("class Foo { String s =\"a\\sb\"; }");
}
+ @Test
+ public void sealedAndNonSealedIdentifiers() {
+ java15.parseResource("NonSealedIdentifier.java");
+ java15p.parseResource("NonSealedIdentifier.java");
+ }
}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15/NonSealedIdentifier.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15/NonSealedIdentifier.java
new file mode 100644
index 0000000000..dbeebe9403
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15/NonSealedIdentifier.java
@@ -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);
+ }
+}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java
index dd273baf5a..c359b99419 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java15p/LocalRecords.java
@@ -34,4 +34,12 @@ public class LocalRecords {
void methodWithLocalClass() {
class MyLocalClass {}
}
+
+ void methodWithLocalVarsNamedSealed() {
+ int result = 0;
+ int non = 1;
+ int sealed = 2;
+ result = non-sealed;
+ System.out.println(result);
+ }
}