Skip to content

Commit a8c27ee

Browse files
feat: improve lambda formatting
1 parent f453535 commit a8c27ee

File tree

4 files changed

+255
-66
lines changed

4 files changed

+255
-66
lines changed

packages/prettier-plugin-java/src/printers/expressions.ts

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -50,40 +50,62 @@ import {
5050
} from "java-parser/api";
5151

5252
import forEach from "lodash/forEach";
53-
import { concat, group, indent, dedent } from "./prettier-builder";
53+
import { concat, dedent, group, indent } from "./prettier-builder";
5454
import { printTokenWithComments } from "./comments/format-comments";
5555
import { handleCommentsBinaryExpression } from "./comments/handle-comments";
5656
import {
57-
matchCategory,
58-
rejectAndJoin,
59-
rejectAndConcat,
60-
sortAnnotationIdentifier,
61-
sortNodes,
62-
rejectAndJoinSeps,
6357
findDeepElementInPartsArray,
6458
isExplicitLambdaParameter,
59+
isShiftOperator,
60+
isUniqueMethodInvocation,
61+
matchCategory,
6562
putIntoBraces,
63+
rejectAndConcat,
64+
rejectAndJoin,
65+
rejectAndJoinSeps,
6666
separateTokensIntoGroups,
67-
isShiftOperator,
68-
isUniqueMethodInvocation
67+
sortAnnotationIdentifier,
68+
sortNodes
6969
} from "./printer-utils";
7070
import { builders } from "prettier/doc";
7171
import { Doc } from "prettier";
72-
import { isAnnotationCstNode, isCstNode } from "../types/utils";
73-
const { ifBreak, line, softline } = builders;
72+
import { isAnnotationCstNode } from "../types/utils";
73+
import {
74+
isArgumentListSingleLambda,
75+
isSingleArgumentLambdaExpressionWithBlock
76+
} from "../utils/expressions-utils";
77+
78+
const { ifBreak, line, softline, indentIfBreak } = builders;
7479

