espolar 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/printers.ts CHANGED
@@ -1,90 +1,179 @@
1
1
  import type { PrinterContext, Printers } from "./api.ts";
2
- import type { AST, Comment } from "./types.ts";
2
+ import type { AST, AST_NODE_TYPES, Comment } from "./types.ts";
3
3
 
4
- const EXPRESSIONS_PRECEDENCE: Record<string, number> = {
4
+ const EXPRESSIONS_PRECEDENCE = {
5
+ // LHS of =, must NOT parenthesized
5
6
  ArrayPattern: 20,
6
7
  ObjectPattern: 20,
7
- ArrayExpression: 20,
8
- TaggedTemplateExpression: 20,
9
- ThisExpression: 20,
10
- Identifier: 20,
11
- TemplateLiteral: 20,
12
- Super: 20,
13
- SequenceExpression: 20,
14
- MemberExpression: 19,
15
- MetaProperty: 19,
16
- CallExpression: 19,
17
- ChainExpression: 19,
18
- ImportExpression: 19,
19
- NewExpression: 19,
8
+
9
+ // PrimaryExpression
10
+ // @ts-expect-error acorn-typescript compat
11
+ ParenthesizedExpression: 18,
12
+ ThisExpression: 18,
13
+ Super: 18, // like ThisExpression
14
+ Identifier: 18,
15
+ PrivateIdentifier: 18, // LHS of in
20
16
  Literal: 18,
21
- TSSatisfiesExpression: 18,
22
- TSInstantiationExpression: 18,
23
- TSNonNullExpression: 18,
24
- TSTypeAssertion: 18,
25
- AwaitExpression: 17,
26
- ClassExpression: 17,
27
- FunctionExpression: 17,
28
- ObjectExpression: 17,
29
- UnaryExpression: 16,
30
- UpdateExpression: 16,
31
- TSAsExpression: 15,
32
- BinaryExpression: 14,
33
- LogicalExpression: 13,
34
- ConditionalExpression: 4,
35
- ArrowFunctionExpression: 3,
36
- AssignmentExpression: 3,
17
+ ClassExpression: 18,
18
+ FunctionExpression: 18,
19
+ ObjectExpression: 18,
20
+ ArrayExpression: 18,
21
+ TemplateLiteral: 18,
22
+ // https://react.github.io/jsx/
23
+ JSXElement: 18,
24
+ JSXFragment: 18,
25
+
26
+ MetaProperty: 17,
27
+ MemberExpression: 17,
28
+ // Same precedence as MemberExpression, e.g. foo!.bar
29
+ TSNonNullExpression: 17,
30
+ ChainExpression: 17,
31
+ NewExpression: 17, // only with argument list, 16 if not (we don't print this form)
32
+ CallExpression: 17,
33
+ TaggedTemplateExpression: 17,
34
+ ImportExpression: 17,
35
+ // Between MemberExpression and NewExpression w/o arguments, e.g.
36
+ // f<T>.x // Error
37
+ // (f<T>).x // OK
38
+ // new f<T> // OK
39
+ TSInstantiationExpression: 16.5,
40
+
41
+ // postfix operators
42
+ UpdateExpression: 15,
43
+ // prefix operators
44
+ UnaryExpression: 14,
45
+ AwaitExpression: 14,
46
+ // behaves like postfix operators (e.g. cannot be part of LHS of **)
47
+ TSTypeAssertion: 14,
48
+
49
+ // ranges from 13-5 depending on operator
50
+ BinaryExpression: 13,
51
+ // as/satisfies have same precedence as relational operators
52
+ TSAsExpression: 9,
53
+ TSSatisfiesExpression: 9,
54
+ // ranges from 4-3 depending on operator
55
+ LogicalExpression: 4,
56
+
57
+ AssignmentExpression: 2,
58
+ ConditionalExpression: 2,
37
59
  YieldExpression: 2,
38
- RestElement: 1,
39
- };
60
+ ArrowFunctionExpression: 2,
61
+ SpreadElement: 2,
40
62
 
41
- const OPERATOR_PRECEDENCE: Record<string, number> = {
42
- "||": 2,
43
- "&&": 3,
44
- "??": 4,
45
- "|": 5,
46
- "^": 6,
47
- "&": 7,
48
- "==": 8,
49
- "!=": 8,
50
- "===": 8,
51
- "!==": 8,
63
+ SequenceExpression: 1,
64
+ } as const satisfies Partial<Record<AST.Expression["type"], number>>;
65
+
66
+ const OPERATOR_PRECEDENCE = {
67
+ "**": 13,
68
+ "*": 12,
69
+ "%": 12,
70
+ "/": 12,
71
+ "+": 11,
72
+ "-": 11,
73
+ "<<": 10,
74
+ ">>": 10,
75
+ ">>>": 10,
52
76
  "<": 9,
53
77
  ">": 9,
54
78
  "<=": 9,
55
79
  ">=": 9,
56
80
  in: 9,
57
81
  instanceof: 9,
58
- "<<": 10,
59
- ">>": 10,
60
- ">>>": 10,
61
- "+": 11,
62
- "-": 11,
63
- "*": 12,
64
- "%": 12,
65
- "/": 12,
66
- "**": 13,
82
+ "==": 8,
83
+ "!=": 8,
84
+ "===": 8,
85
+ "!==": 8,
86
+ "&": 7,
87
+ "^": 6,
88
+ "|": 5,
89
+ "&&": 4,
90
+ "??": 3,
91
+ "||": 3,
92
+ } as const satisfies Record<
93
+ (AST.BinaryExpression | AST.LogicalExpression)["operator"],
94
+ number
95
+ >;
96
+
97
+ const ASSOCIATIVE: Record<number, "left" | "right"> = {
98
+ [EXPRESSIONS_PRECEDENCE.MemberExpression]: "left",
99
+ [EXPRESSIONS_PRECEDENCE.UpdateExpression]: "left",
100
+ [EXPRESSIONS_PRECEDENCE.UnaryExpression]: "right",
101
+ [OPERATOR_PRECEDENCE["**"]]: "right",
102
+ [OPERATOR_PRECEDENCE["*"]]: "left",
103
+ [OPERATOR_PRECEDENCE["+"]]: "left",
104
+ [OPERATOR_PRECEDENCE["<"]]: "left",
105
+ [OPERATOR_PRECEDENCE["==="]]: "left",
106
+ [OPERATOR_PRECEDENCE["&"]]: "left",
107
+ [OPERATOR_PRECEDENCE["^"]]: "left",
108
+ [OPERATOR_PRECEDENCE["|"]]: "left",
109
+ [OPERATOR_PRECEDENCE["&&"]]: "left",
110
+ [OPERATOR_PRECEDENCE["||"]]: "left",
111
+ [EXPRESSIONS_PRECEDENCE.AssignmentExpression]: "right",
67
112
  };
68
113
 
69
- function commentNeedsNewline(comment: Comment): boolean {
70
- if (comment.type === "Line") return true;
71
- return comment.value.includes("\n");
72
- }
73
-
74
- function needsParens(
114
+ function getPrecedence(node: AST.Expression | AST.PrivateIdentifier): number {
115
+ if (node.type === "BinaryExpression" || node.type === "LogicalExpression") {
116
+ return (
117
+ OPERATOR_PRECEDENCE[node.operator] ?? EXPRESSIONS_PRECEDENCE[node.type]
118
+ );
119
+ }
120
+ return EXPRESSIONS_PRECEDENCE[node.type] ?? 20;
121
+ }
122
+
123
+ type ExpressionWithPrecedence = keyof typeof EXPRESSIONS_PRECEDENCE;
124
+ type ExpressionTypeWithPrecedenceAs<T extends number> = {
125
+ [K in AST_NODE_TYPES]: K extends ExpressionWithPrecedence
126
+ ? (typeof EXPRESSIONS_PRECEDENCE)[K] extends T
127
+ ? K
128
+ : never
129
+ : never;
130
+ }[AST_NODE_TYPES];
131
+
132
+ // Expression with same precedence as MemberExpression
133
+ type MemberLikeExpression = Extract<
134
+ AST.Expression,
135
+ {
136
+ type: ExpressionTypeWithPrecedenceAs<
137
+ typeof EXPRESSIONS_PRECEDENCE.MemberExpression
138
+ >;
139
+ }
140
+ >;
141
+
142
+ /**
143
+ * Check whether the operand of a Binary/Logical/AssignmentExpression needs parentheses.
144
+ * @param node
145
+ * @param parent
146
+ * @param where
147
+ * @returns
148
+ */
149
+ function operandOfBinaryExprNeedsParens(
75
150
  node: AST.Expression | AST.PrivateIdentifier,
76
151
  parent:
152
+ | AST.MemberExpression
153
+ | AST.CallExpression
154
+ | AST.NewExpression
155
+ | AST.TaggedTemplateExpression
156
+ | AST.TSInstantiationExpression
157
+ | AST.TSNonNullExpression
158
+ | AST.TSAsExpression
159
+ | AST.TSSatisfiesExpression
77
160
  | AST.BinaryExpression
78
161
  | AST.LogicalExpression
79
- | AST.AssignmentExpression,
80
- isRight: boolean,
162
+ | AST.AssignmentExpression
163
+ | AST.ConditionalExpression,
164
+ where: "left" | "right",
81
165
  ): boolean {
166
+ // In a BinaryExpression where LHS have a TS postfix, e.g.:
167
+ // (0 as number) & 1;
168
+ // (0 as number) | 1;
169
+ // If op is & or |, then LHS should be parenthesized to disambiguate;
170
+ // otherwise, no need to parenthesize.
82
171
  if (
83
- node.type === "PrivateIdentifier" ||
84
- node.type === "Identifier" ||
85
- node.type === "Super"
172
+ where === "left" &&
173
+ (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression") &&
174
+ parent.type === "BinaryExpression"
86
175
  ) {
87
- return false;
176
+ return parent.operator === "&" || parent.operator === "|";
88
177
  }
89
178
 
90
179
  // LogicalExpression mixed with ?? requires parens
@@ -96,62 +185,93 @@ function needsParens(
96
185
  return true;
97
186
  }
98
187
 
99
- const precedence = EXPRESSIONS_PRECEDENCE[node.type] ?? 20;
100
- const parentPrecedence = EXPRESSIONS_PRECEDENCE[parent.type] ?? 20;
188
+ const precedence = getPrecedence(node);
189
+ const parentPrecedence = getPrecedence(parent);
190
+
191
+ if (
192
+ parent.type === "BinaryExpression" &&
193
+ parent.operator === "**" &&
194
+ precedence === EXPRESSIONS_PRECEDENCE.UnaryExpression
195
+ ) {
196
+ // LHS of ** cannot have prefix operators, according to ES spec.
197
+ return true;
198
+ }
101
199
 
102
200
  if (precedence !== parentPrecedence) {
103
- // ** is right-to-left associative: `a ** b ** c` => `a ** (b ** c)`
104
- if (
105
- !isRight &&
106
- precedence === 15 &&
107
- parentPrecedence === 14 &&
108
- parent.operator === "**"
109
- ) {
110
- return false;
111
- }
112
201
  return precedence < parentPrecedence;
113
202
  }
114
203
 
115
- if (precedence !== 13 && precedence !== 14) {
116
- return false;
204
+ // optional chain cannot appeared as LHS of MemberExpression-like
205
+ if (
206
+ precedence === EXPRESSIONS_PRECEDENCE.MemberExpression &&
207
+ node.type === "ChainExpression"
208
+ ) {
209
+ return true;
117
210
  }
118
-
119
- const nodeOp = (node as AST.BinaryExpression | AST.LogicalExpression)
120
- .operator;
121
- if (nodeOp === "**" && parent.operator === "**") {
122
- return !isRight;
211
+ if (
212
+ parent.type === "NewExpression" &&
213
+ !validUnparenthesizedNewOperand(node as MemberLikeExpression)
214
+ ) {
215
+ // new X(), X cannot contain CallExpression
216
+ return true;
123
217
  }
124
218
 
125
- if (isRight) {
126
- return OPERATOR_PRECEDENCE[nodeOp] <= OPERATOR_PRECEDENCE[parent.operator];
127
- }
128
- return OPERATOR_PRECEDENCE[nodeOp] < OPERATOR_PRECEDENCE[parent.operator];
219
+ const associative = ASSOCIATIVE[precedence] ?? "left";
220
+ return associative !== where;
129
221
  }
130
222
 
131
- function arrowConciseBodyNeedsWrap(
132
- body: AST.BlockStatement | AST.Expression,
133
- ): boolean {
134
- if (body.type === "BlockStatement") return false;
135
- switch (body.type) {
136
- case "ObjectExpression":
137
- return true;
138
- case "AssignmentExpression":
139
- return body.left.type === "ObjectPattern";
140
- case "LogicalExpression":
141
- return body.left.type === "ObjectExpression";
142
- case "ConditionalExpression":
143
- return body.test.type === "ObjectExpression";
144
- case "TSAsExpression":
145
- case "TSSatisfiesExpression":
146
- case "TSNonNullExpression":
147
- return body.expression
148
- ? arrowConciseBodyNeedsWrap(body.expression)
149
- : false;
150
- default:
223
+ /**
224
+ * A valid unparenthesized `new` operand must be a member chain
225
+ * which not contains a CallExpression
226
+ */
227
+ function validUnparenthesizedNewOperand(node: MemberLikeExpression): boolean {
228
+ if (node.type === "ChainExpression" || node.type === "ImportExpression") {
229
+ return false;
230
+ }
231
+ let cur: AST.Expression = node;
232
+ while (true) {
233
+ if (cur.type === "CallExpression") {
151
234
  return false;
235
+ } else if (cur.type === "TSNonNullExpression") {
236
+ cur = cur.expression;
237
+ } else if (cur.type === "MemberExpression") {
238
+ cur = cur.object;
239
+ } else if (cur.type === "TaggedTemplateExpression") {
240
+ cur = cur.tag;
241
+ } else {
242
+ return true;
243
+ }
152
244
  }
153
245
  }
154
246
 
247
+ function operandOfUnaryExprNeedsParens(node: AST.Expression): boolean {
248
+ return (
249
+ EXPRESSIONS_PRECEDENCE[node.type] < EXPRESSIONS_PRECEDENCE.UnaryExpression
250
+ );
251
+ }
252
+
253
+ export function expectAssignmentExprNeedsParen(
254
+ node: AST.Expression | AST.SpreadElement,
255
+ ): boolean {
256
+ return (
257
+ EXPRESSIONS_PRECEDENCE[node.type] <
258
+ EXPRESSIONS_PRECEDENCE.AssignmentExpression
259
+ );
260
+ }
261
+
262
+ export function expectLHSExprNeedsParen(
263
+ node: AST.Expression | AST.SpreadElement,
264
+ ): boolean {
265
+ return (
266
+ EXPRESSIONS_PRECEDENCE[node.type] < EXPRESSIONS_PRECEDENCE.MemberExpression
267
+ );
268
+ }
269
+
270
+ function commentNeedsNewline(comment: Comment): boolean {
271
+ if (comment.type === "Line") return true;
272
+ return comment.value.includes("\n");
273
+ }
274
+
155
275
  // Printers
156
276
  export const defaultPrinters = {
157
277
  Program: printProgram,
@@ -306,7 +426,57 @@ export const defaultPrinters = {
306
426
  // JS – Statements
307
427
 
308
428
  function printProgram(program: AST.Program, context: PrinterContext): void {
309
- context.writeNodeListWithSourceGaps(program.body, "\n");
429
+ context.writeNodeListWithNewLineSep(program.body);
430
+ }
431
+
432
+ function canStartExpressionStatement(
433
+ node: AST.Expression | AST.PrivateIdentifier,
434
+ ): boolean {
435
+ let lhs: AST.Expression | AST.PrivateIdentifier;
436
+ switch (node.type) {
437
+ default:
438
+ return true;
439
+ case "ObjectExpression":
440
+ case "FunctionExpression":
441
+ case "ClassExpression":
442
+ case "ObjectPattern":
443
+ return false;
444
+ case "AssignmentExpression":
445
+ case "LogicalExpression":
446
+ case "BinaryExpression":
447
+ lhs = node.left;
448
+ break;
449
+ case "TSAsExpression":
450
+ case "TSSatisfiesExpression":
451
+ case "TSNonNullExpression":
452
+ lhs = node.expression;
453
+ break;
454
+ case "CallExpression":
455
+ lhs = node.callee;
456
+ break;
457
+ case "MemberExpression":
458
+ lhs = node.object;
459
+ if (node.computed && lhs.type === "Identifier" && lhs.name === "let") {
460
+ return false;
461
+ }
462
+ break;
463
+ case "TaggedTemplateExpression":
464
+ lhs = node.tag;
465
+ break;
466
+ case "ConditionalExpression":
467
+ lhs = node.test;
468
+ break;
469
+ case "SequenceExpression":
470
+ return canStartExpressionStatement(node.expressions[0]);
471
+ case "UpdateExpression":
472
+ return canStartExpressionStatement(node.argument);
473
+ case "ChainExpression":
474
+ return canStartExpressionStatement(node.expression);
475
+ }
476
+ return (
477
+ operandOfBinaryExprNeedsParens(lhs, node, "left") ||
478
+ canStartExpressionStatement(lhs)
479
+ );
310
480
  }
311
481
 
312
482
  function printExpressionStatement(
@@ -314,11 +484,7 @@ function printExpressionStatement(
314
484
  context: PrinterContext,
315
485
  ): void {
316
486
  const expr = statement.expression;
317
- if (
318
- expr.type === "ObjectExpression" ||
319
- expr.type === "FunctionExpression" ||
320
- (expr.type === "AssignmentExpression" && expr.left.type === "ObjectPattern")
321
- ) {
487
+ if (!canStartExpressionStatement(expr)) {
322
488
  context.write("(");
323
489
  context.writeNode(expr);
324
490
  context.write(");");
@@ -358,7 +524,14 @@ function printVariableDeclarator(
358
524
  }
359
525
  if (declarator.init) {
360
526
  context.write(" = ");
361
- context.writeNode(declarator.init);
527
+ const needsParens = expectAssignmentExprNeedsParen(declarator.init);
528
+ if (needsParens) {
529
+ context.write("(");
530
+ context.writeNode(declarator.init);
531
+ context.write(")");
532
+ } else {
533
+ context.writeNode(declarator.init);
534
+ }
362
535
  }
363
536
  }
364
537
 
@@ -370,7 +543,7 @@ function printBlockStatement(
370
543
  context.write("{");
371
544
  if (body.length > 0) {
372
545
  context.write("\n");
373
- context.writeNodeListWithSourceGaps(body, "\n");
546
+ context.writeNodeListWithNewLineSep(body);
374
547
  context.write("\n");
375
548
  }
376
549
  context.write("}");
@@ -660,12 +833,21 @@ function printUnaryExpression(
660
833
  if (expr.operator.length > 1) {
661
834
  context.write(" ");
662
835
  }
663
- const argPrec = EXPRESSIONS_PRECEDENCE[expr.argument.type];
664
- if (argPrec != null && argPrec < EXPRESSIONS_PRECEDENCE.UnaryExpression) {
836
+ const needsParen = operandOfUnaryExprNeedsParens(expr.argument);
837
+ if (needsParen) {
665
838
  context.write("(");
666
839
  context.writeNode(expr.argument);
667
840
  context.write(")");
668
841
  } else {
842
+ if (
843
+ expr.operator.length === 1 &&
844
+ (expr.argument.type === "UnaryExpression" ||
845
+ expr.argument.type === "UpdateExpression") &&
846
+ expr.argument.operator.startsWith(expr.operator)
847
+ ) {
848
+ // `- -x` or `+ ++x` should not be printed as `--x` or `+++x`
849
+ context.write(" ");
850
+ }
669
851
  context.writeNode(expr.argument);
670
852
  }
671
853
  }
@@ -704,7 +886,7 @@ function printBinaryExpression(
704
886
  const left = expression.left;
705
887
  const right = expression.right;
706
888
 
707
- if (needsParens(left, expression, false)) {
889
+ if (operandOfBinaryExprNeedsParens(left, expression, "left")) {
708
890
  context.write("(");
709
891
  context.writeNode(left);
710
892
  context.write(")");
@@ -716,7 +898,7 @@ function printBinaryExpression(
716
898
  context.write(String(expression.operator));
717
899
  context.write(" ");
718
900
 
719
- if (needsParens(right, expression, true)) {
901
+ if (operandOfBinaryExprNeedsParens(right, expression, "right")) {
720
902
  context.write("(");
721
903
  context.writeNode(right);
722
904
  context.write(")");
@@ -729,8 +911,7 @@ function printConditionalExpression(
729
911
  expr: AST.ConditionalExpression,
730
912
  context: PrinterContext,
731
913
  ): void {
732
- const testPrec = EXPRESSIONS_PRECEDENCE[expr.test.type] ?? 20;
733
- if (testPrec <= EXPRESSIONS_PRECEDENCE.ConditionalExpression) {
914
+ if (operandOfBinaryExprNeedsParens(expr.test, expr, "left")) {
734
915
  context.write("(");
735
916
  context.writeNode(expr.test);
736
917
  context.write(")");
@@ -740,7 +921,13 @@ function printConditionalExpression(
740
921
  context.write(" ? ");
741
922
  context.writeNode(expr.consequent);
742
923
  context.write(" : ");
743
- context.writeNode(expr.alternate);
924
+ if (operandOfBinaryExprNeedsParens(expr.alternate, expr, "right")) {
925
+ context.write("(");
926
+ context.writeNode(expr.alternate);
927
+ context.write(")");
928
+ } else {
929
+ context.writeNode(expr.alternate);
930
+ }
744
931
  }
745
932
 
746
933
  function printYieldExpression(
@@ -753,12 +940,14 @@ function printYieldExpression(
753
940
  const leadingComments = context.options.getLeadingComments?.(expr.argument);
754
941
  const needsParensASi =
755
942
  leadingComments?.some((c) => commentNeedsNewline(c)) ?? false;
756
- if (needsParensASi) {
943
+ const needsParens =
944
+ needsParensASi || expectAssignmentExprNeedsParen(expr.argument);
945
+ if (needsParens) {
757
946
  context.write("(");
758
- }
759
- context.writeNode(expr.argument);
760
- if (needsParensASi) {
947
+ context.writeNode(expr.argument);
761
948
  context.write(")");
949
+ } else {
950
+ context.writeNode(expr.argument);
762
951
  }
763
952
  }
764
953
  }
@@ -769,8 +958,8 @@ function printAwaitExpression(
769
958
  ): void {
770
959
  context.write("await");
771
960
  if (expr.argument) {
772
- const argPrec = EXPRESSIONS_PRECEDENCE[expr.argument.type];
773
- if (argPrec != null && argPrec < EXPRESSIONS_PRECEDENCE.AwaitExpression) {
961
+ const needsParens = operandOfUnaryExprNeedsParens(expr.argument);
962
+ if (needsParens) {
774
963
  context.write(" (");
775
964
  context.writeNode(expr.argument);
776
965
  context.write(")");
@@ -785,17 +974,19 @@ function printSequenceExpression(
785
974
  expr: AST.SequenceExpression,
786
975
  context: PrinterContext,
787
976
  ): void {
788
- context.write("(");
789
977
  context.writeNodeList(expr.expressions, ", ");
790
- context.write(")");
791
978
  }
792
979
 
793
980
  function printCallExpression(
794
981
  expression: AST.CallExpression,
795
982
  context: PrinterContext,
796
983
  ): void {
797
- const calleePrec = EXPRESSIONS_PRECEDENCE[expression.callee.type] ?? 20;
798
- if (calleePrec < EXPRESSIONS_PRECEDENCE.CallExpression) {
984
+ const needsParens = operandOfBinaryExprNeedsParens(
985
+ expression.callee,
986
+ expression,
987
+ "left",
988
+ );
989
+ if (needsParens) {
799
990
  context.write("(");
800
991
  context.writeNode(expression.callee);
801
992
  context.write(")");
@@ -815,7 +1006,7 @@ function printCallExpression(
815
1006
  } else {
816
1007
  context.write("(");
817
1008
  }
818
- context.writeNodeList(expression.arguments, ", ");
1009
+ context.writeExpressionListWithCommaSep(expression.arguments);
819
1010
  context.write(")");
820
1011
  }
821
1012
 
@@ -824,11 +1015,12 @@ function printNewExpression(
824
1015
  context: PrinterContext,
825
1016
  ): void {
826
1017
  context.write("new ");
827
- const calleePrec = EXPRESSIONS_PRECEDENCE[expression.callee.type] ?? 20;
828
- if (
829
- calleePrec < EXPRESSIONS_PRECEDENCE.NewExpression ||
830
- hasCallExpression(expression.callee)
831
- ) {
1018
+ const needsParens = operandOfBinaryExprNeedsParens(
1019
+ expression.callee,
1020
+ expression,
1021
+ "left",
1022
+ );
1023
+ if (needsParens) {
832
1024
  context.write("(");
833
1025
  context.writeNode(expression.callee);
834
1026
  context.write(")");
@@ -845,23 +1037,10 @@ function printNewExpression(
845
1037
  } else {
846
1038
  context.write("(");
847
1039
  }
848
- context.writeNodeList(expression.arguments, ", ");
1040
+ context.writeExpressionListWithCommaSep(expression.arguments);
849
1041
  context.write(")");
850
1042
  }
851
1043
 
852
- function hasCallExpression(node: AST.Expression): boolean {
853
- let cur: AST.Expression | undefined = node;
854
- while (cur) {
855
- if (cur.type === "CallExpression") return true;
856
- if (cur.type === "MemberExpression") {
857
- cur = cur.object;
858
- } else {
859
- return false;
860
- }
861
- }
862
- return false;
863
- }
864
-
865
1044
  function printChainExpression(
866
1045
  expression: AST.ChainExpression,
867
1046
  context: PrinterContext,
@@ -873,8 +1052,12 @@ function printMemberExpression(
873
1052
  expression: AST.MemberExpression,
874
1053
  context: PrinterContext,
875
1054
  ): void {
876
- const objPrec = EXPRESSIONS_PRECEDENCE[expression.object.type] ?? 20;
877
- if (objPrec < EXPRESSIONS_PRECEDENCE.MemberExpression) {
1055
+ const needsParens = operandOfBinaryExprNeedsParens(
1056
+ expression.object,
1057
+ expression,
1058
+ "left",
1059
+ );
1060
+ if (needsParens) {
878
1061
  context.write("(");
879
1062
  context.writeNode(expression.object);
880
1063
  context.write(")");
@@ -915,7 +1098,7 @@ function printArrayExpression(
915
1098
  context: PrinterContext,
916
1099
  ): void {
917
1100
  context.write("[");
918
- context.writeNodeList(array.elements, ", ");
1101
+ context.writeExpressionListWithCommaSep(array.elements);
919
1102
  context.write("]");
920
1103
  }
921
1104
 
@@ -950,7 +1133,10 @@ function printProperty(
950
1133
  }
951
1134
 
952
1135
  // shorthand method
953
- if (value.type === "FunctionExpression") {
1136
+ if (
1137
+ value.type === "FunctionExpression" &&
1138
+ (property.method || property.kind !== "init")
1139
+ ) {
954
1140
  if (property.kind !== "init") {
955
1141
  context.write(property.kind + " ");
956
1142
  }
@@ -981,9 +1167,6 @@ function printProperty(
981
1167
  context.writeNode(property.key);
982
1168
  context.write("]: ");
983
1169
  } else {
984
- if (property.kind === "get" || property.kind === "set") {
985
- context.write(property.kind + " ");
986
- }
987
1170
  context.writeNode(property.key);
988
1171
  context.write(": ");
989
1172
  }
@@ -996,7 +1179,14 @@ function printSpreadElement(
996
1179
  context: PrinterContext,
997
1180
  ): void {
998
1181
  context.write("...");
999
- context.writeNode(spread.argument);
1182
+ const needsParens = expectAssignmentExprNeedsParen(spread.argument);
1183
+ if (needsParens) {
1184
+ context.write("(");
1185
+ context.writeNode(spread.argument);
1186
+ context.write(")");
1187
+ } else {
1188
+ context.writeNode(spread.argument);
1189
+ }
1000
1190
  }
1001
1191
 
1002
1192
  function printRestElement(
@@ -1037,7 +1227,17 @@ function printTaggedTemplateExpression(
1037
1227
  node: AST.TaggedTemplateExpression,
1038
1228
  context: PrinterContext,
1039
1229
  ): void {
1040
- context.writeNode(node.tag);
1230
+ const needsParens = operandOfBinaryExprNeedsParens(node.tag, node, "left");
1231
+ if (needsParens) {
1232
+ context.write("(");
1233
+ context.writeNode(node.tag);
1234
+ context.write(")");
1235
+ } else {
1236
+ context.writeNode(node.tag);
1237
+ }
1238
+ if (node.typeArguments) {
1239
+ context.writeNode(node.typeArguments);
1240
+ }
1041
1241
  context.writeNode(node.quasi);
1042
1242
  }
1043
1243
 
@@ -1114,6 +1314,60 @@ function printFunction(
1114
1314
  }
1115
1315
  }
1116
1316
 
1317
+ function canStartConciseBody(
1318
+ body: AST.BlockStatement | AST.Expression | AST.PrivateIdentifier,
1319
+ ): boolean {
1320
+ if (body.type === "BlockStatement" || body.type === "PrivateIdentifier") {
1321
+ return true;
1322
+ }
1323
+ if (expectAssignmentExprNeedsParen(body)) {
1324
+ return false;
1325
+ }
1326
+ let lhs: AST.Expression | AST.PrivateIdentifier;
1327
+ switch (body.type) {
1328
+ default:
1329
+ return true;
1330
+ case "ObjectExpression":
1331
+ case "FunctionExpression":
1332
+ case "ClassExpression":
1333
+ case "ObjectPattern":
1334
+ return false;
1335
+ case "AssignmentExpression":
1336
+ case "LogicalExpression":
1337
+ case "BinaryExpression":
1338
+ lhs = body.left;
1339
+ break;
1340
+ case "TSAsExpression":
1341
+ case "TSSatisfiesExpression":
1342
+ case "TSNonNullExpression":
1343
+ lhs = body.expression;
1344
+ break;
1345
+ case "CallExpression":
1346
+ lhs = body.callee;
1347
+ break;
1348
+ case "MemberExpression":
1349
+ lhs = body.object;
1350
+ if (body.computed && lhs.type === "Identifier" && lhs.name === "let") {
1351
+ return false;
1352
+ }
1353
+ break;
1354
+ case "TaggedTemplateExpression":
1355
+ lhs = body.tag;
1356
+ break;
1357
+ case "ConditionalExpression":
1358
+ lhs = body.test;
1359
+ break;
1360
+ case "UpdateExpression":
1361
+ return canStartConciseBody(body.argument);
1362
+ case "ChainExpression":
1363
+ return canStartConciseBody(body.expression);
1364
+ }
1365
+ return (
1366
+ operandOfBinaryExprNeedsParens(lhs, body, "left") ||
1367
+ canStartConciseBody(lhs)
1368
+ );
1369
+ }
1370
+
1117
1371
  function printArrowFunctionExpression(
1118
1372
  fn: AST.ArrowFunctionExpression,
1119
1373
  context: PrinterContext,
@@ -1130,7 +1384,7 @@ function printArrowFunctionExpression(
1130
1384
  writeReturnType(fn, context);
1131
1385
  context.write(" => ");
1132
1386
  const body = fn.body;
1133
- if (arrowConciseBodyNeedsWrap(body)) {
1387
+ if (!canStartConciseBody(body)) {
1134
1388
  context.write("(");
1135
1389
  context.writeNode(body);
1136
1390
  context.write(")");
@@ -1175,7 +1429,14 @@ function printClass(
1175
1429
  }
1176
1430
  if (node.superClass) {
1177
1431
  context.write("extends ");
1178
- context.writeNode(node.superClass);
1432
+ const needsParens = expectLHSExprNeedsParen(node.superClass);
1433
+ if (needsParens) {
1434
+ context.write("(");
1435
+ context.writeNode(node.superClass);
1436
+ context.write(")");
1437
+ } else {
1438
+ context.writeNode(node.superClass);
1439
+ }
1179
1440
  if (node.superTypeArguments) {
1180
1441
  context.writeNode(node.superTypeArguments);
1181
1442
  } else if (node.superTypeParameters) {
@@ -1197,7 +1458,7 @@ function printClassBody(node: AST.ClassBody, context: PrinterContext): void {
1197
1458
  const body = node.body;
1198
1459
  if (body.length > 0) {
1199
1460
  context.write("\n");
1200
- context.writeNodeListWithSourceGaps(body, "\n");
1461
+ context.writeNodeListWithNewLineSep(body);
1201
1462
  context.write("\n");
1202
1463
  }
1203
1464
  context.write("}");
@@ -1211,15 +1472,43 @@ function printStaticBlock(
1211
1472
  const body = node.body;
1212
1473
  if (body.length > 0) {
1213
1474
  context.write("\n");
1214
- context.writeNodeListWithSourceGaps(body, "\n");
1475
+ context.writeNodeListWithNewLineSep(body);
1215
1476
  context.write("\n");
1216
1477
  }
1217
1478
  context.write("}");
1218
1479
  }
1219
1480
 
1481
+ function validUnparenthesizedDecorator(node: AST.Expression): boolean {
1482
+ let current: AST.Expression = node;
1483
+ if (current.type === "CallExpression") {
1484
+ current = current.callee;
1485
+ }
1486
+ if (current.type === "TSInstantiationExpression") {
1487
+ current = current.expression;
1488
+ }
1489
+ while (true) {
1490
+ if (current.type === "Identifier") {
1491
+ return true;
1492
+ } else if (current.type === "MemberExpression") {
1493
+ if (current.computed) {
1494
+ return false;
1495
+ }
1496
+ current = current.object;
1497
+ } else {
1498
+ return false;
1499
+ }
1500
+ }
1501
+ }
1502
+
1220
1503
  function printDecorator(node: AST.Decorator, context: PrinterContext): void {
1221
1504
  context.write("@");
1222
- context.writeNode(node.expression);
1505
+ if (!validUnparenthesizedDecorator(node.expression)) {
1506
+ context.write("(");
1507
+ context.writeNode(node.expression);
1508
+ context.write(")");
1509
+ } else {
1510
+ context.writeNode(node.expression);
1511
+ }
1223
1512
  context.write("\n");
1224
1513
  }
1225
1514
 
@@ -1375,13 +1664,17 @@ function printImportDeclaration(
1375
1664
  }
1376
1665
 
1377
1666
  if (namespaceSpec) {
1378
- if (wroteDefault) context.write(", ");
1667
+ if (wroteDefault) {
1668
+ context.write(", ");
1669
+ }
1379
1670
  context.write("* as ");
1380
1671
  context.writeNode(namespaceSpec.local);
1381
1672
  }
1382
1673
 
1383
1674
  if (namedSpecs.length > 0) {
1384
- if (wroteDefault || namespaceSpec) context.write(", ");
1675
+ if (wroteDefault || namespaceSpec) {
1676
+ context.write(", ");
1677
+ }
1385
1678
  context.write("{ ");
1386
1679
  context.writeNodeList(namedSpecs, ", ");
1387
1680
  context.write(" }");
@@ -1546,8 +1839,12 @@ function printTSAsExpression(
1546
1839
  expression: AST.TSAsExpression,
1547
1840
  context: PrinterContext,
1548
1841
  ): void {
1549
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1550
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSAsExpression) {
1842
+ const needsParens = operandOfBinaryExprNeedsParens(
1843
+ expression.expression,
1844
+ expression,
1845
+ "left",
1846
+ );
1847
+ if (needsParens) {
1551
1848
  context.write("(");
1552
1849
  context.writeNode(expression.expression);
1553
1850
  context.write(")");
@@ -1562,8 +1859,12 @@ function printTSSatisfiesExpression(
1562
1859
  expression: AST.TSSatisfiesExpression,
1563
1860
  context: PrinterContext,
1564
1861
  ): void {
1565
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1566
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSSatisfiesExpression) {
1862
+ const needsParens = operandOfBinaryExprNeedsParens(
1863
+ expression.expression,
1864
+ expression,
1865
+ "left",
1866
+ );
1867
+ if (needsParens) {
1567
1868
  context.write("(");
1568
1869
  context.writeNode(expression.expression);
1569
1870
  context.write(")");
@@ -1581,8 +1882,8 @@ function printTSTypeAssertion(
1581
1882
  context.write("<");
1582
1883
  context.writeNode(expression.typeAnnotation);
1583
1884
  context.write(">");
1584
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1585
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSTypeAssertion) {
1885
+ const needsParens = operandOfUnaryExprNeedsParens(expression.expression);
1886
+ if (needsParens) {
1586
1887
  context.write("(");
1587
1888
  context.writeNode(expression.expression);
1588
1889
  context.write(")");
@@ -1595,7 +1896,18 @@ function printTSNonNullExpression(
1595
1896
  expression: AST.TSNonNullExpression,
1596
1897
  context: PrinterContext,
1597
1898
  ): void {
1598
- context.writeNode(expression.expression);
1899
+ const needsParens = operandOfBinaryExprNeedsParens(
1900
+ expression.expression,
1901
+ expression,
1902
+ "left",
1903
+ );
1904
+ if (needsParens) {
1905
+ context.write("(");
1906
+ context.writeNode(expression.expression);
1907
+ context.write(")");
1908
+ } else {
1909
+ context.writeNode(expression.expression);
1910
+ }
1599
1911
  context.write("!");
1600
1912
  }
1601
1913
 
@@ -1991,7 +2303,14 @@ function printTSMappedType(
1991
2303
  node: AST.TSMappedType,
1992
2304
  context: PrinterContext,
1993
2305
  ): void {
1994
- context.write("{ [");
2306
+ context.write("{ ");
2307
+ if (node.readonly) {
2308
+ if (typeof node.readonly === "string") {
2309
+ context.write(node.readonly);
2310
+ }
2311
+ context.write("readonly ");
2312
+ }
2313
+ context.write("[");
1995
2314
  const legacyTp = node.typeParameter;
1996
2315
  const key = node.key ?? legacyTp?.name;
1997
2316
  const constraint = node.constraint ?? legacyTp?.constraint;
@@ -2007,7 +2326,17 @@ function printTSMappedType(
2007
2326
  context.write(" in ");
2008
2327
  context.writeNode(constraint);
2009
2328
  }
2329
+ if (node.nameType) {
2330
+ context.write(" as ");
2331
+ context.writeNode(node.nameType);
2332
+ }
2010
2333
  context.write("]");
2334
+ if (node.optional) {
2335
+ if (typeof node.optional === "string") {
2336
+ context.write(node.optional);
2337
+ }
2338
+ context.write("?");
2339
+ }
2011
2340
  if (node.typeAnnotation) {
2012
2341
  context.write(": ");
2013
2342
  context.writeNode(node.typeAnnotation);
@@ -2164,11 +2493,18 @@ function printTSModuleDeclaration(
2164
2493
  context.write(String(kind) + " ");
2165
2494
  context.writeNode(node.id);
2166
2495
  }
2167
- if (node.body) {
2168
- if (node.body.type === "TSModuleBlock") {
2169
- context.write(" ");
2170
- }
2171
- context.writeNode(node.body);
2496
+ let body = node.body as
2497
+ | AST.TSModuleDeclaration
2498
+ | AST.TSModuleBlock
2499
+ | undefined;
2500
+ while (body?.type === "TSModuleDeclaration") {
2501
+ context.write(".");
2502
+ context.writeNode(body.id);
2503
+ body = body.body;
2504
+ }
2505
+ if (body) {
2506
+ context.write(" ");
2507
+ context.writeNode(body);
2172
2508
  }
2173
2509
  }
2174
2510
 
@@ -2177,7 +2513,7 @@ function printTSModuleBlock(
2177
2513
  context: PrinterContext,
2178
2514
  ): void {
2179
2515
  context.write("{\n");
2180
- context.writeNodeListWithSourceGaps(node.body, "\n");
2516
+ context.writeNodeListWithNewLineSep(node.body);
2181
2517
  context.write("\n}");
2182
2518
  }
2183
2519
 
@@ -2239,10 +2575,21 @@ function printTSNamespaceExportDeclaration(
2239
2575
  }
2240
2576
 
2241
2577
  function printTSInstantiationExpression(
2242
- node: { expression: AST.Node; typeArguments: AST.Node },
2578
+ node: AST.TSInstantiationExpression,
2243
2579
  context: PrinterContext,
2244
2580
  ): void {
2245
- context.writeNode(node.expression);
2581
+ const needsParens = operandOfBinaryExprNeedsParens(
2582
+ node.expression,
2583
+ node,
2584
+ "left",
2585
+ );
2586
+ if (needsParens) {
2587
+ context.write("(");
2588
+ context.writeNode(node.expression);
2589
+ context.write(")");
2590
+ } else {
2591
+ context.writeNode(node.expression);
2592
+ }
2246
2593
  context.writeNode(node.typeArguments);
2247
2594
  }
2248
2595
 
@@ -2274,3 +2621,14 @@ function writeOptionalTypeAnnotation(
2274
2621
  context.writeNode(node.typeAnnotation);
2275
2622
  }
2276
2623
  }
2624
+
2625
+ export function writeComment(comment: Comment, context: PrinterContext): void {
2626
+ if (comment.type === "Line") {
2627
+ context.write("//" + comment.value + "\n");
2628
+ } else {
2629
+ context.write("/*" + comment.value + "*/");
2630
+ if (comment.value.includes("\n")) {
2631
+ context.write("\n");
2632
+ }
2633
+ }
2634
+ }