diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index c9c55694ab..ea55e57d5a 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -912,9 +912,9 @@ void EnumBody(): {} { "{" - [ EnumConstant() ( LOOKAHEAD(2) "," EnumConstant() )* ] - [ "," ] - [ ";" ( ClassOrInterfaceBodyDeclaration() )* ] + [ EnumConstant() ( LOOKAHEAD(2) "," EnumConstant() )* ] + [ "," { jjtThis.setTrailingComma(); } ] + [ ";" { jjtThis.setSeparatorSemi(); } ( ClassOrInterfaceBodyDeclaration() )* ] "}" } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java index a7e41788c8..177b5728be 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java @@ -10,7 +10,7 @@ package net.sourceforge.pmd.lang.java.ast; *
*
* EnumBody ::= "{"
- * [( {@link ASTAnnotation Annotation} )* {@link ASTEnumConstant EnumConstant} ( "," ( {@link ASTAnnotation Annotation} )* {@link ASTEnumConstant EnumConstant} )* ]
+ * [ {@link ASTEnumConstant EnumConstant} ( "," ( {@link ASTEnumConstant EnumConstant} )* ]
* [ "," ]
* [ ";" ( {@link ASTClassOrInterfaceBodyDeclaration ClassOrInterfaceBodyDeclaration} )* ]
* "}"
@@ -21,6 +21,9 @@ package net.sourceforge.pmd.lang.java.ast;
*/
public final class ASTEnumBody extends AbstractJavaNode implements ASTTypeBody {
+ private boolean trailingComma;
+ private boolean separatorSemi;
+
ASTEnumBody(int id) {
super(id);
}
@@ -35,4 +38,43 @@ public final class ASTEnumBody extends AbstractJavaNode implements ASTTypeBody {
public void jjtAccept(SideEffectingVisitor visitor, T data) {
visitor.visit(this, data);
}
+
+ void setTrailingComma() {
+ this.trailingComma = true;
+ }
+
+ void setSeparatorSemi() {
+ this.separatorSemi = true;
+ }
+
+ /**
+ * Returns true if the last enum constant has a trailing comma.
+ * For example:
+ * {@code
+ * enum Foo { A, B, C, }
+ * enum Bar { , }
+ * }
+ */
+ public boolean hasTrailingComma() {
+ return trailingComma;
+ }
+
+ /**
+ * Returns true if the last enum constant has a trailing semi-colon.
+ * This semi is not optional when the enum has other members.
+ * For example:
+ * {@code
+ * enum Foo {
+ * A(2);
+ *
+ * Foo(int i) {...}
+ * }
+ *
+ * enum Bar { A; }
+ * enum Baz { ; }
+ * }
+ */
+ public boolean hasSeparatorSemi() {
+ return separatorSemi;
+ }
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTEnumConstantTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTEnumConstantTest.kt
index 2a7b399333..054ebb103c 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTEnumConstantTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTEnumConstantTest.kt
@@ -55,6 +55,75 @@ class ASTEnumConstantTest : ParserTestSpec({
}
+ parserTest("Corner cases with separators") {
+
+ inContext(TopLevelTypeDeclarationParsingCtx) {
+
+
+ "enum Foo { A, }" should parseAs {
+
+ enumDecl("Foo") {
+ modifiers { }
+ enumBody {
+ it::hasTrailingComma shouldBe true
+ enumConstant("A")
+ }
+ }
+ }
+
+ "enum Foo { , }" should parseAs {
+
+ enumDecl("Foo") {
+ modifiers { }
+
+ enumBody {
+ it::hasTrailingComma shouldBe true
+ }
+ }
+ }
+
+ "enum Foo { ,; }" should parseAs {
+
+ enumDecl("Foo") {
+ modifiers { }
+ enumBody {
+ it::hasTrailingComma shouldBe true
+ it::hasSeparatorSemi shouldBe true
+ }
+ }
+ }
+
+ "enum Foo { ,, }" shouldNot parse()
+
+ "enum Foo { ; }" should parseAs {
+
+ enumDecl("Foo") {
+ modifiers { }
+ enumBody {
+ it::hasTrailingComma shouldBe false
+ it::hasSeparatorSemi shouldBe true
+ }
+ }
+ }
+
+ "enum Foo { ;; }" should parseAs {
+
+ enumDecl("Foo") {
+ modifiers { }
+
+ enumBody {
+ it::hasTrailingComma shouldBe false
+ it::hasSeparatorSemi shouldBe true
+ child {
+ child {}
+ }
+ }
+ }
+ }
+ }
+ }
+
+
parserTest("Enum constants should have an anonymous class node") {
inContext(TopLevelTypeDeclarationParsingCtx) {
@@ -96,7 +165,6 @@ class ASTEnumConstantTest : ParserTestSpec({
it::getModifiers shouldBe modifiers {}
enumBody {
-
enumConstant("B") {
val c = it
@@ -160,6 +228,7 @@ class ASTEnumConstantTest : ParserTestSpec({
}
it::getAnonymousClass shouldBe null
+
}
}
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
index 0c2bd2fdd0..324448d9b5 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/TestExtensions.kt
@@ -124,7 +124,6 @@ fun TreeNodeWrapper.enumDecl(name: String, spec: NodeSpec.enumBody(contents: NodeSpec = EmptyAssertions) =
child {
contents()