espolar 0.2.1 → 0.3.0

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,36 +185,98 @@ 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];
219
+ const associative = ASSOCIATIVE[precedence] ?? "left";
220
+ return associative !== where;
221
+ }
222
+
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
+ let cur: AST.Expression = node;
229
+ while (true) {
230
+ if (cur.type === "CallExpression") {
231
+ return false;
232
+ } else if (cur.type === "TSNonNullExpression") {
233
+ cur = cur.expression;
234
+ } else if (cur.type === "MemberExpression") {
235
+ cur = cur.object;
236
+ } else if (cur.type === "TaggedTemplateExpression") {
237
+ cur = cur.tag;
238
+ } else if (
239
+ cur.type === "MetaProperty" ||
240
+ cur.type === "NewExpression" ||
241
+ cur.type === "ImportExpression"
242
+ ) {
243
+ return true;
244
+ } else {
245
+ // The operand is either:
246
+ // - an expression with higher precedence, no need to check further
247
+ // - an expression with lower precedence, will be inner-parenthesized later
248
+ // - a ChainExpression, will be inner-parenthesized later
249
+ return cur !== node;
250
+ }
127
251
  }
128
- return OPERATOR_PRECEDENCE[nodeOp] < OPERATOR_PRECEDENCE[parent.operator];
252
+ }
253
+
254
+ function operandOfUnaryExprNeedsParens(node: AST.Expression): boolean {
255
+ return (
256
+ EXPRESSIONS_PRECEDENCE[node.type] < EXPRESSIONS_PRECEDENCE.UnaryExpression
257
+ );
258
+ }
259
+
260
+ export function expectAssignmentExprNeedsParen(
261
+ node: AST.Expression | AST.SpreadElement,
262
+ ): boolean {
263
+ return (
264
+ EXPRESSIONS_PRECEDENCE[node.type] <
265
+ EXPRESSIONS_PRECEDENCE.AssignmentExpression
266
+ );
267
+ }
268
+
269
+ export function expectLHSExprNeedsParen(
270
+ node: AST.Expression | AST.SpreadElement,
271
+ ): boolean {
272
+ return (
273
+ EXPRESSIONS_PRECEDENCE[node.type] < EXPRESSIONS_PRECEDENCE.MemberExpression
274
+ );
275
+ }
276
+
277
+ function commentNeedsNewline(comment: Comment): boolean {
278
+ if (comment.type === "Line") return true;
279
+ return comment.value.includes("\n");
129
280
  }
130
281
 
