From 96786409027fb43f30981a741ec709d68438b3c6 Mon Sep 17 00:00:00 2001 From: Jordan Kiesel Date: Sun, 12 Nov 2023 19:31:13 -0700 Subject: [PATCH] feat: JDK 22 unnamed variables & patterns closes #612 --- packages/java-parser/api.d.ts | 84 +++++------ .../src/productions/blocks-and-statements.js | 98 +++++------- .../java-parser/src/productions/classes.js | 17 ++- .../src/productions/expressions.js | 25 +++- .../blocks-and-statements/switch-case-spec.js | 2 +- .../pattern-matching/pattern-matching-spec.js | 4 +- .../unnamed-variables-and-patterns-spec.js | 139 ++++++++++++++++++ packages/prettier-plugin-java/src/options.js | 7 +- .../src/printers/blocks-and-statements.ts | 77 +++------- .../src/printers/classes.ts | 5 +- .../src/printers/expressions.ts | 29 +++- .../test/unit-test/switch/_input.java | 2 +- .../test/unit-test/switch/_output.java | 2 +- .../_input.java | 107 ++++++++++++++ .../_output.java | 123 ++++++++++++++++ .../unnamed-variables-and-patterns-spec.ts | 5 + 16 files changed, 535 insertions(+), 191 deletions(-) create mode 100644 packages/java-parser/test/pattern-matching/unnamed-variables-and-patterns-spec.js create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_input.java create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_output.java create mode 100644 packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/unnamed-variables-and-patterns-spec.ts diff --git a/packages/java-parser/api.d.ts b/packages/java-parser/api.d.ts index 81c689666..446326e58 100644 --- a/packages/java-parser/api.d.ts +++ b/packages/java-parser/api.d.ts @@ -270,8 +270,6 @@ 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; @@ -297,7 +295,6 @@ export abstract class JavaCstVisitor implements ICstVisitor { resourceSpecification(ctx: ResourceSpecificationCtx, param?: IN): OUT; resourceList(ctx: ResourceListCtx, param?: IN): OUT; resource(ctx: ResourceCtx, param?: IN): OUT; - resourceInit(ctx: ResourceInitCtx, param?: IN): OUT; yieldStatement(ctx: YieldStatementCtx, param?: IN): OUT; variableAccess(ctx: VariableAccessCtx, param?: IN): OUT; isBasicForStatement(ctx: IsBasicForStatementCtx, param?: IN): OUT; @@ -377,7 +374,9 @@ export abstract class JavaCstVisitor implements ICstVisitor { pattern(ctx: PatternCtx, param?: IN): OUT; typePattern(ctx: TypePatternCtx, param?: IN): OUT; recordPattern(ctx: RecordPatternCtx, param?: IN): OUT; - patternList(ctx: PatternListCtx, param?: IN): OUT; + componentPatternList(ctx: ComponentPatternListCtx, param?: IN): OUT; + componentPattern(ctx: ComponentPatternCtx, param?: IN): OUT; + unnamedPattern(ctx: UnnamedPatternCtx, param?: IN): OUT; guard(ctx: GuardCtx, param?: IN): OUT; identifyNewExpressionType(ctx: IdentifyNewExpressionTypeCtx, param?: IN): OUT; isLambdaExpression(ctx: IsLambdaExpressionCtx, param?: IN): OUT; @@ -628,8 +627,6 @@ export abstract class JavaCstVisitorWithDefaults 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; @@ -655,7 +652,6 @@ export abstract class JavaCstVisitorWithDefaults resourceSpecification(ctx: ResourceSpecificationCtx, param?: IN): OUT; resourceList(ctx: ResourceListCtx, param?: IN): OUT; resource(ctx: ResourceCtx, param?: IN): OUT; - resourceInit(ctx: ResourceInitCtx, param?: IN): OUT; yieldStatement(ctx: YieldStatementCtx, param?: IN): OUT; variableAccess(ctx: VariableAccessCtx, param?: IN): OUT; isBasicForStatement(ctx: IsBasicForStatementCtx, param?: IN): OUT; @@ -735,7 +731,9 @@ export abstract class JavaCstVisitorWithDefaults pattern(ctx: PatternCtx, param?: IN): OUT; typePattern(ctx: TypePatternCtx, param?: IN): OUT; recordPattern(ctx: RecordPatternCtx, param?: IN): OUT; - patternList(ctx: PatternListCtx, param?: IN): OUT; + componentPatternList(ctx: ComponentPatternListCtx, param?: IN): OUT; + componentPattern(ctx: ComponentPatternCtx, param?: IN): OUT; + unnamedPattern(ctx: UnnamedPatternCtx, param?: IN): OUT; guard(ctx: GuardCtx, param?: IN): OUT; identifyNewExpressionType(ctx: IdentifyNewExpressionTypeCtx, param?: IN): OUT; isLambdaExpression(ctx: IsLambdaExpressionCtx, param?: IN): OUT; @@ -1275,8 +1273,9 @@ export interface VariableDeclaratorIdCstNode extends CstNode { } export type VariableDeclaratorIdCtx = { - Identifier: IToken[]; + Identifier?: IToken[]; dims?: DimsCstNode[]; + Underscore?: IToken[]; }; export interface VariableInitializerCstNode extends CstNode { @@ -2644,28 +2643,8 @@ 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[]; - caseLabelElement?: CaseLabelElementCstNode[]; Comma?: IToken[]; - Default?: IToken[]; -}; - -export interface CaseLabelElementCstNode extends CstNode { - name: "caseLabelElement"; - children: CaseLabelElementCtx; -} - -export type CaseLabelElementCtx = { Null?: IToken[]; Default?: IToken[]; pattern?: PatternCstNode[]; @@ -2961,23 +2940,10 @@ export interface ResourceCstNode extends CstNode { } export type ResourceCtx = { - resourceInit?: ResourceInitCstNode[]; + localVariableDeclaration?: LocalVariableDeclarationCstNode[]; variableAccess?: VariableAccessCstNode[]; }; -export interface ResourceInitCstNode extends CstNode { - name: "resourceInit"; - children: ResourceInitCtx; -} - -export type ResourceInitCtx = { - variableModifier?: VariableModifierCstNode[]; - localVariableType: LocalVariableTypeCstNode[]; - Identifier: IToken[]; - Equals: IToken[]; - expression: ExpressionCstNode[]; -}; - export interface YieldStatementCstNode extends CstNode { name: "yieldStatement"; children: YieldStatementCtx; @@ -3060,6 +3026,7 @@ export interface LambdaParametersCstNode extends CstNode { export type LambdaParametersCtx = { lambdaParametersWithBraces?: LambdaParametersWithBracesCstNode[]; Identifier?: IToken[]; + Underscore?: IToken[]; }; export interface LambdaParametersWithBracesCstNode extends CstNode { @@ -3525,20 +3492,39 @@ export interface RecordPatternCstNode extends CstNode { export type RecordPatternCtx = { referenceType: ReferenceTypeCstNode[]; LBrace: IToken[]; - patternList?: PatternListCstNode[]; + componentPatternList?: ComponentPatternListCstNode[]; RBrace: IToken[]; }; -export interface PatternListCstNode extends CstNode { - name: "patternList"; - children: PatternListCtx; +export interface ComponentPatternListCstNode extends CstNode { + name: "componentPatternList"; + children: ComponentPatternListCtx; } -export type PatternListCtx = { - pattern: PatternCstNode[]; +export type ComponentPatternListCtx = { + componentPattern: ComponentPatternCstNode[]; Comma?: IToken[]; }; +export interface ComponentPatternCstNode extends CstNode { + name: "componentPattern"; + children: ComponentPatternCtx; +} + +export type ComponentPatternCtx = { + pattern?: PatternCstNode[]; + unnamedPattern?: UnnamedPatternCstNode[]; +}; + +export interface UnnamedPatternCstNode extends CstNode { + name: "unnamedPattern"; + children: UnnamedPatternCtx; +} + +export type UnnamedPatternCtx = { + Underscore: IToken[]; +}; + export interface GuardCstNode extends CstNode { name: "guard"; children: GuardCtx; diff --git a/packages/java-parser/src/productions/blocks-and-statements.js b/packages/java-parser/src/productions/blocks-and-statements.js index 7f72bea1a..fc1be9543 100644 --- a/packages/java-parser/src/productions/blocks-and-statements.js +++ b/packages/java-parser/src/productions/blocks-and-statements.js @@ -207,56 +207,49 @@ function defineRules($, t) { }); }); + // https://docs.oracle.com/javase/specs/jls/se21/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($.caseLabelElement); - $.MANY(() => { - $.CONSUME(t.Comma); - $.SUBRULE2($.caseLabelElement); - }); - } - }, - { - ALT: () => $.CONSUME(t.Default) - } - ]); - }); - - $.RULE("caseLabelElement", () => { - $.OR([ - { ALT: () => $.CONSUME(t.Null) }, - { ALT: () => $.CONSUME(t.Default) }, - { - GATE: () => this.BACKTRACK_LOOKAHEAD($.pattern), - ALT: () => { - $.SUBRULE($.pattern); - $.OPTION(() => { - $.SUBRULE($.guard); - }); + $.OR2([ + { + ALT: () => { + $.CONSUME(t.Null); + $.OPTION2(() => { + $.CONSUME3(t.Comma); + $.CONSUME(t.Default); + }); + } + }, + { + GATE: () => this.BACKTRACK_LOOKAHEAD($.pattern), + ALT: () => { + $.SUBRULE($.pattern); + $.MANY(() => { + $.CONSUME(t.Comma); + $.SUBRULE2($.pattern); + }); + $.OPTION(() => { + $.SUBRULE($.guard); + }); + } + }, + { + GATE: () => !tokenMatcher($.LA(1).tokenType, t.Null), + ALT: () => { + $.SUBRULE($.caseConstant); + $.MANY2(() => { + $.CONSUME2(t.Comma); + $.SUBRULE2($.caseConstant); + }); + } + } + ]); } }, - { - GATE: () => tokenMatcher($.LA(1).tokenType, t.Null) === false, - ALT: () => $.SUBRULE($.caseConstant) - } + { ALT: () => $.CONSUME2(t.Default) } ]); }); @@ -516,30 +509,17 @@ function defineRules($, t) { }); }); - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-Resource + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-Resource $.RULE("resource", () => { $.OR([ { - GATE: $.BACKTRACK($.resourceInit), - // Spec Deviation: extracted this alternative to "resourceInit" - // to enable backtracking. - ALT: () => $.SUBRULE($.resourceInit) + GATE: () => $.BACKTRACK_LOOKAHEAD($.isLocalVariableDeclaration), + ALT: () => $.SUBRULE($.localVariableDeclaration) }, { ALT: () => $.SUBRULE($.variableAccess) } ]); }); - // Spec Deviation: extracted from "resource" - $.RULE("resourceInit", () => { - $.MANY(() => { - $.SUBRULE($.variableModifier); - }); - $.SUBRULE($.localVariableType); - $.CONSUME(t.Identifier); - $.CONSUME(t.Equals); - $.SUBRULE($.expression); - }); - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-14.html#jls-YieldStatement $.RULE("yieldStatement", () => { $.CONSUME(t.Yield); diff --git a/packages/java-parser/src/productions/classes.js b/packages/java-parser/src/productions/classes.js index 0286bc351..333864025 100644 --- a/packages/java-parser/src/productions/classes.js +++ b/packages/java-parser/src/productions/classes.js @@ -210,12 +210,19 @@ function defineRules($, t) { }); }); - // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableDeclaratorId + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html#jls-VariableDeclaratorId $.RULE("variableDeclaratorId", () => { - $.CONSUME(t.Identifier); - $.OPTION(() => { - $.SUBRULE($.dims); - }); + $.OR([ + { + ALT: () => { + $.CONSUME(t.Identifier); + $.OPTION(() => { + $.SUBRULE($.dims); + }); + } + }, + { ALT: () => $.CONSUME(t.Underscore) } + ]); }); // https://docs.oracle.com/javase/specs/jls/se16/html/jls-8.html#jls-VariableInitializer diff --git a/packages/java-parser/src/productions/expressions.js b/packages/java-parser/src/productions/expressions.js index de43c3055..6ad01dd34 100644 --- a/packages/java-parser/src/productions/expressions.js +++ b/packages/java-parser/src/productions/expressions.js @@ -21,7 +21,8 @@ function defineRules($, t) { $.RULE("lambdaParameters", () => { $.OR([ { ALT: () => $.SUBRULE($.lambdaParametersWithBraces) }, - { ALT: () => $.CONSUME(t.Identifier) } + { ALT: () => $.CONSUME(t.Identifier) }, + { ALT: () => $.CONSUME(t.Underscore) } ]); }); @@ -584,20 +585,31 @@ function defineRules($, t) { $.SUBRULE($.referenceType); $.CONSUME(t.LBrace); $.OPTION(() => { - $.SUBRULE($.patternList); + $.SUBRULE($.componentPatternList); }); $.CONSUME(t.RBrace); }); // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-PatternList - $.RULE("patternList", () => { - $.SUBRULE($.pattern); + $.RULE("componentPatternList", () => { + $.SUBRULE($.componentPattern); $.MANY(() => { $.CONSUME(t.Comma); - $.SUBRULE2($.pattern); + $.SUBRULE2($.componentPattern); }); }); + $.RULE("componentPattern", () => { + $.OR([ + { ALT: () => $.SUBRULE($.pattern) }, + { ALT: () => $.SUBRULE($.unnamedPattern) } + ]); + }); + + $.RULE("unnamedPattern", () => { + $.CONSUME(t.Underscore); + }); + // https://docs.oracle.com/javase/specs/jls/se21/html/jls-14.html#jls-Guard $.RULE("guard", () => { $.CONSUME(t.When); @@ -643,7 +655,8 @@ function defineRules($, t) { const secondTokenType = this.LA(2).tokenType; // no parent lambda "x -> x * 2" if ( - tokenMatcher(firstTokenType, t.Identifier) && + (tokenMatcher(firstTokenType, t.Identifier) || + tokenMatcher(firstTokenType, t.Underscore)) && tokenMatcher(secondTokenType, t.Arrow) ) { return true; 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 65965deeb..4578806a3 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 @@ -48,7 +48,7 @@ describe("Switch cases", () => { 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 !"); + case null, default -> String.format("Default !"); default -> o.toString(); }`; expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); diff --git a/packages/java-parser/test/pattern-matching/pattern-matching-spec.js b/packages/java-parser/test/pattern-matching/pattern-matching-spec.js index a92d5c1b3..0fed032fd 100644 --- a/packages/java-parser/test/pattern-matching/pattern-matching-spec.js +++ b/packages/java-parser/test/pattern-matching/pattern-matching-spec.js @@ -52,6 +52,8 @@ describe("Pattern matching", () => { it("should parse pattern list", () => { const input = `A a, B b`; - expect(() => javaParser.parse(input, "patternList")).to.not.throw(); + expect(() => + javaParser.parse(input, "componentPatternList") + ).to.not.throw(); }); }); diff --git a/packages/java-parser/test/pattern-matching/unnamed-variables-and-patterns-spec.js b/packages/java-parser/test/pattern-matching/unnamed-variables-and-patterns-spec.js new file mode 100644 index 000000000..ae24074ad --- /dev/null +++ b/packages/java-parser/test/pattern-matching/unnamed-variables-and-patterns-spec.js @@ -0,0 +1,139 @@ +"use strict"; + +const { expect } = require("chai"); +const javaParser = require("../../src/index"); + +describe("Unnamed Variables & Patterns", () => { + it("should parse enhanced for loop with unnamed variable", () => { + const input = ` + for (Order _ : orders) {} + `; + expect(() => javaParser.parse(input, "forStatement")).to.not.throw(); + }); + + it("should parse simple for loop with unnamed variable", () => { + const input = ` + for (int i = 0, _ = sideEffect(); i < 10; i++) {} + `; + expect(() => javaParser.parse(input, "forStatement")).to.not.throw(); + }); + + it("should parse unnamed variable assignment", () => { + const input = ` + var _ = q.remove(); + `; + expect(() => + javaParser.parse(input, "localVariableDeclarationStatement") + ).to.not.throw(); + }); + + it("should parse multiple unnamed variable assignments", () => { + const input = ` + var _ = q.remove(); + var _ = q.remove(); + `; + expect(() => javaParser.parse(input, "blockStatements")).to.not.throw(); + }); + + it("should parse unnamed caught exception", () => { + const input = ` + try { + int i = Integer.parseInt(s); + } catch (NumberFormatException _) { + System.out.println("Bad number: " + s); + } + `; + expect(() => javaParser.parse(input, "tryStatement")).to.not.throw(); + }); + + it("should parse multiple unnamed caught exceptions", () => { + const input = ` + try {} + catch (Exception _) {} + catch (Throwable _) {} + `; + expect(() => javaParser.parse(input, "tryStatement")).to.not.throw(); + }); + + it("should parse try-with-resources with unnamed resource", () => { + const input = ` + try (var _ = ScopedContext.acquire()) {} + `; + expect(() => + javaParser.parse(input, "tryWithResourcesStatement") + ).to.not.throw(); + }); + + it("should parse lambda with unnamed parameter", () => { + const input = ` + stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")) + `; + expect(() => javaParser.parse(input, "primary")).to.not.throw(); + }); + + it("should parse switch labels with unnamed pattern variables", () => { + const input = ` + switch (ball) { + case RedBall _ -> process(ball); + case BlueBall _ -> process(ball); + case GreenBall _ -> stopProcessing(); + } + `; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); + + it("should parse switch labels with unnamed variables in record patterns", () => { + const input = ` + switch (box) { + case Box(RedBall _) -> processBox(box); + case Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(var _) -> pickAnotherBox(); + } + `; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); + + it("should parse switch label with multiple patterns", () => { + const input = ` + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(var _) -> pickAnotherBox(); + } + `; + expect(() => javaParser.parse(input, "switchStatement")).to.not.throw(); + }); + + it("should parse switch label with multiple patterns and guard", () => { + const input = ` + case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(b); + `; + expect(() => javaParser.parse(input, "switchRule")).to.not.throw(); + }); + + it("should not parse switch label with multiple guards", () => { + const input = ` + case Box(RedBall _) when x == 0, Box(BlueBall _) when x == 42 -> processBox(b); + `; + expect(() => javaParser.parse(input, "switchRule")).to.throw(); + }); + + it("should parse instanceof expression with unnamed pattern", () => { + const input = ` + if (r instanceof ColoredPoint(Point(int x, int y), _)) {} + `; + expect(() => javaParser.parse(input, "ifStatement")).to.not.throw(); + }); + + it("should parse switch label with unnamed pattern", () => { + const input = ` + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(_) -> pickAnotherBox(); + } + `; + 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 6cf1862a5..794f782c0 100644 --- a/packages/prettier-plugin-java/src/options.js +++ b/packages/prettier-plugin-java/src/options.js @@ -27,8 +27,6 @@ module.exports = { { value: "switchBlock" }, { value: "switchBlockStatementGroup" }, { value: "switchLabel" }, - { value: "caseOrDefaultLabel" }, - { value: "caseLabelElement" }, { value: "switchRule" }, { value: "caseConstant" }, { value: "whileStatement" }, @@ -54,7 +52,6 @@ module.exports = { { value: "resourceSpecification" }, { value: "resourceList" }, { value: "resource" }, - { value: "resourceInit" }, { value: "yieldStatement" }, { value: "variableAccess" }, { value: "isBasicForStatement" }, @@ -174,7 +171,9 @@ module.exports = { { value: "pattern" }, { value: "typePattern" }, { value: "recordPattern" }, - { value: "patternList" }, + { value: "componentPatternList" }, + { value: "componentPattern" }, + { value: "unnamedPattern" }, { value: "guard" }, { value: "identifyNewExpressionType" }, { value: "isLambdaExpression" }, 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 0ad4b8e6c..747fceb3b 100644 --- a/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts +++ b/packages/prettier-plugin-java/src/printers/blocks-and-statements.ts @@ -27,8 +27,6 @@ import { BlockStatementsCtx, BreakStatementCtx, CaseConstantCtx, - CaseLabelElementCtx, - CaseOrDefaultLabelCtx, CatchClauseCtx, CatchesCtx, CatchFormalParameterCtx, @@ -49,7 +47,6 @@ import { LocalVariableDeclarationStatementCtx, LocalVariableTypeCtx, ResourceCtx, - ResourceInitCtx, ResourceListCtx, ResourceSpecificationCtx, ReturnStatementCtx, @@ -279,49 +276,32 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { } 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 caseLabelElements = this.mapVisit(ctx.caseLabelElement); - - const commas = ctx.Comma - ? ctx.Comma.map(elt => { - return concat([elt, line]); - }) - : []; - + const Case = ctx.Case?.[0]; + const commas = ctx.Comma?.map(elt => concat([elt, line])); + if (ctx.caseConstant || ctx.Null) { + const caseConstants = ctx.Null + ? [ctx.Null[0], ctx.Default?.[0]] + : this.mapVisit(ctx.caseConstant); return group( - indent( - rejectAndConcat([ - concat([ctx.Case[0], " "]), - rejectAndJoinSeps(commas, caseLabelElements) - ]) - ) + indent(join(" ", [Case!, rejectAndJoinSeps(commas, caseConstants)])) ); - } - - return concat([ctx.Default![0]]); - } - - caseLabelElement(ctx: CaseLabelElementCtx) { - if (ctx.Default || ctx.Null) { - return this.getSingle(ctx); } else if (ctx.pattern) { - const pattern = this.visit(ctx.pattern); + const patterns = this.mapVisit(ctx.pattern); const guard = this.visit(ctx.guard); - return rejectAndJoin(" ", [dedent(pattern), guard]); + const multiplePatterns = ctx.pattern.length > 1; + const separator = multiplePatterns ? line : " "; + const contents = join(separator, [ + Case!, + rejectAndJoinSeps(commas, patterns) + ]); + return group( + rejectAndJoin(separator, [ + multiplePatterns ? indent(contents) : contents, + guard + ]) + ); } - return this.visit(ctx.caseConstant); + return printTokenWithComments(ctx.Default![0]); } switchRule(ctx: SwitchRuleCtx) { @@ -621,21 +601,6 @@ export class BlocksAndStatementPrettierVisitor extends BaseCstPrettierPrinter { return this.visitSingle(ctx); } - resourceInit(ctx: ResourceInitCtx) { - const variableModifiers = this.mapVisit(ctx.variableModifier); - const localVariableType = this.visit(ctx.localVariableType); - const identifier = ctx.Identifier[0]; - const expression = this.visit(ctx.expression); - - return rejectAndJoin(" ", [ - rejectAndJoin(" ", variableModifiers), - localVariableType, - identifier, - ctx.Equals[0], - expression - ]); - } - yieldStatement(ctx: YieldStatementCtx) { const expression = this.visit(ctx.expression); return join(" ", [ctx.Yield[0], concat([expression, ctx.Semicolon[0]])]); diff --git a/packages/prettier-plugin-java/src/printers/classes.ts b/packages/prettier-plugin-java/src/printers/classes.ts index 6ea8792d1..979f50a92 100644 --- a/packages/prettier-plugin-java/src/printers/classes.ts +++ b/packages/prettier-plugin-java/src/printers/classes.ts @@ -422,7 +422,10 @@ export class ClassesPrettierVisitor extends BaseCstPrettierPrinter { } variableDeclaratorId(ctx: VariableDeclaratorIdCtx) { - const identifier = ctx.Identifier[0]; + if (ctx.Underscore) { + return printTokenWithComments(ctx.Underscore[0]); + } + const identifier = ctx.Identifier![0]; const dims = this.visit(ctx.dims); return rejectAndConcat([identifier, dims]); diff --git a/packages/prettier-plugin-java/src/printers/expressions.ts b/packages/prettier-plugin-java/src/printers/expressions.ts index 724ac1570..33bc810f7 100644 --- a/packages/prettier-plugin-java/src/printers/expressions.ts +++ b/packages/prettier-plugin-java/src/printers/expressions.ts @@ -10,6 +10,8 @@ import { CastExpressionCtx, ClassLiteralSuffixCtx, ClassOrInterfaceTypeToInstantiateCtx, + ComponentPatternListCtx, + ComponentPatternCtx, DiamondCtx, DimExprCtx, DimExprsCtx, @@ -34,7 +36,6 @@ import { NewExpressionCtx, ParenthesisExpressionCtx, PatternCtx, - PatternListCtx, PrimaryCtx, PrimaryPrefixCtx, PrimarySuffixCtx, @@ -47,6 +48,7 @@ import { TypePatternCtx, UnaryExpressionCtx, UnaryExpressionNotPlusMinusCtx, + UnnamedPatternCtx, UnqualifiedClassInstanceCreationExpressionCtx } from "java-parser/api"; @@ -726,17 +728,30 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter { recordPattern(ctx: RecordPatternCtx) { const referenceType = this.visit(ctx.referenceType); - const patternList = this.visit(ctx.patternList); + const componentPatternList = this.visit(ctx.componentPatternList); return concat([ referenceType, - putIntoBraces(patternList, softline, ctx.LBrace[0], ctx.RBrace[0]) + putIntoBraces( + componentPatternList, + softline, + ctx.LBrace[0], + ctx.RBrace[0] + ) ]); } - patternList(ctx: PatternListCtx) { - const patterns = this.mapVisit(ctx.pattern); + componentPatternList(ctx: ComponentPatternListCtx) { + const componentPatterns = this.mapVisit(ctx.componentPattern); const commas = ctx.Comma?.map(elt => concat([elt, line])) ?? []; - return rejectAndJoinSeps(commas, patterns); + return rejectAndJoinSeps(commas, componentPatterns); + } + + componentPattern(ctx: ComponentPatternCtx) { + return this.visitSingle(ctx); + } + + unnamedPattern(ctx: UnnamedPatternCtx) { + return printTokenWithComments(ctx.Underscore[0]); } guard(ctx: GuardCtx) { @@ -744,7 +759,7 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter { addParenthesisToWrapStatement: true }); - return concat([ctx.When[0], " ", dedent(expression)]); + return concat([ctx.When[0], " ", expression]); } identifyNewExpressionType() { 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 637949bef..20c279cd8 100644 --- a/packages/prettier-plugin-java/test/unit-test/switch/_input.java +++ b/packages/prettier-plugin-java/test/unit-test/switch/_input.java @@ -107,7 +107,7 @@ static String formatterPatternSwitchRules(Object o) { 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 !"); + case null, default -> String.format("Default !"); default -> o.toString(); }; } 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 2af1d6ec9..45868c7e3 100644 --- a/packages/prettier-plugin-java/test/unit-test/switch/_output.java +++ b/packages/prettier-plugin-java/test/unit-test/switch/_output.java @@ -125,7 +125,7 @@ static String formatterPatternSwitchRules(Object o) { 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 !"); + case null, default -> String.format("Default !"); default -> o.toString(); }; } diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_input.java b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_input.java new file mode 100644 index 000000000..c2ad37f29 --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_input.java @@ -0,0 +1,107 @@ +class T { + static int count(Iterable orders) { + int total = 0; + for (Order _ : orders) // Unnamed variable + total++; + return total; + } + + void simpleForLoop() { + for (int i = 0, _ = sideEffect(); i < 10; i++) {} + } + + void assignment() { + while (q.size() >= 3) { + var x = q.remove(); + var y = q.remove(); + var _ = q.remove(); // Unnamed variable + } + } + + void multipleAssignment() { + while (q.size() >= 3) { + var x = q.remove(); + var _ = q.remove(); // Unnamed variable + var _ = q.remove(); // Unnamed variable + } + } + + void catchClause() { + try { + int i = Integer.parseInt(s); + } catch (NumberFormatException _) { // Unnamed variable + System.out.println("Bad number: " + s); + } + } + + void multipleCatchClauses() { + try {} + catch (Exception _) {} // Unnamed variable + catch (Throwable _) {} // Unnamed variable + } + + void tryWithResources() { + try (var _ = ScopedContext.acquire()) { // Unnamed variable + } + } + + void lambda() { + stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")); // Unnamed variable + } + + void switchTypePattern() { + switch (ball) { + case RedBall _ -> process(ball); // Unnamed pattern variable + case BlueBall _ -> process(ball); // Unnamed pattern variable + case GreenBall _ -> stopProcessing(); // Unnamed pattern variable + } + } + + void switchRecordPattern() { + switch (box) { + case Box(RedBall _) -> processBox(box); // Unnamed pattern variable + case Box(BlueBall _) -> processBox(box); // Unnamed pattern variable + case Box(GreenBall _) -> stopProcessing(); // Unnamed pattern variable + case Box(var _) -> pickAnotherBox(); // Unnamed pattern variable + } + } + + void multipleSwitchPatterns() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(var _) -> pickAnotherBox(); + } + } + + void multipleSwitchPatternsWithGuard() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(b); + } + } + + void instanceofExpressions() { + if (r instanceof ColoredPoint(Point(int x, int y), _)) {} + if (r instanceof ColoredPoint(_, Color c)) {} + if (r instanceof ColoredPoint(Point(int x, _), _)) {} + } + + void switchLabelWithUnnamedPattern() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(_) -> pickAnotherBox(); + } + } + + int wrappingMultipleSwitchPatterns() { + return switch ("") { + case LongTypeName longVariableName, LongTypeName longVariableName -> 0; + case LongTypeName longVariableName, LongTypeName longVariableName, LongTypeName longVariableName -> 0; + case MyRecord(A a), MyRecord(B b) -> 0; + case MyRecord(A a), MyRecord(B b) when true -> 0; + case MyRecord(LongTypeName longVariableName, LongTypeName longVariableName), MyRecord(LongTypeName longVariableName, LongTypeName longVariableName) -> 0; + case MyRecord(LongTypeName longVariableName, LongTypeName longVariableName), MyRecord(LongTypeName longVariableName, LongTypeName longVariableName) when this.longVariableName > longVariableName && this.longVariableName > longVariableName -> longMethodName(longVariableName, longVariableName, longVariableName, longVariableName); + }; + } +} diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_output.java b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_output.java new file mode 100644 index 000000000..034842473 --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/_output.java @@ -0,0 +1,123 @@ +class T { + + static int count(Iterable orders) { + int total = 0; + for (Order _ : orders) total++; // Unnamed variable + return total; + } + + void simpleForLoop() { + for (int i = 0, _ = sideEffect(); i < 10; i++) {} + } + + void assignment() { + while (q.size() >= 3) { + var x = q.remove(); + var y = q.remove(); + var _ = q.remove(); // Unnamed variable + } + } + + void multipleAssignment() { + while (q.size() >= 3) { + var x = q.remove(); + var _ = q.remove(); // Unnamed variable + var _ = q.remove(); // Unnamed variable + } + } + + void catchClause() { + try { + int i = Integer.parseInt(s); + } catch (NumberFormatException _) { // Unnamed variable + System.out.println("Bad number: " + s); + } + } + + void multipleCatchClauses() { + try {} catch (Exception _) {} catch (Throwable _) {} // Unnamed variable // Unnamed variable + } + + void tryWithResources() { + try (var _ = ScopedContext.acquire()) {} // Unnamed variable + } + + void lambda() { + stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA")); // Unnamed variable + } + + void switchTypePattern() { + switch (ball) { + case RedBall _ -> process(ball); // Unnamed pattern variable + case BlueBall _ -> process(ball); // Unnamed pattern variable + case GreenBall _ -> stopProcessing(); // Unnamed pattern variable + } + } + + void switchRecordPattern() { + switch (box) { + case Box(RedBall _) -> processBox(box); // Unnamed pattern variable + case Box(BlueBall _) -> processBox(box); // Unnamed pattern variable + case Box(GreenBall _) -> stopProcessing(); // Unnamed pattern variable + case Box(var _) -> pickAnotherBox(); // Unnamed pattern variable + } + } + + void multipleSwitchPatterns() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(var _) -> pickAnotherBox(); + } + } + + void multipleSwitchPatternsWithGuard() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) when x == 42 -> processBox(b); + } + } + + void instanceofExpressions() { + if (r instanceof ColoredPoint(Point(int x, int y), _)) {} + if (r instanceof ColoredPoint(_, Color c)) {} + if (r instanceof ColoredPoint(Point(int x, _), _)) {} + } + + void switchLabelWithUnnamedPattern() { + switch (box) { + case Box(RedBall _), Box(BlueBall _) -> processBox(box); + case Box(GreenBall _) -> stopProcessing(); + case Box(_) -> pickAnotherBox(); + } + } + + int wrappingMultipleSwitchPatterns() { + return switch ("") { + case LongTypeName longVariableName, LongTypeName longVariableName -> 0; + case + LongTypeName longVariableName, + LongTypeName longVariableName, + LongTypeName longVariableName -> 0; + case MyRecord(A a), MyRecord(B b) -> 0; + case MyRecord(A a), MyRecord(B b) when true -> 0; + case + MyRecord(LongTypeName longVariableName, LongTypeName longVariableName), + MyRecord( + LongTypeName longVariableName, + LongTypeName longVariableName + ) -> 0; + case + MyRecord(LongTypeName longVariableName, LongTypeName longVariableName), + MyRecord(LongTypeName longVariableName, LongTypeName longVariableName) + when ( + this.longVariableName > longVariableName && + this.longVariableName > longVariableName + ) -> longMethodName( + longVariableName, + longVariableName, + longVariableName, + longVariableName + ); + }; + } +} diff --git a/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/unnamed-variables-and-patterns-spec.ts b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/unnamed-variables-and-patterns-spec.ts new file mode 100644 index 000000000..1ff2c3acc --- /dev/null +++ b/packages/prettier-plugin-java/test/unit-test/unnamed-variables-and-patterns/unnamed-variables-and-patterns-spec.ts @@ -0,0 +1,5 @@ +import { testSample } from "../../test-utils"; + +describe("prettier-java: unnamed variables & patterns", () => { + testSample(__dirname); +});