Skip to content

Commit

Permalink
feat: support sealed classes
Browse files Browse the repository at this point in the history
  • Loading branch information
clementdessoude committed Jun 13, 2021
1 parent d20c1e3 commit 0daf032
Show file tree
Hide file tree
Showing 15 changed files with 357 additions and 17 deletions.
36 changes: 35 additions & 1 deletion packages/java-parser/api.d.ts
Expand Up @@ -53,6 +53,7 @@ export abstract class JavaCstVisitor<IN, OUT> implements ICstVisitor<IN, OUT> {
superclass(ctx: SuperclassCtx, param?: IN): OUT;
superinterfaces(ctx: SuperinterfacesCtx, param?: IN): OUT;
interfaceTypeList(ctx: InterfaceTypeListCtx, param?: IN): OUT;
classPermits(ctx: ClassPermitsCtx, param?: IN): OUT;
classBody(ctx: ClassBodyCtx, param?: IN): OUT;
classBodyDeclaration(ctx: ClassBodyDeclarationCtx, param?: IN): OUT;
classMemberDeclaration(ctx: ClassMemberDeclarationCtx, param?: IN): OUT;
Expand Down Expand Up @@ -164,6 +165,7 @@ export abstract class JavaCstVisitor<IN, OUT> implements ICstVisitor<IN, OUT> {
): OUT;
interfaceModifier(ctx: InterfaceModifierCtx, param?: IN): OUT;
extendsInterfaces(ctx: ExtendsInterfacesCtx, param?: IN): OUT;
interfacePermits(ctx: InterfacePermitsCtx, param?: IN): OUT;
interfaceBody(ctx: InterfaceBodyCtx, param?: IN): OUT;
interfaceMemberDeclaration(
ctx: InterfaceMemberDeclarationCtx,
Expand Down Expand Up @@ -401,6 +403,7 @@ export abstract class JavaCstVisitorWithDefaults<IN, OUT>
superclass(ctx: SuperclassCtx, param?: IN): OUT;
superinterfaces(ctx: SuperinterfacesCtx, param?: IN): OUT;
interfaceTypeList(ctx: InterfaceTypeListCtx, param?: IN): OUT;
classPermits(ctx: ClassPermitsCtx, param?: IN): OUT;
classBody(ctx: ClassBodyCtx, param?: IN): OUT;
classBodyDeclaration(ctx: ClassBodyDeclarationCtx, param?: IN): OUT;
classMemberDeclaration(ctx: ClassMemberDeclarationCtx, param?: IN): OUT;
Expand Down Expand Up @@ -512,6 +515,7 @@ export abstract class JavaCstVisitorWithDefaults<IN, OUT>
): OUT;
interfaceModifier(ctx: InterfaceModifierCtx, param?: IN): OUT;
extendsInterfaces(ctx: ExtendsInterfacesCtx, param?: IN): OUT;
interfacePermits(ctx: InterfacePermitsCtx, param?: IN): OUT;
interfaceBody(ctx: InterfaceBodyCtx, param?: IN): OUT;
interfaceMemberDeclaration(
ctx: InterfaceMemberDeclarationCtx,
Expand Down Expand Up @@ -1050,6 +1054,7 @@ export type NormalClassDeclarationCtx = {
typeParameters?: TypeParametersCstNode[];
superclass?: SuperclassCstNode[];
superinterfaces?: SuperinterfacesCstNode[];
classPermits?: ClassPermitsCstNode[];
classBody: ClassBodyCstNode[];
};

Expand All @@ -1066,6 +1071,8 @@ export type ClassModifierCtx = {
Abstract?: IToken[];
Static?: IToken[];
Final?: IToken[];
Sealed?: IToken[];
NonSealed?: IToken[];
Strictfp?: IToken[];
};

Expand Down Expand Up @@ -1120,6 +1127,17 @@ export type InterfaceTypeListCtx = {
Comma?: IToken[];
};

export interface ClassPermitsCstNode extends CstNode {
name: "classPermits";
children: ClassPermitsCtx;
}

export type ClassPermitsCtx = {
Permits: IToken[];
typeName: TypeNameCstNode[];
Comma?: IToken[];
};

export interface ClassBodyCstNode extends CstNode {
name: "classBody";
children: ClassBodyCtx;
Expand Down Expand Up @@ -1703,7 +1721,8 @@ export interface RecordComponentCstNode extends CstNode {
export type RecordComponentCtx = {
recordComponentModifier?: RecordComponentModifierCstNode[];
unannType: UnannTypeCstNode[];
Identifier: IToken[];
Identifier?: IToken[];
variableArityRecordComponent?: VariableArityRecordComponentCstNode[];
};

export interface VariableArityRecordComponentCstNode extends CstNode {
Expand Down Expand Up @@ -1811,6 +1830,7 @@ export type IsCompactConstructorDeclarationCtx = {
Protected?: IToken[];
Private?: IToken[];
simpleTypeName: SimpleTypeNameCstNode[];
LCurly: IToken[];
};

export interface CompilationUnitCstNode extends CstNode {
Expand Down Expand Up @@ -2019,6 +2039,7 @@ export type NormalInterfaceDeclarationCtx = {
typeIdentifier: TypeIdentifierCstNode[];
typeParameters?: TypeParametersCstNode[];
extendsInterfaces?: ExtendsInterfacesCstNode[];
interfacePermits?: InterfacePermitsCstNode[];
interfaceBody: InterfaceBodyCstNode[];
};

Expand All @@ -2034,6 +2055,8 @@ export type InterfaceModifierCtx = {
Private?: IToken[];
Abstract?: IToken[];
Static?: IToken[];
Sealed?: IToken[];
NonSealed?: IToken[];
Strictfp?: IToken[];
};

Expand All @@ -2047,6 +2070,17 @@ export type ExtendsInterfacesCtx = {
interfaceTypeList: InterfaceTypeListCstNode[];
};

export interface InterfacePermitsCstNode extends CstNode {
name: "interfacePermits";
children: InterfacePermitsCtx;
}

export type InterfacePermitsCtx = {
Permits: IToken[];
typeName: TypeNameCstNode[];
Comma?: IToken[];
};

export interface InterfaceBodyCstNode extends CstNode {
name: "interfaceBody";
children: InterfaceBodyCtx;
Expand Down
4 changes: 4 additions & 0 deletions packages/java-parser/scripts/clone-samples.js
Expand Up @@ -83,6 +83,10 @@ const sampleRepos = [
{
repoUrl: "https://github.com/jhipster/jhipster-sample-app-react",
branch: "main"
},
{
repoUrl: "https://github.com/nipafx/demo-java-x",
branch: "master"
}
];

Expand Down
28 changes: 17 additions & 11 deletions packages/java-parser/src/productions/blocks-and-statements.js
Expand Up @@ -31,17 +31,23 @@ function defineRules($, t) {

const isClassDeclaration = this.BACKTRACK_LOOKAHEAD($.isClassDeclaration);

$.OR([
{
GATE: () => isLocalVariableDeclaration,
ALT: () => $.SUBRULE($.localVariableDeclarationStatement)
},
{
GATE: () => isClassDeclaration,
ALT: () => $.SUBRULE($.classDeclaration)
},
{ ALT: () => $.SUBRULE($.statement) }
]);
$.OR({
DEF: [
{
GATE: () => isLocalVariableDeclaration,
ALT: () => $.SUBRULE($.localVariableDeclarationStatement)
},
{
GATE: () => isClassDeclaration,
ALT: () => $.SUBRULE($.classDeclaration)
},
{
ALT: () => $.SUBRULE($.interfaceDeclaration)
},
{ ALT: () => $.SUBRULE($.statement) }
],
IGNORE_AMBIGUITIES: true
});
});

// https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-LocalVariableDeclarationStatement
Expand Down
15 changes: 15 additions & 0 deletions packages/java-parser/src/productions/classes.js
Expand Up @@ -32,6 +32,9 @@ function defineRules($, t) {
$.OPTION3(() => {
$.SUBRULE($.superinterfaces);
});
$.OPTION4(() => {
$.SUBRULE($.classPermits);
});
$.SUBRULE($.classBody);
});

Expand All @@ -45,6 +48,8 @@ function defineRules($, t) {
{ ALT: () => $.CONSUME(t.Abstract) },
{ ALT: () => $.CONSUME(t.Static) },
{ ALT: () => $.CONSUME(t.Final) },
{ ALT: () => $.CONSUME(t.Sealed) },
{ ALT: () => $.CONSUME(t.NonSealed) },
{ ALT: () => $.CONSUME(t.Strictfp) }
]);
});
Expand Down Expand Up @@ -86,6 +91,16 @@ function defineRules($, t) {
});
});

