Skip to content

Commit

Permalink
feat: support pattern matching in switch statements
Browse files Browse the repository at this point in the history
  • Loading branch information
clementdessoude committed Oct 16, 2021
1 parent c2653b9 commit 2f7efa4
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 26 deletions.
28 changes: 26 additions & 2 deletions packages/java-parser/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ export abstract class JavaCstVisitor<IN, OUT> implements ICstVisitor<IN, OUT> {
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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
44 changes: 34 additions & 10 deletions packages/java-parser/src/productions/blocks-and-statements.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
2 changes: 2 additions & 0 deletions packages/prettier-plugin-java/src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ module.exports = {
{ value: "switchBlock" },
{ value: "switchBlockStatementGroup" },
{ value: "switchLabel" },
{ value: "caseOrDefaultLabel" },
{ value: "caseLabelElement" },
{ value: "switchRule" },
{ value: "caseConstant" },
{ value: "whileStatement" },
Expand Down
43 changes: 29 additions & 14 deletions packages/prettier-plugin-java/src/printers/blocks-and-statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
BlockStatementsCtx,
BreakStatementCtx,
CaseConstantCtx,
CaseLabelElementCtx,
CaseOrDefaultLabelCtx,
CatchClauseCtx,
CatchesCtx,
CatchFormalParameterCtx,
Expand Down Expand Up @@ -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 => {
Expand All @@ -295,7 +303,7 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter {
indent(
rejectAndConcat([
concat([ctx.Case[0], " "]),
rejectAndJoinSeps(commas, caseConstants)
rejectAndJoinSeps(commas, caseLabelElements)
])
)
);
Expand All @@ -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);

Expand Down
43 changes: 43 additions & 0 deletions packages/prettier-plugin-java/test/unit-test/switch/_input.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 !");

}
}
35 changes: 35 additions & 0 deletions packages/prettier-plugin-java/test/unit-test/switch/_output.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 !");
}
}

0 comments on commit 2f7efa4

Please sign in to comment.