Skip to content

Commit

Permalink
Support unnamed class compilation unit (#615)
Browse files Browse the repository at this point in the history
* feat: add UnnamedClassCompilationUnit declaration in parser

* feat: support unnamed class compilation unit (with spec deviation)

* test: add test for Unnamed Class Compilation Unit printing

* chore: update signature

* style: fix prettier issue
  • Loading branch information
clementdessoude authored Nov 12, 2023
1 parent 87192b4 commit f489ce2
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 19 deletions.
4 changes: 3 additions & 1 deletion packages/java-parser/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1973,9 +1973,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 {
Expand Down
13 changes: 1 addition & 12 deletions packages/java-parser/src/productions/classes.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down
42 changes: 36 additions & 6 deletions packages/java-parser/src/productions/packages-and-modules.js
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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)
}
]);
});

Expand Down
17 changes: 17 additions & 0 deletions packages/java-parser/src/productions/utils/class-body-types.js
Original file line number Diff line number Diff line change
@@ -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
};
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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());
}
Original file line number Diff line number Diff line change
@@ -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());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { testSample } from "../../test-utils";

describe("prettier-java: Unnamed Class Compilation Unit", () => {
testSample(__dirname);
});

0 comments on commit f489ce2

Please sign in to comment.