diff --git a/packages/java-parser/api.d.ts b/packages/java-parser/api.d.ts index 6f54a331..18f54bc0 100644 --- a/packages/java-parser/api.d.ts +++ b/packages/java-parser/api.d.ts @@ -53,6 +53,7 @@ export abstract class JavaCstVisitor implements ICstVisitor { 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; @@ -164,6 +165,7 @@ export abstract class JavaCstVisitor implements ICstVisitor { ): 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, @@ -401,6 +403,7 @@ export abstract class JavaCstVisitorWithDefaults 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; @@ -512,6 +515,7 @@ export abstract class JavaCstVisitorWithDefaults ): 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, @@ -1050,6 +1054,7 @@ export type NormalClassDeclarationCtx = { typeParameters?: TypeParametersCstNode[]; superclass?: SuperclassCstNode[]; superinterfaces?: SuperinterfacesCstNode[]; + classPermits?: ClassPermitsCstNode[]; classBody: ClassBodyCstNode[]; }; @@ -1066,6 +1071,8 @@ export type ClassModifierCtx = { Abstract?: IToken[]; Static?: IToken[]; Final?: IToken[]; + Sealed?: IToken[]; + NonSealed?: IToken[]; Strictfp?: IToken[]; }; @@ -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; @@ -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 { @@ -1811,6 +1830,7 @@ export type IsCompactConstructorDeclarationCtx = { Protected?: IToken[]; Private?: IToken[]; simpleTypeName: SimpleTypeNameCstNode[]; + LCurly: IToken[]; }; export interface CompilationUnitCstNode extends CstNode { @@ -2019,6 +2039,7 @@ export type NormalInterfaceDeclarationCtx = { typeIdentifier: TypeIdentifierCstNode[]; typeParameters?: TypeParametersCstNode[]; extendsInterfaces?: ExtendsInterfacesCstNode[]; + interfacePermits?: InterfacePermitsCstNode[]; interfaceBody: InterfaceBodyCstNode[]; }; @@ -2034,6 +2055,8 @@ export type InterfaceModifierCtx = { Private?: IToken[]; Abstract?: IToken[]; Static?: IToken[]; + Sealed?: IToken[]; + NonSealed?: IToken[]; Strictfp?: IToken[]; }; @@ -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; diff --git a/packages/java-parser/scripts/clone-samples.js b/packages/java-parser/scripts/clone-samples.js index 9b1f0715..bec3989e 100644 --- a/packages/java-parser/scripts/clone-samples.js +++ b/packages/java-parser/scripts/clone-samples.js @@ -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" } ]; diff --git a/packages/java-parser/src/productions/blocks-and-statements.js b/packages/java-parser/src/productions/blocks-and-statements.js index 30d91380..bb04a75a 100644 --- a/packages/java-parser/src/productions/blocks-and-statements.js +++ b/packages/java-parser/src/productions/blocks-and-statements.js @@ -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 diff --git a/packages/java-parser/src/productions/classes.js b/packages/java-parser/src/productions/classes.js index 3df381dc..3da984a9 100644 --- a/packages/java-parser/src/productions/classes.js +++ b/packages/java-parser/src/productions/classes.js @@ -32,6 +32,9 @@ function defineRules($, t) { $.OPTION3(() => { $.SUBRULE($.superinterfaces); }); + $.OPTION4(() => { + $.SUBRULE($.classPermits); + }); $.SUBRULE($.classBody); }); @@ -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) } ]); }); @@ -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); diff --git a/packages/java-parser/src/productions/interfaces.js b/packages/java-parser/src/productions/interfaces.js index 5eafde52..9dd3a42a 100644 --- a/packages/java-parser/src/productions/interfaces.js +++ b/packages/java-parser/src/productions/interfaces.js @@ -29,6 +29,9 @@ function defineRules($, t) { $.OPTION2(() => { $.SUBRULE($.extendsInterfaces); }); + $.OPTION3(() => { + $.SUBRULE($.interfacePermits); + }); $.SUBRULE($.interfaceBody); }); @@ -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); diff --git a/packages/java-parser/src/tokens.js b/packages/java-parser/src/tokens.js index 95243587..c12ff761 100644 --- a/packages/java-parser/src/tokens.js +++ b/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 = {}; @@ -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 diff --git a/packages/java-parser/test/samples-spec.js b/packages/java-parser/test/samples-spec.js index e552d67b..2bd39590 100644 --- a/packages/java-parser/test/samples-spec.js +++ b/packages/java-parser/test/samples-spec.js @@ -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) { diff --git a/packages/java-parser/test/sealed/sealed-spec.js b/packages/java-parser/test/sealed/sealed-spec.js new file mode 100644 index 00000000..d391933f --- /dev/null +++ b/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(); + }); +}); diff --git a/packages/prettier-plugin-java/src/options.js b/packages/prettier-plugin-java/src/options.js index ad2e3399..4cef7702 100644 --- a/packages/prettier-plugin-java/src/options.js +++ b/packages/prettier-plugin-java/src/options.js @@ -66,6 +66,7 @@ module.exports = { { value: "superclass" }, { value: "superinterfaces" }, { value: "interfaceTypeList" }, + { value: "classPermits" }, { value: "classBody" }, { value: "classBodyDeclaration" }, { value: "classMemberDeclaration" }, @@ -180,6 +181,7 @@ module.exports = { { value: "normalInterfaceDeclaration" }, { value: "interfaceModifier" }, { value: "extendsInterfaces" }, + { value: "interfacePermits" }, { value: "interfaceBody" }, { value: "interfaceMemberDeclaration" }, { value: "constantDeclaration" }, diff --git a/packages/prettier-plugin-java/src/printers/classes.js b/packages/prettier-plugin-java/src/printers/classes.js index d5c6c8ed..99cab093 100644 --- a/packages/prettier-plugin-java/src/printers/classes.js +++ b/packages/prettier-plugin-java/src/printers/classes.js @@ -49,6 +49,7 @@ class ClassesPrettierVisitor { const optionalTypeParams = this.visit(ctx.typeParameters); const optionalSuperClasses = this.visit(ctx.superclass); const optionalSuperInterfaces = this.visit(ctx.superinterfaces); + const optionalClassPermits = this.visit(ctx.classPermits); const body = this.visit(ctx.classBody, { isNormalClassDeclaration: true }); let superClassesPart = ""; @@ -63,13 +64,19 @@ class ClassesPrettierVisitor { ); } + let classPermits = ""; + if (optionalClassPermits) { + classPermits = indent(rejectAndConcat([line, optionalClassPermits])); + } + return rejectAndJoin(" ", [ group( rejectAndConcat([ rejectAndJoin(" ", [ctx.Class[0], name]), optionalTypeParams, superClassesPart, - superInterfacesPart + superInterfacesPart, + classPermits ]) ), body @@ -112,6 +119,20 @@ class ClassesPrettierVisitor { ); } + classPermits(ctx) { + const typeNames = this.mapVisit(ctx.typeName); + const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; + + return group( + rejectAndConcat([ + ctx.Permits[0], + indent( + rejectAndConcat([line, group(rejectAndJoinSeps(commas, typeNames))]) + ) + ]) + ); + } + interfaceTypeList(ctx) { const interfaceType = this.mapVisit(ctx.interfaceType); const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : []; diff --git a/packages/prettier-plugin-java/src/printers/interfaces.js b/packages/prettier-plugin-java/src/printers/interfaces.js index ca3902db..ddbdaa6e 100644 --- a/packages/prettier-plugin-java/src/printers/interfaces.js +++ b/packages/prettier-plugin-java/src/printers/interfaces.js @@ -35,6 +35,7 @@ class InterfacesPrettierVisitor { const typeIdentifier = this.visit(ctx.typeIdentifier); const typeParameters = this.visit(ctx.typeParameters); const extendsInterfaces = this.visit(ctx.extendsInterfaces); + const optionalInterfacePermits = this.visit(ctx.interfacePermits); const interfaceBody = this.visit(ctx.interfaceBody); let extendsInterfacesPart = ""; @@ -44,12 +45,20 @@ class InterfacesPrettierVisitor { ); } + let interfacePermits = ""; + if (optionalInterfacePermits) { + interfacePermits = indent( + rejectAndConcat([softline, optionalInterfacePermits]) + ); + } + return rejectAndJoin(" ", [ group( rejectAndJoin(" ", [ ctx.Interface[0], concat([typeIdentifier, typeParameters]), - extendsInterfacesPart + extendsInterfacesPart, + interfacePermits ]) ), interfaceBody @@ -74,6 +83,10 @@ class InterfacesPrettierVisitor { ); } + interfacePermits(ctx) { + return this.classPermits(ctx); + } + interfaceBody(ctx) { let joinedInterfaceMemberDeclaration = ""; diff --git a/packages/prettier-plugin-java/src/printers/printer-utils.js b/packages/prettier-plugin-java/src/printers/printer-utils.js index a077cf5b..3417c608 100644 --- a/packages/prettier-plugin-java/src/printers/printer-utils.js +++ b/packages/prettier-plugin-java/src/printers/printer-utils.js @@ -20,6 +20,8 @@ const orderedModifiers = [ "Volatile", "Synchronized", "Native", + "Sealed", + "NonSealed", "Strictfp" ]; diff --git a/packages/prettier-plugin-java/test/unit-test/sealed/_input.java b/packages/prettier-plugin-java/test/unit-test/sealed/_input.java new file mode 100644 index 00000000..1f498eee --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/sealed/_input.java @@ -0,0 +1,72 @@ +public sealed class Rectangle + implements Shape + permits Square { + + private final double length; + private final double height; + + public Rectangle(double length, double height) { + this.length = length; + this.height = height; + } + + @Override + public double area() { + return length * height; + } + +} + +public non-sealed class RightTriangle implements Triangle { + + private final double adjacent; + private final double opposite; + + public RightTriangle(double adjacent, double opposite) { + this.adjacent = adjacent; + this.opposite = opposite; + } + + @Override + public double area() { + interface People { String name(); } + record Person(String name) implements People { } + record Persons(String... names) { } + + People p = new Person("John Doe"); + + return adjacent * opposite / 2; + } + +} + +public sealed interface Shape + permits Circle, Rectangle, Triangle, Unicorn { + + double area(); + + default Shape rotate(double angle) { + return this; + } + + default String areaMessage() { + if (this instanceof Circle) + return "Circle: " + area(); + else if (this instanceof Rectangle) + return "Rectangle: " + area(); + else if (this instanceof RightTriangle) + return "Triangle: " + area(); + // :( + throw new IllegalArgumentException(); + } + +} + +public non-sealed interface Triangle extends Shape { + +} + +public sealed interface Shape permits ALongVeryLongCircle, ALongVeryLongRectangle, ALongVeryLongTriangle, ALongVeryLongUnicorn {} +public sealed interface Shape extends AbstractShape permits ALongVeryLongCircle, ALongVeryLongRectangle, ALongVeryLongTriangle, ALongVeryLongUnicorn {} +public sealed class Shape permits ALongVeryLongCircle, ALongVeryLongRectangle, ALongVeryLongTriangle, ALongVeryLongUnicorn {} +public sealed class Shape extends AbstractShape permits ALongVeryLongCircle, ALongVeryLongRectangle, ALongVeryLongTriangle, ALongVeryLongUnicorn {} diff --git a/packages/prettier-plugin-java/test/unit-test/sealed/_output.java b/packages/prettier-plugin-java/test/unit-test/sealed/_output.java new file mode 100644 index 00000000..6a756e68 --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/sealed/_output.java @@ -0,0 +1,89 @@ +public sealed class Rectangle implements Shape permits Square { + + private final double length; + private final double height; + + public Rectangle(double length, double height) { + this.length = length; + this.height = height; + } + + @Override + public double area() { + return length * height; + } +} + +public non-sealed class RightTriangle implements Triangle { + + private final double adjacent; + private final double opposite; + + public RightTriangle(double adjacent, double opposite) { + this.adjacent = adjacent; + this.opposite = opposite; + } + + @Override + public double area() { + interface People { + String name(); + } + record Person(String name) implements People {} + record Persons(String... names) {} + + People p = new Person("John Doe"); + + return adjacent * opposite / 2; + } +} + +public sealed interface Shape permits Circle, Rectangle, Triangle, Unicorn { + double area(); + + default Shape rotate(double angle) { + return this; + } + + default String areaMessage() { + if (this instanceof Circle) return "Circle: " + area(); else if ( + this instanceof Rectangle + ) return "Rectangle: " + area(); else if ( + this instanceof RightTriangle + ) return "Triangle: " + area(); + // :( + throw new IllegalArgumentException(); + } +} + +public non-sealed interface Triangle extends Shape {} + +public sealed interface Shape + permits + ALongVeryLongCircle, + ALongVeryLongRectangle, + ALongVeryLongTriangle, + ALongVeryLongUnicorn {} + +public sealed interface Shape + extends AbstractShape + permits + ALongVeryLongCircle, + ALongVeryLongRectangle, + ALongVeryLongTriangle, + ALongVeryLongUnicorn {} + +public sealed class Shape + permits + ALongVeryLongCircle, + ALongVeryLongRectangle, + ALongVeryLongTriangle, + ALongVeryLongUnicorn {} + +public sealed class Shape + extends AbstractShape + permits + ALongVeryLongCircle, + ALongVeryLongRectangle, + ALongVeryLongTriangle, + ALongVeryLongUnicorn {} diff --git a/packages/prettier-plugin-java/test/unit-test/sealed/sealed-spec.js b/packages/prettier-plugin-java/test/unit-test/sealed/sealed-spec.js new file mode 100644 index 00000000..43e43b5b --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/sealed/sealed-spec.js @@ -0,0 +1,3 @@ +describe("sealed classes and interfaces", () => { + require("../../test-utils").testSample(__dirname); +});