From 2f7efa41db2423a200236027afb9f0fe8c198b02 Mon Sep 17 00:00:00 2001 From: Clement Dessoude Date: Sat, 2 Oct 2021 18:44:25 +0200 Subject: [PATCH] feat: support pattern matching in switch statements --- packages/java-parser/api.d.ts | 28 +++++++++++- .../src/productions/blocks-and-statements.js | 44 ++++++++++++++----- .../blocks-and-statements/switch-case-spec.js | 34 ++++++++++++++ packages/prettier-plugin-java/src/options.js | 2 + .../src/printers/blocks-and-statements.ts | 43 ++++++++++++------ .../test/unit-test/switch/_input.java | 43 ++++++++++++++++++ .../test/unit-test/switch/_output.java | 35 +++++++++++++++ 7 files changed, 203 insertions(+), 26 deletions(-) diff --git a/packages/java-parser/api.d.ts b/packages/java-parser/api.d.ts index 3f49742a..10da51cb 100644 --- a/packages/java-parser/api.d.ts +++ b/packages/java-parser/api.d.ts @@ -270,6 +270,8 @@ export abstract class JavaCstVisitor implements ICstVisitor { switchBlock(ctx: SwitchBlockCtx, param?: IN): OUT; switchBlockStatementGroup(ctx: SwitchBlockStatementGroupCtx, param?: IN): OUT; switchLabel(ctx: SwitchLabelCtx, param?: IN): OUT; + caseOrDefaultLabel(ctx: CaseOrDefaultLabelCtx, param?: IN): OUT; + caseLabelElement(ctx: CaseLabelElementCtx, param?: IN): OUT; switchRule(ctx: SwitchRuleCtx, param?: IN): OUT; caseConstant(ctx: CaseConstantCtx, param?: IN): OUT; whileStatement(ctx: WhileStatementCtx, param?: IN): OUT; @@ -2619,7 +2621,7 @@ export interface SwitchBlockStatementGroupCstNode extends CstNode { export type SwitchBlockStatementGroupCtx = { switchLabel: SwitchLabelCstNode[]; Colon: IToken[]; - blockStatements?: BlockStatementsCstNode[]; + blockStatements: BlockStatementsCstNode[]; }; export interface SwitchLabelCstNode extends CstNode { @@ -2628,12 +2630,34 @@ export interface SwitchLabelCstNode extends CstNode { } export type SwitchLabelCtx = { + caseOrDefaultLabel: CaseOrDefaultLabelCstNode[]; + Colon?: IToken[]; +}; + +export interface CaseOrDefaultLabelCstNode extends CstNode { + name: "caseOrDefaultLabel"; + children: CaseOrDefaultLabelCtx; +} + +export type CaseOrDefaultLabelCtx = { Case?: IToken[]; - caseConstant?: CaseConstantCstNode[]; + caseLabelElement?: CaseLabelElementCstNode[]; Comma?: IToken[]; Default?: IToken[]; }; +export interface CaseLabelElementCstNode extends CstNode { + name: "caseLabelElement"; + children: CaseLabelElementCtx; +} + +export type CaseLabelElementCtx = { + caseConstant?: CaseConstantCstNode[]; + pattern?: PatternCstNode[]; + Null?: IToken[]; + Default?: IToken[]; +}; + export interface SwitchRuleCstNode extends CstNode { name: "switchRule"; children: SwitchRuleCtx; diff --git a/packages/java-parser/src/productions/blocks-and-statements.js b/packages/java-parser/src/productions/blocks-and-statements.js index 2d41651f..d19f00b9 100644 --- a/packages/java-parser/src/productions/blocks-and-statements.js +++ b/packages/java-parser/src/productions/blocks-and-statements.js @@ -200,37 +200,61 @@ function defineRules($, t) { }); $.RULE("switchBlockStatementGroup", () => { - $.AT_LEAST_ONE(() => { - $.SUBRULE($.switchLabel); - $.CONSUME(t.Colon); - }); + $.SUBRULE($.switchLabel); + $.CONSUME(t.Colon); $.OPTION(() => { $.SUBRULE($.blockStatements); }); }); - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-SwitchLabel $.RULE("switchLabel", () => { + $.SUBRULE($.caseOrDefaultLabel); + $.MANY({ + GATE: () => + tokenMatcher($.LA(1).tokenType, t.Colon) && + (tokenMatcher($.LA(2).tokenType, t.Case) || + tokenMatcher($.LA(2).tokenType, t.Default)), + DEF: () => { + $.CONSUME(t.Colon); + $.SUBRULE2($.caseOrDefaultLabel); + } + }); + }); + + // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-SwitchLabel + $.RULE("caseOrDefaultLabel", () => { $.OR([ { ALT: () => { $.CONSUME(t.Case); - $.SUBRULE($.caseConstant); + $.SUBRULE($.caseLabelElement); $.MANY(() => { $.CONSUME(t.Comma); - $.SUBRULE2($.caseConstant); + $.SUBRULE2($.caseLabelElement); }); } }, - // SPEC Deviation: the variant with "enumConstantName" was removed - // as it can be matched by the "constantExpression" variant - // the distinction is semantic not syntactic. { ALT: () => $.CONSUME(t.Default) } ]); }); + $.RULE("caseLabelElement", () => { + $.OR([ + { ALT: () => $.CONSUME(t.Null) }, + { ALT: () => $.CONSUME(t.Default) }, + { + GATE: () => this.BACKTRACK_LOOKAHEAD($.pattern), + ALT: () => $.SUBRULE($.pattern) + }, + { + GATE: () => tokenMatcher($.LA(1).tokenType, t.Null) === false, + ALT: () => $.SUBRULE($.caseConstant) + } + ]); + }); + // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-SwitchRule $.RULE("switchRule", () => { $.SUBRULE($.switchLabel); diff --git a/packages/java-parser/test/blocks-and-statements/switch-case-spec.js b/packages/java-parser/test/blocks-and-statements/switch-case-spec.js index 6da3f9fb..65965dee 100644 --- a/packages/java-parser/test/blocks-and-statements/switch-case-spec.js +++ b/packages/java-parser/test/blocks-and-statements/switch-case-spec.js @@ -39,4 +39,38 @@ describe("Switch cases", () => { }`; expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); }); + + it("should handle pattern matching in switch rules", () => { + const input = `switch (o) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + case TOTO -> String.format("TOTO %s", o); + case null -> String.format("Null !"); + case default -> String.format("Default !"); + default -> o.toString(); + }`; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); + + it("should handle pattern matching in classic switch cases", () => { + const input = `switch (o) { + case Integer i : + yield "It is an integer"; + case Double d : + case String s: + yield "It is an something else"; + }`; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); + + it("should handle switch cases with empty blocks at the end", () => { + const input = `switch (o) { + case String s: + yield "It is an something"; + default: + }`; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); }); diff --git a/packages/prettier-plugin-java/src/options.js b/packages/prettier-plugin-java/src/options.js index 4cef7702..dab8a834 100644 --- a/packages/prettier-plugin-java/src/options.js +++ b/packages/prettier-plugin-java/src/options.js @@ -27,6 +27,8 @@ module.exports = { { value: "switchBlock" }, { value: "switchBlockStatementGroup" }, { value: "switchLabel" }, + { value: "caseOrDefaultLabel" }, + { value: "caseLabelElement" }, { value: "switchRule" }, { value: "caseConstant" }, { value: "whileStatement" }, diff --git a/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts b/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts index 6adbff48..eb84349c 100644 --- a/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts +++ b/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts @@ -27,6 +27,8 @@ import { BlockStatementsCtx, BreakStatementCtx, CaseConstantCtx, + CaseLabelElementCtx, + CaseOrDefaultLabelCtx, CatchClauseCtx, CatchesCtx, CatchFormalParameterCtx, @@ -264,26 +266,32 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { } switchBlockStatementGroup(ctx: SwitchBlockStatementGroupCtx) { - const switchLabels = this.mapVisit(ctx.switchLabel); - - const labels = []; - for (let i = 0; i < switchLabels.length; i++) { - labels.push(concat([switchLabels[i], ctx.Colon[i]])); - } + const switchLabel = this.visit(ctx.switchLabel); const blockStatements = this.visit(ctx.blockStatements); - return indent( - rejectAndJoin(hardline, [ - dedent(rejectAndJoin(hardline, labels)), - blockStatements - ]) - ); + return concat([ + switchLabel, + ctx.Colon[0], + blockStatements && indent([hardline, blockStatements]) + ]); } switchLabel(ctx: SwitchLabelCtx) { + const caseOrDefaultLabels = this.mapVisit(ctx.caseOrDefaultLabel); + + const colons = ctx.Colon + ? ctx.Colon.map(elt => { + return concat([elt, hardline]); + }) + : []; + + return group(rejectAndJoinSeps(colons, caseOrDefaultLabels)); + } + + caseOrDefaultLabel(ctx: CaseOrDefaultLabelCtx) { if (ctx.Case) { - const caseConstants = this.mapVisit(ctx.caseConstant); + const caseLabelElements = this.mapVisit(ctx.caseLabelElement); const commas = ctx.Comma ? ctx.Comma.map(elt => { @@ -295,7 +303,7 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { indent( rejectAndConcat([ concat([ctx.Case[0], " "]), - rejectAndJoinSeps(commas, caseConstants) + rejectAndJoinSeps(commas, caseLabelElements) ]) ) ); @@ -304,6 +312,13 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { return concat([ctx.Default![0]]); } + caseLabelElement(ctx: CaseLabelElementCtx) { + if (ctx.Default || ctx.Null) { + return this.getSingle(ctx); + } + return this.visitSingle(ctx); + } + switchRule(ctx: SwitchRuleCtx) { const switchLabel = this.visit(ctx.switchLabel); diff --git a/packages/prettier-plugin-java/test/unit-test/switch/_input.java b/packages/prettier-plugin-java/test/unit-test/switch/_input.java index 20f03b0a..637949be 100644 --- a/packages/prettier-plugin-java/test/unit-test/switch/_input.java +++ b/packages/prettier-plugin-java/test/unit-test/switch/_input.java @@ -93,4 +93,47 @@ public void caseConstantsWithComments(TestEnum testEnum) { } } + + static String formatterPatternSwitchRules(Object o) { + return switch (o) { + case + + Integer i -> + + + String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + case TOTO -> String.format("TOTO %s", o); + case null -> String.format("Null !"); + case default -> String.format("Default !"); + default -> o.toString(); + }; + } + + static String formatterPatternSwitch(Object o) { + return switch (o) { + case Integer i : + yield "It is an integer"; + case Double d : + case String s: + yield "It is an integer"; + }; + } + + static String shouldFormatSwitchBlocksWithEmptyLastBlock(Object o) { + switch (state) { + case READY: + return true; + case DONE: + return false; + default: + + + } + + log.info("Done !"); + + } } diff --git a/packages/prettier-plugin-java/test/unit-test/switch/_output.java b/packages/prettier-plugin-java/test/unit-test/switch/_output.java index 4603c1fb..2af1d6ec 100644 --- a/packages/prettier-plugin-java/test/unit-test/switch/_output.java +++ b/packages/prettier-plugin-java/test/unit-test/switch/_output.java @@ -116,4 +116,39 @@ public void caseConstantsWithComments(TestEnum testEnum) { case BAR, /* bar */BAZ -> System.out.println("Not Foo!"); } } + + static String formatterPatternSwitchRules(Object o) { + return switch (o) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + case TOTO -> String.format("TOTO %s", o); + case null -> String.format("Null !"); + case default -> String.format("Default !"); + default -> o.toString(); + }; + } + + static String formatterPatternSwitch(Object o) { + return switch (o) { + case Integer i: + yield "It is an integer"; + case Double d: + case String s: + yield "It is an integer"; + }; + } + + static String shouldFormatSwitchBlocksWithEmptyLastBlock(Object o) { + switch (state) { + case READY: + return true; + case DONE: + return false; + default: + } + + log.info("Done !"); + } }