// https://docs.oracle.com/javase/specs/jls/se16/preview/specs/sealed-classes-jls.html
$.RULE("classPermits", () => {
$.CONSUME(t.Permits);
$.SUBRULE($.typeName);
$.MANY(() => {
$.CONSUME(t.Comma);
$.SUBRULE2($.typeName);
});
});

// https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-ClassBody
$.RULE("classBody", () => {
$.CONSUME(t.LCurly);
Expand Down
17 changes: 16 additions & 1 deletion packages/java-parser/src/productions/interfaces.js
Expand Up @@ -29,6 +29,9 @@ function defineRules($, t) {
$.OPTION2(() => {
$.SUBRULE($.extendsInterfaces);
});
$.OPTION3(() => {
$.SUBRULE($.interfacePermits);
});
$.SUBRULE($.interfaceBody);
});

Expand All @@ -41,16 +44,28 @@ function defineRules($, t) {
{ ALT: () => $.CONSUME(t.Private) },
{ ALT: () => $.CONSUME(t.Abstract) },
{ ALT: () => $.CONSUME(t.Static) },
{ ALT: () => $.CONSUME(t.Sealed) },
{ ALT: () => $.CONSUME(t.NonSealed) },
{ ALT: () => $.CONSUME(t.Strictfp) }
]);
});