131
282
  function arrowConciseBodyNeedsWrap(
@@ -306,7 +457,54 @@ export const defaultPrinters = {
306
457
  // JS – Statements
307
458
 
308
459
  function printProgram(program: AST.Program, context: PrinterContext): void {
309
- context.writeNodeListWithSourceGaps(program.body, "\n");
460
+ context.writeNodeListWithNewLineSep(program.body);
461
+ }
462
+
463
+ function canStartExpressionStatement(
464
+ node: AST.Expression | AST.PrivateIdentifier,
465
+ ): boolean {
466
+ let lhs: AST.Expression | AST.PrivateIdentifier;
467
+ switch (node.type) {
468
+ default:
469
+ return true;
470
+ case "ObjectExpression":
471
+ case "FunctionExpression":
472
+ case "ClassExpression":
473
+ case "ObjectPattern":
474
+ return false;
475
+ case "AssignmentExpression":
476
+ case "LogicalExpression":
477
+ case "BinaryExpression":
478
+ lhs = node.left;
479
+ break;
480
+ case "TSAsExpression":
481
+ case "TSSatisfiesExpression":
482
+ case "TSNonNullExpression":
483
+ lhs = node.expression;
484
+ break;
485
+ case "CallExpression":
486
+ lhs = node.callee;
487
+ break;
488
+ case "MemberExpression":
489
+ lhs = node.object;
490
+ break;
491
+ case "TaggedTemplateExpression":
492
+ lhs = node.tag;
493
+ break;
494
+ case "ConditionalExpression":
495
+ lhs = node.test;
496
+ break;
497
+ case "SequenceExpression":
498
+ return canStartExpressionStatement(node.expressions[0]);
499
+ case "UpdateExpression":
500
+ return canStartExpressionStatement(node.argument);
501
+ case "ChainExpression":
502
+ return canStartExpressionStatement(node.expression);
503
+ }
504
+ return (
505
+ operandOfBinaryExprNeedsParens(lhs, node, "left") ||
506
+ canStartExpressionStatement(lhs)
507
+ );
310
508
  }
311
509
 
312
510
  function printExpressionStatement(
@@ -314,11 +512,7 @@ function printExpressionStatement(
314
512
  context: PrinterContext,
315
513
  ): void {
316
514
  const expr = statement.expression;
317
- if (
318
- expr.type === "ObjectExpression" ||
319
- expr.type === "FunctionExpression" ||
320
- (expr.type === "AssignmentExpression" && expr.left.type === "ObjectPattern")
321
- ) {
515
+ if (!canStartExpressionStatement(expr)) {
322
516
  context.write("(");
323
517
  context.writeNode(expr);
324
518
  context.write(");");
@@ -358,7 +552,14 @@ function printVariableDeclarator(
358
552
  }
359
553
  if (declarator.init) {
360
554
  context.write(" = ");
361
- context.writeNode(declarator.init);
555
+ const needsParens = expectAssignmentExprNeedsParen(declarator.init);
556
+ if (needsParens) {
557
+ context.write("(");
558
+ context.writeNode(declarator.init);
559
+ context.write(")");
560
+ } else {
561
+ context.writeNode(declarator.init);
562
+ }
362
563
  }
363
564
  }
364
565
 
@@ -370,7 +571,7 @@ function printBlockStatement(
370
571
  context.write("{");
371
572
  if (body.length > 0) {
372
573
  context.write("\n");
373
- context.writeNodeListWithSourceGaps(body, "\n");
574
+ context.writeNodeListWithNewLineSep(body);
374
575
  context.write("\n");
375
576
  }
376
577
  context.write("}");
@@ -660,12 +861,21 @@ function printUnaryExpression(
660
861
  if (expr.operator.length > 1) {
661
862
  context.write(" ");
662
863
  }
663
- const argPrec = EXPRESSIONS_PRECEDENCE[expr.argument.type];
664
- if (argPrec != null && argPrec < EXPRESSIONS_PRECEDENCE.UnaryExpression) {
864
+ const needsParen = operandOfUnaryExprNeedsParens(expr.argument);
865
+ if (needsParen) {
665
866
  context.write("(");
666
867
  context.writeNode(expr.argument);
667
868
  context.write(")");
668
869
  } else {
870
+ if (
871
+ expr.operator.length === 1 &&
872
+ (expr.argument.type === "UnaryExpression" ||
873
+ expr.argument.type === "UpdateExpression") &&
874
+ expr.argument.operator.startsWith(expr.operator)
875
+ ) {
876
+ // `- -x` or `+ ++x` should not be printed as `--x` or `+++x`
877
+ context.write(" ");
878
+ }
669
879
  context.writeNode(expr.argument);
670
880
  }
671
881
  }
@@ -704,7 +914,7 @@ function printBinaryExpression(
704
914
  const left = expression.left;
705
915
  const right = expression.right;
706
916
 
707
- if (needsParens(left, expression, false)) {
917
+ if (operandOfBinaryExprNeedsParens(left, expression, "left")) {
708
918
  context.write("(");
709
919
  context.writeNode(left);
710
920
  context.write(")");
@@ -716,7 +926,7 @@ function printBinaryExpression(
716
926
  context.write(String(expression.operator));
717
927
  context.write(" ");
718
928
 
719
- if (needsParens(right, expression, true)) {
929
+ if (operandOfBinaryExprNeedsParens(right, expression, "right")) {
720
930
  context.write("(");
721
931
  context.writeNode(right);
722
932
  context.write(")");
@@ -729,8 +939,7 @@ function printConditionalExpression(
729
939
  expr: AST.ConditionalExpression,
730
940
  context: PrinterContext,
731
941
  ): void {
732
- const testPrec = EXPRESSIONS_PRECEDENCE[expr.test.type] ?? 20;
733
- if (testPrec <= EXPRESSIONS_PRECEDENCE.ConditionalExpression) {
942
+ if (operandOfBinaryExprNeedsParens(expr.test, expr, "left")) {
734
943
  context.write("(");
735
944
  context.writeNode(expr.test);
736
945
  context.write(")");
@@ -740,7 +949,13 @@ function printConditionalExpression(
740
949
  context.write(" ? ");
741
950
  context.writeNode(expr.consequent);
742
951
  context.write(" : ");
743
- context.writeNode(expr.alternate);
952
+ if (operandOfBinaryExprNeedsParens(expr.alternate, expr, "right")) {
953
+ context.write("(");
954
+ context.writeNode(expr.alternate);
955
+ context.write(")");
956
+ } else {
957
+ context.writeNode(expr.alternate);
958
+ }
744
959
  }
745
960
 
746
961
  function printYieldExpression(
@@ -753,12 +968,14 @@ function printYieldExpression(
753
968
  const leadingComments = context.options.getLeadingComments?.(expr.argument);
754
969
  const needsParensASi =
755
970
  leadingComments?.some((c) => commentNeedsNewline(c)) ?? false;
756
- if (needsParensASi) {
971
+ const needsParens =
972
+ needsParensASi || expectAssignmentExprNeedsParen(expr.argument);
973
+ if (needsParens) {
757
974
  context.write("(");
758
- }
759
- context.writeNode(expr.argument);
760
- if (needsParensASi) {
975
+ context.writeNode(expr.argument);
761
976
  context.write(")");
977
+ } else {
978
+ context.writeNode(expr.argument);
762
979
  }
763
980
  }
764
981
  }
@@ -769,8 +986,8 @@ function printAwaitExpression(
769
986
  ): void {
770
987
  context.write("await");
771
988
  if (expr.argument) {
772
- const argPrec = EXPRESSIONS_PRECEDENCE[expr.argument.type];
773
- if (argPrec != null && argPrec < EXPRESSIONS_PRECEDENCE.AwaitExpression) {
989
+ const needsParens = operandOfUnaryExprNeedsParens(expr.argument);
990
+ if (needsParens) {
774
991
  context.write(" (");
775
992
  context.writeNode(expr.argument);
776
993
  context.write(")");
@@ -785,17 +1002,19 @@ function printSequenceExpression(
785
1002
  expr: AST.SequenceExpression,
786
1003
  context: PrinterContext,
787
1004
  ): void {
788
- context.write("(");
789
1005
  context.writeNodeList(expr.expressions, ", ");
790
- context.write(")");
791
1006
  }
792
1007
 
793
1008
  function printCallExpression(
794
1009
  expression: AST.CallExpression,
795
1010
  context: PrinterContext,
796
1011
  ): void {
797
- const calleePrec = EXPRESSIONS_PRECEDENCE[expression.callee.type] ?? 20;
798
- if (calleePrec < EXPRESSIONS_PRECEDENCE.CallExpression) {
1012
+ const needsParens = operandOfBinaryExprNeedsParens(
1013
+ expression.callee,
1014
+ expression,
1015
+ "left",
1016
+ );
1017
+ if (needsParens) {
799
1018
  context.write("(");
800
1019
  context.writeNode(expression.callee);
801
1020
  context.write(")");
@@ -815,7 +1034,7 @@ function printCallExpression(
815
1034
  } else {
816
1035
  context.write("(");
817
1036
  }
818
- context.writeNodeList(expression.arguments, ", ");
1037
+ context.writeExpressionListWithCommaSep(expression.arguments);
819
1038
  context.write(")");
820
1039
  }
821
1040
 
@@ -824,11 +1043,12 @@ function printNewExpression(
824
1043
  context: PrinterContext,
825
1044
  ): void {
826
1045
  context.write("new ");
827
- const calleePrec = EXPRESSIONS_PRECEDENCE[expression.callee.type] ?? 20;
828
- if (
829
- calleePrec < EXPRESSIONS_PRECEDENCE.NewExpression ||
830
- hasCallExpression(expression.callee)
831
- ) {
1046
+ const needsParens = operandOfBinaryExprNeedsParens(
1047
+ expression.callee,
1048
+ expression,
1049
+ "left",
1050
+ );
1051
+ if (needsParens) {
832
1052
  context.write("(");
833
1053
  context.writeNode(expression.callee);
834
1054
  context.write(")");
@@ -845,23 +1065,10 @@ function printNewExpression(
845
1065
  } else {
846
1066
  context.write("(");
847
1067
  }
848
- context.writeNodeList(expression.arguments, ", ");
1068
+ context.writeExpressionListWithCommaSep(expression.arguments);
849
1069
  context.write(")");
850
1070
  }
851
1071
 
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
1072
  function printChainExpression(
866
1073
  expression: AST.ChainExpression,
867
1074
  context: PrinterContext,
@@ -873,8 +1080,12 @@ function printMemberExpression(
873
1080
  expression: AST.MemberExpression,
874
1081
  context: PrinterContext,
875
1082
  ): void {
876
- const objPrec = EXPRESSIONS_PRECEDENCE[expression.object.type] ?? 20;
877
- if (objPrec < EXPRESSIONS_PRECEDENCE.MemberExpression) {
1083
+ const needsParens = operandOfBinaryExprNeedsParens(
1084
+ expression.object,
1085
+ expression,
1086
+ "left",
1087
+ );
1088
+ if (needsParens) {
878
1089
  context.write("(");
879
1090
  context.writeNode(expression.object);
880
1091
  context.write(")");
@@ -915,7 +1126,7 @@ function printArrayExpression(
915
1126
  context: PrinterContext,
916
1127
  ): void {
917
1128
  context.write("[");
918
- context.writeNodeList(array.elements, ", ");
1129
+ context.writeExpressionListWithCommaSep(array.elements);
919
1130
  context.write("]");
920
1131
  }
921
1132
 
@@ -996,7 +1207,14 @@ function printSpreadElement(
996
1207
  context: PrinterContext,
997
1208
  ): void {
998
1209
  context.write("...");
999
- context.writeNode(spread.argument);
1210
+ const needsParens = expectAssignmentExprNeedsParen(spread.argument);
1211
+ if (needsParens) {
1212
+ context.write("(");
1213
+ context.writeNode(spread.argument);
1214
+ context.write(")");
1215
+ } else {
1216
+ context.writeNode(spread.argument);
1217
+ }
1000
1218
  }
1001
1219
 
1002
1220
  function printRestElement(
@@ -1037,7 +1255,17 @@ function printTaggedTemplateExpression(
1037
1255
  node: AST.TaggedTemplateExpression,
1038
1256
  context: PrinterContext,
1039
1257
  ): void {
1040
- context.writeNode(node.tag);
1258
+ const needsParens = operandOfBinaryExprNeedsParens(node.tag, node, "left");
1259
+ if (needsParens) {
1260
+ context.write("(");
1261
+ context.writeNode(node.tag);
1262
+ context.write(")");
1263
+ } else {
1264
+ context.writeNode(node.tag);
1265
+ }
1266
+ if (node.typeArguments) {
1267
+ context.writeNode(node.typeArguments);
1268
+ }
1041
1269
  context.writeNode(node.quasi);
1042
1270
  }
1043
1271
 
@@ -1175,7 +1403,14 @@ function printClass(
1175
1403
  }
1176
1404
  if (node.superClass) {
1177
1405
  context.write("extends ");
1178
- context.writeNode(node.superClass);
1406
+ const needsParens = expectLHSExprNeedsParen(node.superClass);
1407
+ if (needsParens) {
1408
+ context.write("(");
1409
+ context.writeNode(node.superClass);
1410
+ context.write(")");
1411
+ } else {
1412
+ context.writeNode(node.superClass);
1413
+ }
1179
1414
  if (node.superTypeArguments) {
1180
1415
  context.writeNode(node.superTypeArguments);
1181
1416
  } else if (node.superTypeParameters) {
@@ -1197,7 +1432,7 @@ function printClassBody(node: AST.ClassBody, context: PrinterContext): void {
1197
1432
  const body = node.body;
1198
1433
  if (body.length > 0) {
1199
1434
  context.write("\n");
1200
- context.writeNodeListWithSourceGaps(body, "\n");
1435
+ context.writeNodeListWithNewLineSep(body);
1201
1436
  context.write("\n");
1202
1437
  }
1203
1438
  context.write("}");
@@ -1211,15 +1446,43 @@ function printStaticBlock(
1211
1446
  const body = node.body;
1212
1447
  if (body.length > 0) {
1213
1448
  context.write("\n");
1214
- context.writeNodeListWithSourceGaps(body, "\n");
1449
+ context.writeNodeListWithNewLineSep(body);
1215
1450
  context.write("\n");
1216
1451
  }
1217
1452
  context.write("}");
1218
1453
  }
1219
1454
 
1455
+ function validUnparenthesizedDecorator(node: AST.Expression): boolean {
1456
+ let current: AST.Expression = node;
1457
+ if (current.type === "CallExpression") {
1458
+ current = current.callee;
1459
+ }
1460
+ if (current.type === "TSInstantiationExpression") {
1461
+ current = current.expression;
1462
+ }
1463
+ while (true) {
1464
+ if (current.type === "Identifier") {
1465
+ return true;
1466
+ } else if (current.type === "MemberExpression") {
1467
+ if (current.computed) {
1468
+ return false;
1469
+ }
1470
+ current = current.object;
1471
+ } else {
1472
+ return false;
1473
+ }
1474
+ }
1475
+ }
1476
+
1220
1477
  function printDecorator(node: AST.Decorator, context: PrinterContext): void {
1221
1478
  context.write("@");
1222
- context.writeNode(node.expression);
1479
+ if (!validUnparenthesizedDecorator(node.expression)) {
1480
+ context.write("(");
1481
+ context.writeNode(node.expression);
1482
+ context.write(")");
1483
+ } else {
1484
+ context.writeNode(node.expression);
1485
+ }
1223
1486
  context.write("\n");
1224
1487
  }
1225
1488
 
@@ -1375,13 +1638,17 @@ function printImportDeclaration(
1375
1638
  }
1376
1639
 
1377
1640
  if (namespaceSpec) {
1378
- if (wroteDefault) context.write(", ");
1641
+ if (wroteDefault) {
1642
+ context.write(", ");
1643
+ }
1379
1644
  context.write("* as ");
1380
1645
  context.writeNode(namespaceSpec.local);
1381
1646
  }
1382
1647
 
1383
1648
  if (namedSpecs.length > 0) {
1384
- if (wroteDefault || namespaceSpec) context.write(", ");
1649
+ if (wroteDefault || namespaceSpec) {
1650
+ context.write(", ");
1651
+ }
1385
1652
  context.write("{ ");
1386
1653
  context.writeNodeList(namedSpecs, ", ");
1387
1654
  context.write(" }");
@@ -1546,8 +1813,12 @@ function printTSAsExpression(
1546
1813
  expression: AST.TSAsExpression,
1547
1814
  context: PrinterContext,
1548
1815
  ): void {
1549
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1550
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSAsExpression) {
1816
+ const needsParens = operandOfBinaryExprNeedsParens(
1817
+ expression.expression,
1818
+ expression,
1819
+ "left",
1820
+ );
1821
+ if (needsParens) {
1551
1822
  context.write("(");
1552
1823
  context.writeNode(expression.expression);
1553
1824
  context.write(")");
@@ -1562,8 +1833,12 @@ function printTSSatisfiesExpression(
1562
1833
  expression: AST.TSSatisfiesExpression,
1563
1834
  context: PrinterContext,
1564
1835
  ): void {
1565
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1566
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSSatisfiesExpression) {
1836
+ const needsParens = operandOfBinaryExprNeedsParens(
1837
+ expression.expression,
1838
+ expression,
1839
+ "left",
1840
+ );
1841
+ if (needsParens) {
1567
1842
  context.write("(");
1568
1843
  context.writeNode(expression.expression);
1569
1844
  context.write(")");
@@ -1581,8 +1856,8 @@ function printTSTypeAssertion(
1581
1856
  context.write("<");
1582
1857
  context.writeNode(expression.typeAnnotation);
1583
1858
  context.write(">");
1584
- const exprPrec = EXPRESSIONS_PRECEDENCE[expression.expression.type] ?? 20;
1585
- if (exprPrec < EXPRESSIONS_PRECEDENCE.TSTypeAssertion) {
1859
+ const needsParens = operandOfUnaryExprNeedsParens(expression.expression);
1860
+ if (needsParens) {
1586
1861
  context.write("(");
1587
1862
  context.writeNode(expression.expression);
1588
1863
  context.write(")");
@@ -1595,7 +1870,18 @@ function printTSNonNullExpression(
1595
1870
  expression: AST.TSNonNullExpression,
1596
1871
  context: PrinterContext,
1597
1872
  ): void {
1598
- context.writeNode(expression.expression);
1873
+ const needsParens = operandOfBinaryExprNeedsParens(
1874
+ expression.expression,
1875
+ expression,
1876
+ "left",
1877
+ );
1878
+ if (needsParens) {
1879
+ context.write("(");
1880
+ context.writeNode(expression.expression);
1881
+ context.write(")");
1882
+ } else {
1883
+ context.writeNode(expression.expression);
1884
+ }
1599
1885
  context.write("!");
1600
1886
  }
1601
1887
 
@@ -1991,7 +2277,14 @@ function printTSMappedType(
1991
2277
  node: AST.TSMappedType,
1992
2278
  context: PrinterContext,
1993
2279
  ): void {
1994
- context.write("{ [");
2280
+ context.write("{ ");
2281
+ if (node.readonly) {
2282
+ if (typeof node.readonly === "string") {
2283
+ context.write(node.readonly);
2284
+ }
2285
+ context.write("readonly ");
2286
+ }
2287
+ context.write("[");
1995
2288
  const legacyTp = node.typeParameter;
1996
2289
  const key = node.key ?? legacyTp?.name;
1997
2290
  const constraint = node.constraint ?? legacyTp?.constraint;
@@ -2007,7 +2300,17 @@ function printTSMappedType(
2007
2300
  context.write(" in ");
2008
2301
  context.writeNode(constraint);
2009
2302
  }
2303
+ if (node.nameType) {
2304
+ context.write(" as ");
2305
+ context.writeNode(node.nameType);
2306
+ }
2010
2307
  context.write("]");
2308
+ if (node.optional) {
2309
+ if (typeof node.optional === "string") {
2310
+ context.write(node.optional);
2311
+ }
2312
+ context.write("?");
2313
+ }
2011
2314
  if (node.typeAnnotation) {
2012
2315
  context.write(": ");
2013
2316
  context.writeNode(node.typeAnnotation);
@@ -2164,11 +2467,18 @@ function printTSModuleDeclaration(
2164
2467
  context.write(String(kind) + " ");
2165
2468
  context.writeNode(node.id);
2166
2469
  }
2167
- if (node.body) {
2168
- if (node.body.type === "TSModuleBlock") {
2169
- context.write(" ");
2170
- }
2171
- context.writeNode(node.body);
2470
+ let body = node.body as
2471
+ | AST.TSModuleDeclaration
2472
+ | AST.TSModuleBlock
2473
+ | undefined;
2474
+ while (body?.type === "TSModuleDeclaration") {
2475
+ context.write(".");
2476
+ context.writeNode(body.id);
2477
+ body = body.body;
2478
+ }
2479
+ if (body) {
2480
+ context.write(" ");
2481
+ context.writeNode(body);
2172
2482
  }
2173
2483
  }
2174
2484
 
@@ -2177,7 +2487,7 @@ function printTSModuleBlock(
2177
2487
  context: PrinterContext,
2178
2488
  ): void {
2179
2489
  context.write("{\n");
2180
- context.writeNodeListWithSourceGaps(node.body, "\n");
2490
+ context.writeNodeListWithNewLineSep(node.body);
2181
2491
  context.write("\n}");
2182
2492
  }
2183
2493
 
@@ -2239,10 +2549,21 @@ function printTSNamespaceExportDeclaration(
2239
2549
  }
2240
2550
 
2241
2551
  function printTSInstantiationExpression(
2242
- node: { expression: AST.Node; typeArguments: AST.Node },
2552
+ node: AST.TSInstantiationExpression,
2243
2553
  context: PrinterContext,
2244
2554
  ): void {
2245
- context.writeNode(node.expression);
2555
+ const needsParens = operandOfBinaryExprNeedsParens(
2556
+ node.expression,
2557
+ node,
2558
+ "left",
2559
+ );
2560
+ if (needsParens) {
2561
+ context.write("(");
2562
+ context.writeNode(node.expression);
2563
+ context.write(")");
2564
+ } else {
2565
+ context.writeNode(node.expression);
2566
+ }
2246
2567
  context.writeNode(node.typeArguments);
2247
2568
  }
2248
2569
 
@@ -2274,3 +2595,14 @@ function writeOptionalTypeAnnotation(
2274
2595
  context.writeNode(node.typeAnnotation);
2275
2596
  }
2276
2597
  }
2598
+
2599
+ export function writeComment(comment: Comment, context: PrinterContext): void {
2600
+ if (comment.type === "Line") {
2601
+ context.write("//" + comment.value + "\n");
2602
+ } else {
2603
+ context.write("/*" + comment.value + "*/");
2604
+ if (comment.value.includes("\n")) {
2605
+ context.write("\n");
2606
+ }
2607
+ }
2608
+ }