7580
export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter {
7681
expression(ctx: ExpressionCtx, params: any) {
7782
return this.visitSingle(ctx, params);
7883
}
7984

80-
lambdaExpression(ctx: LambdaExpressionCtx) {
81-
const lambdaParameters = this.visit(ctx.lambdaParameters);
85+
lambdaExpression(
86+
ctx: LambdaExpressionCtx,
87+
params?: {
88+
lambdaParametersGroupId: symbol;
89+
isInsideMethodInvocationSuffix: boolean;
90+
}
91+
) {
92+
const lambdaParameters = group(
93+
this.visit(ctx.lambdaParameters, params),
94+
params ? { id: params.lambdaParametersGroupId } : undefined
95+
);
8296
const lambdaBody = this.visit(ctx.lambdaBody);
8397

8498
const isLambdaBodyABlock = ctx.lambdaBody[0].children.block !== undefined;
8599
if (isLambdaBodyABlock) {
86-
return rejectAndJoin(" ", [lambdaParameters, ctx.Arrow[0], lambdaBody]);
100+
return rejectAndJoin(" ", [
101+
lambdaParameters,
102+
ctx.Arrow[0],
103+
params?.lambdaParametersGroupId !== undefined
104+
? indentIfBreak(lambdaBody, {
105+
groupId: params.lambdaParametersGroupId
106+
})
107+
: lambdaBody
108+
]);
87109
}
88110

89111
return group(
@@ -96,23 +118,35 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter {
96118
);
97119
}
98120

99-
lambdaParameters(ctx: LambdaParametersCtx) {
121+
lambdaParameters(
122+
ctx: LambdaParametersCtx,
123+
params?: { isInsideMethodInvocationSuffix: boolean }
124+
) {
100125
if (ctx.lambdaParametersWithBraces) {
101-
return this.visitSingle(ctx);
126+
return this.visitSingle(ctx, params);
102127
}
103128

104129
return printTokenWithComments(this.getSingle(ctx) as IToken);
105130
}
106131

107-
lambdaParametersWithBraces(ctx: LambdaParametersWithBracesCtx) {
132+
lambdaParametersWithBraces(
133+
ctx: LambdaParametersWithBracesCtx,
134+
params?: { isInsideMethodInvocationSuffix: boolean }
135+
) {
108136
const lambdaParameterList = this.visit(ctx.lambdaParameterList);
109137

110138
if (findDeepElementInPartsArray(lambdaParameterList, ",")) {
111-
return rejectAndConcat([
112-
ctx.LBrace[0],
139+
const content = putIntoBraces(
113140
lambdaParameterList,
141+
softline,
142+
ctx.LBrace[0],
114143
ctx.RBrace[0]
115-
]);
144+
);
145+
if (params?.isInsideMethodInvocationSuffix === true) {
146+
return indent(concat([softline, content]));
147+
}
148+
149+
return content;
116150
}
117151

118152
// removing braces when only no comments attached
@@ -142,7 +176,7 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter {
142176
inferredLambdaParameterList(ctx: InferredLambdaParameterListCtx) {
143177
const commas = ctx.Comma
144178
? ctx.Comma.map(elt => {
145-
return concat([elt, " "]);
179+
return concat([elt, line]);
146180
})
147181
: [];
148182

@@ -153,7 +187,7 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter {
153187
const lambdaParameter = this.mapVisit(ctx.lambdaParameter);
154188
const commas = ctx.Comma
155189
? ctx.Comma.map(elt => {
156-
return concat([elt, " "]);
190+
return concat([elt, line]);
157191
})
158192
: [];
159193
return rejectAndJoinSeps(commas, lambdaParameter);
@@ -642,17 +676,39 @@ export class ExpressionsPrettierVisitor extends BaseCstPrettierPrinter {
642676
}
643677

644678
methodInvocationSuffix(ctx: MethodInvocationSuffixCtx, params: any) {
645-
const argumentList = this.visit(ctx.argumentList);
679+
const lambdaParametersGroupId = Symbol("lambdaParameters");
680+
const argumentList = this.visit(ctx.argumentList, {
681+
lambdaParametersGroupId,
682+
isInsideMethodInvocationSuffix: true
683+
});
684+
685+
const isSingleLambda = isArgumentListSingleLambda(ctx.argumentList);
686+
let sep = isSingleLambda ? "" : softline;
687+
if (isSingleLambda) {
688+
const rBrace = isSingleArgumentLambdaExpressionWithBlock(ctx.argumentList)
689+
? ifBreak(
690+
indent(concat([softline, ctx.RBrace[0]])),
691+
printTokenWithComments(ctx.RBrace[0]),
692+
{ groupId: lambdaParametersGroupId }
693+
)
694+
: indent(concat([softline, ctx.RBrace[0]]));
695+
return dedent(putIntoBraces(argumentList, sep, ctx.LBrace[0], rBrace));
696+
}
697+
646698
if (params && params.shouldDedent) {
647699
return dedent(
648-
putIntoBraces(argumentList, softline, ctx.LBrace[0], ctx.RBrace[0])
700+
putIntoBraces(argumentList, sep, ctx.LBrace[0], ctx.RBrace[0])
649701
);
650702
}
651-
return putIntoBraces(argumentList, softline, ctx.LBrace[0], ctx.RBrace[0]);
703+
704+
return putIntoBraces(argumentList, sep, ctx.LBrace[0], ctx.RBrace[0]);
652705
}
653706

654-
argumentList(ctx: ArgumentListCtx) {
655-
const expressions = this.mapVisit(ctx.expression);
707+
argumentList(
708+
ctx: ArgumentListCtx,
709+
params: { lambdaParametersGroupId: symbol }
710+
) {
711+
const expressions = this.mapVisit(ctx.expression, params);
656712
const commas = ctx.Comma ? ctx.Comma.map(elt => concat([elt, line])) : [];
657713
return rejectAndJoinSeps(commas, expressions);
658714
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ArgumentListCstNode } from "java-parser";
2+
3+
export function isArgumentListSingleLambda(
4+
argumentList: ArgumentListCstNode[] | undefined
5+
) {
6+
if (argumentList === undefined) {
7+
return false;
8+
}
9+
10+
const args = argumentList[0].children.expression;
11+
if (args.length !== 1) {
12+
return false;
13+
}
14+
15+
const argument = args[0];
16+
return argument.children.lambdaExpression !== undefined;
17+
}
18+
19+
export const isSingleArgumentLambdaExpressionWithBlock = (
20+
argumentList: ArgumentListCstNode[] | undefined
21+
) => {
22+
if (argumentList === undefined) {
23+
return false;
24+
}
25+
26+
const args = argumentList[0].children.expression;
27+
if (args.length !== 1) {
28+
return false;
29+
}
30+
31+
const argument = args[0];
32+
return (
33+
argument.children.lambdaExpression !== undefined &&
34+
argument.children.lambdaExpression[0].children.lambdaBody[0].children
35+
.block !== undefined
36+
);
37+
};

packages/prettier-plugin-java/test/unit-test/lambda/_input.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,57 @@ public void lambdaWithoutBracesWhichBreak() {
5050
foo.isAnotherVeryVeryLongConditionTrue());
5151
}
5252

53+
public void chainCallWithLambda() {
54+
Stream
55+
.of(1, 2)
56+
.map(n -> {
57+
// testing method
58+
return n * 2;
59+
})
60+
.collect(Collectors.toList());
61+
}
62+
63+
public void lambdaWithLongListOfParameters() {
64+
final List<Integer> values = Stream
65+
.of(1, 2)
66+
.map((
67+
aVeryLongListOfParameter,
68+
aVeryLongListOfParameter,
69+
aVeryLongListOfParameter,
70+
aVeryLongListOfParameter,
71+
aVeryLongListOfParameter,
72+
aVeryLongListOfParameter
73+
) -> {
74+
// testing method
75+
return n * 2;
76+
})
77+
.collect(Collectors.toList());
78+
}
79+
80+
public void shortLambdaAssignation() {
81+
V t = t -> toto();
82+
}
83+
84+
public void longLambdaAssignation() {
85+
V t = (
86+
aVeryLongListOfParameter,
87+
aVeryLongListOfParameter,
88+
aVeryLongListOfParameter,
89+
aVeryLongListOfParameter,
90+
aVeryLongListOfParaa
91+
) -> {
92+
// testing method
93+
return n * 2;
94+
};
95+
}
96+
97+
public void callWithLambdaAndExtraParameter() {
98+
CompletableFuture.supplyAsync(
99+
() -> {
100+
// some processing
101+
return 2;
102+
},
103+
executor
104+
);
105+
}
53106
}

0 commit comments

Comments
 (0)