// https://docs.oracle.com/javase/specs/jls/se16/html/jls-9.html#jls-ExtendsInterfaces
// https://docs.oracle.com/javase/specs/jls/se16/html/jls-9.html#jls-InterfaceExtends
$.RULE("extendsInterfaces", () => {
$.CONSUME(t.Extends);
$.SUBRULE($.interfaceTypeList);
});

// https://docs.oracle.com/javase/specs/jls/se16/preview/specs/sealed-classes-jls.html
$.RULE("interfacePermits", () => {
$.CONSUME(t.Permits);
$.SUBRULE($.typeName);
$.MANY(() => {
$.CONSUME(t.Comma);
$.SUBRULE2($.typeName);
});
});

// https://docs.oracle.com/javase/specs/jls/se16/html/jls-9.html#jls-InterfaceBody
$.RULE("interfaceBody", () => {
$.CONSUME(t.LCurly);
Expand Down
9 changes: 7 additions & 2 deletions packages/java-parser/src/tokens.js
@@ -1,5 +1,7 @@
"use strict";
const { createToken: createTokenOrg, Lexer } = require("chevrotain");
const camelCase = require("lodash/camelCase");

let chars;
// A little mini DSL for easier lexer definition.
const fragments = {};
Expand Down Expand Up @@ -247,14 +249,17 @@ const restrictedKeywords = [
"to",
"uses",
"provides",
"with"
"with",
"sealed",
"non-sealed",
"permits"
];

// By sorting the keywords in descending order we avoid ambiguities
// of common prefixes.
sortDescLength(restrictedKeywords).forEach(word => {
createKeywordLikeToken({
name: word[0].toUpperCase() + word.substr(1),
name: word[0].toUpperCase() + camelCase(word.substr(1)),
pattern: word,
// restricted keywords can also be used as an Identifiers according to the spec.
// TODO: inspect this causes no ambiguities
Expand Down
1 change: 1 addition & 0 deletions packages/java-parser/test/samples-spec.js
Expand Up @@ -27,6 +27,7 @@ describe("The Java Parser", () => {
createSampleSpecs("jhipster-sample-app-react");
createSampleSpecs("jhipster-sample-app-websocket");
createSampleSpecs("guava");
createSampleSpecs("demo-java-x");
});

function createSampleSpecs(sampleName) {
Expand Down
58 changes: 58 additions & 0 deletions packages/java-parser/test/sealed/sealed-spec.js
@@ -0,0 +1,58 @@
"use strict";

const { expect } = require("chai");
const javaParser = require("../../src/index");

describe("Sealed Classes & Interfaces", () => {
it("should handle sealed interfaces", () => {
const input = `
public sealed interface People {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle non-sealed interfaces", () => {
const input = `
public non-sealed interface People {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle sealed interfaces with permits", () => {
const input = `
public sealed interface Rectangle permits Square {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle sealed class", () => {
const input = `
public sealed class People {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle non-sealed class", () => {
const input = `
public non-sealed class People {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle sealed class with permits", () => {
const input = `
public sealed class Rectangle permits Square {}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});

it("should handle sealed and permits as field names", () => {
const input = `
public class Rectangle {
boolean sealed;
boolean permits;
}
`;
expect(() => javaParser.parse(input, "compilationUnit")).to.not.throw();
});
});
2 changes: 2 additions & 0 deletions packages/prettier-plugin-java/src/options.js
Expand Up @@ -66,6 +66,7 @@ module.exports = {
{ value: "superclass" },
{ value: "superinterfaces" },
{ value: "interfaceTypeList" },
{ value: "classPermits" },
{ value: "classBody" },
{ value: "classBodyDeclaration" },
{ value: "classMemberDeclaration" },
Expand Down Expand Up @@ -180,6 +181,7 @@ module.exports = {
{ value: "normalInterfaceDeclaration" },
{ value: "interfaceModifier" },
{ value: "extendsInterfaces" },
{ value: "interfacePermits" },
{ value: "interfaceBody" },
{ value: "interfaceMemberDeclaration" },
{ value: "constantDeclaration" },
Expand Down

0 comments on commit 0daf032

Please sign in to comment.