Repository: javaparser/javaparser (language: java)

Issue:
Local Enum and Interface (Java 16)
From Java 16's [JEP395](https://openjdk.org/jeps/395):

> The ability to declare local record classes, local enum classes, and local interfaces was introduced

JavaParser does allow for local records, but does not allow local enums or interfaces.

Enums:
```java
void b() {
    enum Z { Y, X }
    System.out.println("Z: " + Z.Y);
}
```

Error: 

> Parse error. Found "{", expected one of  "," ";" "=" "@" "["

Interfaces:
```java
void a() {
    interface B { void b(); }
    B b = new B() {
        @Override 
        public void b() { 
            System.out.println("B");
        }
    };
    b.b();
 }
```

Error:

> There is no such thing as a local interface.




Discussion / hints:
Thanks for reporting this issue which specifically refers to https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-14.3.
To be completely sincere I'm not sure that this problem will be solved quickly because the main developers of JavaParser are no longer active.
> From Java 16's [JEP395](https://openjdk.org/jeps/395):
> 
> > The ability to declare local record classes, local enum classes, and local interfaces was introduced

If you (or anybody else) want a Java parser that is up to date to the latest spec (JDK 20 at the moment) you could have a look [here](https://parsers.org/announcements/reference-java-grammar/). The parser described there certainly supports all the language features currently classified as "stable". It also supports a couple of preview features, such as switch-case with pattern matching.

If you have any questions, it would be better to pose them in the [CongoCC discussion forum](https://discuss.congocc.org/).
Is there any hope of getting this resolved? This seems to be the only missing feature that you are likely to encounter in practice when parsing Java files for Java 17 or older (at least it is the only missing feature that I have encountered in practice working with Java 17 code).

Fix patch:
diff --git a/javaparser-core/src/main/java/com/github/javaparser/ast/validator/language_level_validations/Java16Validator.java b/javaparser-core/src/main/java/com/github/javaparser/ast/validator/language_level_validations/Java16Validator.java
index 4a7d854a37..3f399f8267 100644
--- a/javaparser-core/src/main/java/com/github/javaparser/ast/validator/language_level_validations/Java16Validator.java
+++ b/javaparser-core/src/main/java/com/github/javaparser/ast/validator/language_level_validations/Java16Validator.java
@@ -35,6 +35,9 @@ public Java16Validator() {
         {
             // Records released within Java 16 - https://openjdk.java.net/jeps/395
             remove(noRecordDeclaration);
+            // local interface released within Java 16 -
+            // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-14.3
+            remove(innerClasses);
             add(recordAsTypeIdentifierNotAllowed);
             add(recordDeclarationValidator);
         }
diff --git a/javaparser-core/src/main/javacc/java.jj b/javaparser-core/src/main/javacc/java.jj
index 4fa7237d39..da719adfd9 100644
--- a/javaparser-core/src/main/javacc/java.jj
+++ b/javaparser-core/src/main/javacc/java.jj
@@ -4414,7 +4414,7 @@ Statement BlockStatement():
 {
     try {
         (
-            LOOKAHEAD( Modifiers() ("class" | "interface") ) // TODO/FIXME: Is this a bug in the grammar? JLS specifies class or enum, not interface.
+            LOOKAHEAD( Modifiers() ("class" | "interface") ) // JLS specifies local class. Since java 16 local enum or interface are allowed.
             modifier = Modifiers()
             typeDecl = ClassOrInterfaceDeclaration(modifier) { ret = new LocalClassDeclarationStmt(range(typeDecl, token()), typeDecl); }
          |


Test patch:
diff --git a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/validator/Java16ValidatorTest.java b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/validator/Java16ValidatorTest.java
index 4392a6057a..ea178cf208 100644
--- a/javaparser-core-testing/src/test/java/com/github/javaparser/ast/validator/Java16ValidatorTest.java
+++ b/javaparser-core-testing/src/test/java/com/github/javaparser/ast/validator/Java16ValidatorTest.java
@@ -25,13 +25,14 @@
 import static com.github.javaparser.ParseStart.STATEMENT;
 import static com.github.javaparser.ParserConfiguration.LanguageLevel.JAVA_16;
 import static com.github.javaparser.Providers.provider;
+import static com.github.javaparser.utils.TestUtils.assertNoProblems;
+import static com.github.javaparser.utils.TestUtils.assertProblems;
 
 import com.github.javaparser.JavaParser;
 import com.github.javaparser.ParseResult;
 import com.github.javaparser.ParserConfiguration;
 import com.github.javaparser.ast.CompilationUnit;
 import com.github.javaparser.ast.stmt.Statement;
-import com.github.javaparser.utils.TestUtils;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 
@@ -39,12 +40,19 @@ class Java16ValidatorTest {
 
     private final JavaParser javaParser = new JavaParser(new ParserConfiguration().setLanguageLevel(JAVA_16));
 
+    @Test
+    void localInterface() {
+        ParseResult<CompilationUnit> result =
+                javaParser.parse(COMPILATION_UNIT, provider("class X{ void x() {" + "interface I{}}}"));
+        assertNoProblems(result);
+    }
+
     @Nested
     class Yield {
         @Test
         void yieldAllowed() {
             ParseResult<Statement> result = javaParser.parse(STATEMENT, provider("switch(x){case 3: yield 6;}"));
-            TestUtils.assertNoProblems(result);
+            assertNoProblems(result);
         }
     }
 
@@ -53,13 +61,13 @@ class PatternMatching {
         @Test
         void patternMatchingAllowed() {
             ParseResult<Statement> result = javaParser.parse(STATEMENT, provider("if (a instanceof String s) {}"));
-            TestUtils.assertNoProblems(result);
+            assertNoProblems(result);
         }
 
         @Test
         void recordPatternsForbidden() {
             ParseResult<Statement> result = javaParser.parse(STATEMENT, provider("if (a instanceof Box(String s)) {}"));
-            TestUtils.assertProblems(
+            assertProblems(
                     result,
                     "(line 1,col 18) Record patterns are not supported. Pay attention that this feature is supported starting from 'JAVA_21' language level. If you need that feature the language level must be configured in the configuration before parsing the source files.");
         }
@@ -78,7 +86,7 @@ class RecordAsTypeIdentifierForbidden {
             void recordUsedAsClassIdentifier() {
                 String s = "public class record {}";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertProblems(
+                assertProblems(
                         result,
                         "(line 1,col 14) 'record' is a restricted identifier and cannot be used for type declarations");
             }
@@ -87,7 +95,7 @@ void recordUsedAsClassIdentifier() {
             void recordUsedAsEnumIdentifier() {
                 String s = "public enum record {}";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertProblems(
+                assertProblems(
                         result,
                         "(line 1,col 13) 'record' is a restricted identifier and cannot be used for type declarations");
             }
@@ -96,7 +104,7 @@ void recordUsedAsEnumIdentifier() {
             void recordUsedAsRecordIdentifier() {
                 String s = "public record record() {}";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertProblems(
+                assertProblems(
                         result,
                         "(line 1,col 15) 'record' is a restricted identifier and cannot be used for type declarations");
             }
@@ -108,14 +116,14 @@ class RecordUsedAsIdentifierAllowedAsFieldDeclarations {
             void recordUsedAsFieldIdentifierInClass() {
                 String s = "class X { int record; }";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertNoProblems(result);
+                assertNoProblems(result);
             }
 
             @Test
             void recordUsedAsFieldIdentifierInInterface() {
                 String s = "interface X { int record; }";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertNoProblems(result);
+                assertNoProblems(result);
             }
         }
 
@@ -125,7 +133,7 @@ class RecordDeclarationPermitted {
             void recordDeclaration() {
                 String s = "record X() { }";
                 ParseResult<CompilationUnit> result = javaParser.parse(COMPILATION_UNIT, provider(s));
-                TestUtils.assertNoProblems(result);
+                assertNoProblems(result);
             }
         }
     }