diff --git a/packages/java-parser/api.d.ts b/packages/java-parser/api.d.ts index f94a263c8..7b00fa972 100644 --- a/packages/java-parser/api.d.ts +++ b/packages/java-parser/api.d.ts @@ -1969,9 +1969,11 @@ export interface TypeDeclarationCstNode extends CstNode { } export type TypeDeclarationCtx = { + Semicolon?: IToken[]; classDeclaration?: ClassDeclarationCstNode[]; interfaceDeclaration?: InterfaceDeclarationCstNode[]; - Semicolon?: IToken[]; + fieldDeclaration?: FieldDeclarationCstNode[]; + methodDeclaration?: MethodDeclarationCstNode[]; }; export interface ModuleDeclarationCstNode extends CstNode { diff --git a/packages/java-parser/src/productions/classes.js b/packages/java-parser/src/productions/classes.js index 509b195d5..ab5e92ff5 100644 --- a/packages/java-parser/src/productions/classes.js +++ b/packages/java-parser/src/productions/classes.js @@ -1,6 +1,7 @@ "use strict"; const { isRecognitionException, tokenMatcher } = require("chevrotain"); +const { classBodyTypes } = require("./utils/class-body-types"); function defineRules($, t) { // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassDeclaration @@ -110,18 +111,6 @@ function defineRules($, t) { $.CONSUME(t.RCurly); }); - const classBodyTypes = { - unknown: 0, - fieldDeclaration: 1, - methodDeclaration: 2, - classDeclaration: 3, - interfaceDeclaration: 4, - semiColon: 5, - instanceInitializer: 6, - staticInitializer: 7, - constructorDeclaration: 8 - }; - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassBodyDeclaration $.RULE("classBodyDeclaration", () => { const nextRuleType = $.BACKTRACK_LOOKAHEAD( diff --git a/packages/java-parser/src/productions/packages-and-modules.js b/packages/java-parser/src/productions/packages-and-modules.js index ff6a4fd9f..51b0fae70 100644 --- a/packages/java-parser/src/productions/packages-and-modules.js +++ b/packages/java-parser/src/productions/packages-and-modules.js @@ -1,8 +1,16 @@ "use strict"; const { isRecognitionException, tokenMatcher, EOF } = require("chevrotain"); +const { classBodyTypes } = require("./utils/class-body-types"); function defineRules($, t) { - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-7.html#CompilationUnit + /** + * Spec Deviation: As OrdinaryCompilationUnit and UnnamedClassCompilationUnit + * both can have multiple class or interface declarations, both were combined + * in the ordinaryCompilationUnit rule + * + * https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-7.3 + * https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-classes-instance-main-methods-jls.html + */ $.RULE("compilationUnit", () => { // custom optimized backtracking lookahead logic const isModule = $.BACKTRACK_LOOKAHEAD($.isModuleCompilationUnit); @@ -95,18 +103,40 @@ function defineRules($, t) { ]); }); - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-7.html#jls-TypeDeclaration + /** + * Spec Deviation: As OrdinaryCompilationUnit and UnnamedClassCompilationUnit + * both can have multiple class or interface declarations, both were combined + * in the ordinaryCompilationUnit rule + * + * As a result, the typeDeclaration combine TopLevelClassOrInterfaceDeclaration and includes fields and method declarations as well + * to handle unnamed class compilation unit + * + * https://docs.oracle.com/javase/specs/jls/se21/html/jls-7.html#jls-TopLevelClassOrInterfaceDeclaration + * https://docs.oracle.com/javase/specs/jls/se21/preview/specs/unnamed-classes-instance-main-methods-jls.html + */ $.RULE("typeDeclaration", () => { // TODO: consider extracting the prefix modifiers here to avoid backtracking - const isClassDeclaration = this.BACKTRACK_LOOKAHEAD($.isClassDeclaration); + const nextRuleType = $.BACKTRACK_LOOKAHEAD( + $.identifyClassBodyDeclarationType + ); $.OR([ + { ALT: () => $.CONSUME(t.Semicolon) }, { - GATE: () => isClassDeclaration, + GATE: () => nextRuleType === classBodyTypes.classDeclaration, ALT: () => $.SUBRULE($.classDeclaration) }, - { ALT: () => $.SUBRULE($.interfaceDeclaration) }, - { ALT: () => $.CONSUME(t.Semicolon) } + { + GATE: () => nextRuleType === classBodyTypes.interfaceDeclaration, + ALT: () => $.SUBRULE($.interfaceDeclaration) + }, + { + GATE: () => nextRuleType === classBodyTypes.fieldDeclaration, + ALT: () => $.SUBRULE($.fieldDeclaration) + }, + { + ALT: () => $.SUBRULE($.methodDeclaration) + } ]); }); diff --git a/packages/java-parser/src/productions/utils/class-body-types.js b/packages/java-parser/src/productions/utils/class-body-types.js new file mode 100644 index 000000000..7787d32e2 --- /dev/null +++ b/packages/java-parser/src/productions/utils/class-body-types.js @@ -0,0 +1,17 @@ +"use strict"; + +const classBodyTypes = { + unknown: 0, + fieldDeclaration: 1, + methodDeclaration: 2, + classDeclaration: 3, + interfaceDeclaration: 4, + semiColon: 5, + instanceInitializer: 6, + staticInitializer: 7, + constructorDeclaration: 8 +}; + +module.exports = { + classBodyTypes +}; diff --git a/packages/java-parser/test/compilation-unit/unnamed-class-compilation-unit-spec.js b/packages/java-parser/test/compilation-unit/unnamed-class-compilation-unit-spec.js new file mode 100644 index 000000000..44ba6be97 --- /dev/null +++ b/packages/java-parser/test/compilation-unit/unnamed-class-compilation-unit-spec.js @@ -0,0 +1,96 @@ +"use strict"; + +const { expect } = require("chai"); +const javaParser = require("../../src/index"); + +describe("Unnamed Class Compilation Unit", () => { + it("should handle UnnamedClassCompilationUnit", () => { + const input = ` + void main() { + System.out.println("Hello, World!"); + } + `; + javaParser.parse(input, "compilationUnit"); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with only method", () => { + const input = ` + void main() { + System.out.println("Hello, World!"); + } + `; + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with fields", () => { + const input = ` + String greeting = "Hello world!"; + String hourra = "Hourra!"; + + void main() { + System.out.println(greeting); + System.out.println(hourra); + } + `; + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with class declaration", () => { + const input = ` + class Test { static String greetings() { return "Hello world!"; } } + + void main() { + System.out.println(Test.greetings()); + } + `; + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with interface declaration", () => { + const input = ` + interface Test { default String greetings() { return "Hello world!"; } } + + void main() { + System.out.println(Test.greetings()); + } + `; + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with semicolons", () => { + const input = ` + ; + + void main() { + System.out.println("Hello World!"); + } + `; + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with class member declarations", () => { + const input = ` + String greeting() { return "Hello, World!"; } + + void main() { + System.out.println(greeting()); + } + `; + + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with imports", () => { + const input = ` + import com.toto.titi.Test; + import com.toto.titi.Toast; + + void main() { + System.out.println(Test.greeting()); + } + `; + + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); + }); +}); diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_input.java b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_input.java new file mode 100644 index 000000000..d9e7ebd91 --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_input.java @@ -0,0 +1,12 @@ +import com.toto.titi.Test; +import com.toto.titi.Toast; + +class TestClass { static String greetings() { return "Hello world!"; } } +interface TestInterface { default String greetings() { return "Hello world!"; } } + +; +String greeting() { return "Hello, World!"; } + +void main() { + System.out.println(Test.greeting()); +} diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_output.java b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_output.java new file mode 100644 index 000000000..a33539e8a --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_output.java @@ -0,0 +1,23 @@ +import com.toto.titi.Test; +import com.toto.titi.Toast; + +class TestClass { + + static String greetings() { + return "Hello world!"; + } +} + +interface TestInterface { + default String greetings() { + return "Hello world!"; + } +} + +String greeting() { + return "Hello, World!"; +} + +void main() { + System.out.println(Test.greeting()); +} diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/unnamed-class-compilation-unit-spec.ts b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/unnamed-class-compilation-unit-spec.ts new file mode 100644 index 000000000..f16f13bd2 --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/unnamed-class-compilation-unit-spec.ts @@ -0,0 +1,5 @@ +import { testSample } from "../../test-utils"; + +describe("prettier-java: Unnamed Class Compilation Unit", () => { + testSample(__dirname); +});