From 7ea8ece305b87aaebf23f5bdfa18dbe76c548833 Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sun, 29 Oct 2023 09:58:49 +0100 Subject: [PATCH 1/5] feat: add UnnamedClassCompilationUnit declaration in parser --- .../java-parser/src/productions/classes.js | 13 +-- .../src/productions/packages-and-modules.js | 41 ++++++++ .../src/productions/utils/class-body-types.js | 17 ++++ .../unnamed-class-compilation-unit-spec.js | 95 +++++++++++++++++++ 4 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 packages/java-parser/src/productions/utils/class-body-types.js create mode 100644 packages/java-parser/test/compilation-unit/unnamed-class-compilation-unit-spec.js 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..65a463dc0 100644 --- a/packages/java-parser/src/productions/packages-and-modules.js +++ b/packages/java-parser/src/productions/packages-and-modules.js @@ -1,5 +1,6 @@ "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 @@ -226,6 +227,46 @@ function defineRules($, t) { ]); }); + $.RULE("unnamedClassCompilationUnit", () => { + $.MANY(() => $.SUBRULE($.importDeclaration)); + + const nextRuleType = $.BACKTRACK_LOOKAHEAD( + $.identifyClassBodyDeclarationType + ); + $.MANY1({ + GATE: () => nextRuleType !== classBodyTypes.methodDeclaration, + DEF: () => $.SUBRULE($.classMemberDeclarationNoMethod, { + ARGS: [nextRuleType] + }) + }); + $.SUBRULE($.methodDeclaration); + $.MANY2(() => { + const nextRuleType = $.BACKTRACK_LOOKAHEAD($.identifyClassBodyDeclarationType); + $.SUBRULE($.classMemberDeclaration, { + ARGS: [nextRuleType] + }); + }); + }); + + $.RULE("classMemberDeclarationNoMethod", nextRuleType => { + $.OR([ + { + GATE: () => nextRuleType === classBodyTypes.fieldDeclaration, + ALT: () => $.SUBRULE($.fieldDeclaration) + }, + { + GATE: () => nextRuleType === classBodyTypes.classDeclaration, + ALT: () => $.SUBRULE($.classDeclaration) + }, + { ALT: () => $.CONSUME(t.Semicolon) }, + { + GATE: () => nextRuleType === classBodyTypes.interfaceDeclaration, + ALT: () => $.SUBRULE($.interfaceDeclaration) + } + ]); + + }); + $.RULE("isModuleCompilationUnit", () => { $.OPTION(() => { $.SUBRULE($.packageDeclaration); 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..3785aabc9 --- /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..9fc6cd6db --- /dev/null +++ b/packages/java-parser/test/compilation-unit/unnamed-class-compilation-unit-spec.js @@ -0,0 +1,95 @@ +"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!"); + } + `; + 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, "unnamedClassCompilationUnit")).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, "unnamedClassCompilationUnit")).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, "unnamedClassCompilationUnit")).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, "unnamedClassCompilationUnit")).to.not.throw(); + }); + + it("should handle UnnamedClassCompilationUnit with semicolons", () => { + const input = ` + ; + + void main() { + System.out.println("Hello World!"); + } + `; + expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).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, "unnamedClassCompilationUnit")).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, "unnamedClassCompilationUnit")).to.not.throw(); + }); +}); From 2b425638b43b9aec34d99466c70503457a30d791 Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sun, 29 Oct 2023 10:37:30 +0100 Subject: [PATCH 2/5] feat: support unnamed class compilation unit (with spec deviation) --- .../src/productions/packages-and-modules.js | 83 +++++++++---------- .../unnamed-class-compilation-unit-spec.js | 15 ++-- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/packages/java-parser/src/productions/packages-and-modules.js b/packages/java-parser/src/productions/packages-and-modules.js index 65a463dc0..d4ce3003e 100644 --- a/packages/java-parser/src/productions/packages-and-modules.js +++ b/packages/java-parser/src/productions/packages-and-modules.js @@ -3,7 +3,15 @@ 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); @@ -96,18 +104,41 @@ 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) + } ]); }); @@ -227,46 +258,6 @@ function defineRules($, t) { ]); }); - $.RULE("unnamedClassCompilationUnit", () => { - $.MANY(() => $.SUBRULE($.importDeclaration)); - - const nextRuleType = $.BACKTRACK_LOOKAHEAD( - $.identifyClassBodyDeclarationType - ); - $.MANY1({ - GATE: () => nextRuleType !== classBodyTypes.methodDeclaration, - DEF: () => $.SUBRULE($.classMemberDeclarationNoMethod, { - ARGS: [nextRuleType] - }) - }); - $.SUBRULE($.methodDeclaration); - $.MANY2(() => { - const nextRuleType = $.BACKTRACK_LOOKAHEAD($.identifyClassBodyDeclarationType); - $.SUBRULE($.classMemberDeclaration, { - ARGS: [nextRuleType] - }); - }); - }); - - $.RULE("classMemberDeclarationNoMethod", nextRuleType => { - $.OR([ - { - GATE: () => nextRuleType === classBodyTypes.fieldDeclaration, - ALT: () => $.SUBRULE($.fieldDeclaration) - }, - { - GATE: () => nextRuleType === classBodyTypes.classDeclaration, - ALT: () => $.SUBRULE($.classDeclaration) - }, - { ALT: () => $.CONSUME(t.Semicolon) }, - { - GATE: () => nextRuleType === classBodyTypes.interfaceDeclaration, - ALT: () => $.SUBRULE($.interfaceDeclaration) - } - ]); - - }); - $.RULE("isModuleCompilationUnit", () => { $.OPTION(() => { $.SUBRULE($.packageDeclaration); 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 index 9fc6cd6db..44ba6be97 100644 --- 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 @@ -10,6 +10,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println("Hello, World!"); } `; + javaParser.parse(input, "compilationUnit"); expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); @@ -19,7 +20,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println("Hello, World!"); } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with fields", () => { @@ -32,7 +33,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println(hourra); } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with class declaration", () => { @@ -43,7 +44,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println(Test.greetings()); } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with interface declaration", () => { @@ -54,7 +55,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println(Test.greetings()); } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with semicolons", () => { @@ -65,7 +66,7 @@ describe("Unnamed Class Compilation Unit", () => { System.out.println("Hello World!"); } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with class member declarations", () => { @@ -77,7 +78,7 @@ describe("Unnamed Class Compilation Unit", () => { } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); it("should handle UnnamedClassCompilationUnit with imports", () => { @@ -90,6 +91,6 @@ describe("Unnamed Class Compilation Unit", () => { } `; - expect(() => javaParser.parse(input, "unnamedClassCompilationUnit")).to.not.throw(); + expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw(); }); }); From 9e2cd48d9c99ae833d3c767ad42d99b4d6faad85 Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sun, 29 Oct 2023 10:43:42 +0100 Subject: [PATCH 3/5] test: add test for Unnamed Class Compilation Unit printing --- .../_input.java | 12 ++++++++++ .../_output.java | 23 +++++++++++++++++++ .../unnamed-class-compilation-unit-spec.ts | 5 ++++ 3 files changed, 40 insertions(+) create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_input.java create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/_output.java create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-class-compilation-unit/unnamed-class-compilation-unit-spec.ts 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); +}); From d7985fa8887fc4ee8e17ccedafd8cf4f253fc357 Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sun, 29 Oct 2023 10:50:38 +0100 Subject: [PATCH 4/5] chore: update signature --- packages/java-parser/api.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 { From 6f007c15923086fc01d8f075960d58464222239f Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sun, 29 Oct 2023 10:51:29 +0100 Subject: [PATCH 5/5] style: fix prettier issue --- packages/java-parser/src/productions/packages-and-modules.js | 2 -- packages/java-parser/src/productions/utils/class-body-types.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/java-parser/src/productions/packages-and-modules.js b/packages/java-parser/src/productions/packages-and-modules.js index d4ce3003e..51b0fae70 100644 --- a/packages/java-parser/src/productions/packages-and-modules.js +++ b/packages/java-parser/src/productions/packages-and-modules.js @@ -3,7 +3,6 @@ const { isRecognitionException, tokenMatcher, EOF } = require("chevrotain"); const { classBodyTypes } = require("./utils/class-body-types"); function defineRules($, t) { - /** * Spec Deviation: As OrdinaryCompilationUnit and UnnamedClassCompilationUnit * both can have multiple class or interface declarations, both were combined @@ -104,7 +103,6 @@ function defineRules($, t) { ]); }); - /** * Spec Deviation: As OrdinaryCompilationUnit and UnnamedClassCompilationUnit * both can have multiple class or interface declarations, both were combined diff --git a/packages/java-parser/src/productions/utils/class-body-types.js b/packages/java-parser/src/productions/utils/class-body-types.js index 3785aabc9..7787d32e2 100644 --- a/packages/java-parser/src/productions/utils/class-body-types.js +++ b/packages/java-parser/src/productions/utils/class-body-types.js @@ -14,4 +14,4 @@ const classBodyTypes = { module.exports = { classBodyTypes -} +};