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 { publicvoid 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()