esrap 1.0.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.
@@ -0,0 +1,1373 @@
1
+ // heavily based on https://github.com/davidbonnet/astring
2
+ // released under MIT license https://github.com/davidbonnet/astring/blob/master/LICENSE
3
+
4
+ /**
5
+ * Does `array.push` for all `items`. Needed because `array.push(...items)` throws
6
+ * "Maximum call stack size exceeded" when `items` is too big of an array.
7
+ *
8
+ * @param {any[]} array
9
+ * @param {any[]} items
10
+ */
11
+ function push_array(array, items) {
12
+ for (let i = 0; i < items.length; i++) {
13
+ array.push(items[i]);
14
+ }
15
+ }
16
+
17
+ /**
18
+ * @param {import('estree').Node} node
19
+ * @param {import('./types').State} state
20
+ * @returns {import('./types').Chunk[]}
21
+ */
22
+ export function handle(node, state) {
23
+ const handler = handlers[node.type];
24
+
25
+ if (!handler) {
26
+ throw new Error(`Not implemented ${node.type}`);
27
+ }
28
+
29
+ // @ts-expect-error
30
+ const result = handler(node, state);
31
+
32
+ if (node.leadingComments) {
33
+ result.unshift(
34
+ c(
35
+ node.leadingComments
36
+ .map((comment) =>
37
+ comment.type === 'Block'
38
+ ? `/*${comment.value}*/${
39
+ /** @type {any} */ (comment).has_trailing_newline ? `\n${state.indent}` : ` `
40
+ }`
41
+ : `//${comment.value}${
42
+ /** @type {any} */ (comment).has_trailing_newline ? `\n${state.indent}` : ` `
43
+ }`
44
+ )
45
+ .join(``)
46
+ )
47
+ );
48
+ }
49
+
50
+ if (node.trailingComments) {
51
+ state.comments.push(node.trailingComments[0]); // there is only ever one
52
+ }
53
+
54
+ return result;
55
+ }
56
+
57
+ /**
58
+ * @param {string} content
59
+ * @param {import('estree').Node} [node]
60
+ * @returns {import('./types').Chunk}
61
+ */
62
+ function c(content, node) {
63
+ return {
64
+ content,
65
+ loc: node?.loc ?? null,
66
+ has_newline: /\n/.test(content)
67
+ };
68
+ }
69
+
70
+ const OPERATOR_PRECEDENCE = {
71
+ '||': 2,
72
+ '&&': 3,
73
+ '??': 4,
74
+ '|': 5,
75
+ '^': 6,
76
+ '&': 7,
77
+ '==': 8,
78
+ '!=': 8,
79
+ '===': 8,
80
+ '!==': 8,
81
+ '<': 9,
82
+ '>': 9,
83
+ '<=': 9,
84
+ '>=': 9,
85
+ in: 9,
86
+ instanceof: 9,
87
+ '<<': 10,
88
+ '>>': 10,
89
+ '>>>': 10,
90
+ '+': 11,
91
+ '-': 11,
92
+ '*': 12,
93
+ '%': 12,
94
+ '/': 12,
95
+ '**': 13
96
+ };
97
+
98
+ /** @type {Record<import('estree').Expression['type'] | 'Super' | 'RestElement', number>} */
99
+ const EXPRESSIONS_PRECEDENCE = {
100
+ ArrayExpression: 20,
101
+ TaggedTemplateExpression: 20,
102
+ ThisExpression: 20,
103
+ Identifier: 20,
104
+ Literal: 18,
105
+ TemplateLiteral: 20,
106
+ Super: 20,
107
+ SequenceExpression: 20,
108
+ MemberExpression: 19,
109
+ MetaProperty: 19,
110
+ CallExpression: 19,
111
+ ChainExpression: 19,
112
+ ImportExpression: 19,
113
+ NewExpression: 19,
114
+ AwaitExpression: 17,
115
+ ClassExpression: 17,
116
+ FunctionExpression: 17,
117
+ ObjectExpression: 17,
118
+ UpdateExpression: 16,
119
+ UnaryExpression: 15,
120
+ BinaryExpression: 14,
121
+ LogicalExpression: 13,
122
+ ConditionalExpression: 4,
123
+ ArrowFunctionExpression: 3,
124
+ AssignmentExpression: 3,
125
+ YieldExpression: 2,
126
+ RestElement: 1
127
+ };
128
+
129
+ /**
130
+ *
131
+ * @param {import('estree').Expression} node
132
+ * @param {import('estree').BinaryExpression | import('estree').LogicalExpression} parent
133
+ * @param {boolean} is_right
134
+ * @returns
135
+ */
136
+ function needs_parens(node, parent, is_right) {
137
+ // special case where logical expressions and coalesce expressions cannot be mixed,
138
+ // either of them need to be wrapped with parentheses
139
+ if (
140
+ node.type === 'LogicalExpression' &&
141
+ parent.type === 'LogicalExpression' &&
142
+ ((parent.operator === '??' && node.operator !== '??') ||
143
+ (parent.operator !== '??' && node.operator === '??'))
144
+ ) {
145
+ return true;
146
+ }
147
+
148
+ const precedence = EXPRESSIONS_PRECEDENCE[node.type];
149
+ const parent_precedence = EXPRESSIONS_PRECEDENCE[parent.type];
150
+
151
+ if (precedence !== parent_precedence) {
152
+ // Different node types
153
+ return (
154
+ (!is_right && precedence === 15 && parent_precedence === 14 && parent.operator === '**') ||
155
+ precedence < parent_precedence
156
+ );
157
+ }
158
+
159
+ if (precedence !== 13 && precedence !== 14) {
160
+ // Not a `LogicalExpression` or `BinaryExpression`
161
+ return false;
162
+ }
163
+
164
+ if (
165
+ /** @type {import('estree').BinaryExpression} */ (node).operator === '**' &&
166
+ parent.operator === '**'
167
+ ) {
168
+ // Exponentiation operator has right-to-left associativity
169
+ return !is_right;
170
+ }
171
+
172
+ if (is_right) {
173
+ // Parenthesis are used if both operators have the same precedence
174
+ return (
175
+ OPERATOR_PRECEDENCE[/** @type {import('estree').BinaryExpression} */ (node).operator] <=
176
+ OPERATOR_PRECEDENCE[parent.operator]
177
+ );
178
+ }
179
+
180
+ return (
181
+ OPERATOR_PRECEDENCE[/** @type {import('estree').BinaryExpression} */ (node).operator] <
182
+ OPERATOR_PRECEDENCE[parent.operator]
183
+ );
184
+ }
185
+
186
+ /** @param {import('estree').Node} node */
187
+ function has_call_expression(node) {
188
+ while (node) {
189
+ if (node.type[0] === 'CallExpression') {
190
+ return true;
191
+ } else if (node.type === 'MemberExpression') {
192
+ node = node.object;
193
+ } else {
194
+ return false;
195
+ }
196
+ }
197
+ }
198
+
199
+ /** @param {import('./types').Chunk[]} chunks */
200
+ const has_newline = (chunks) => {
201
+ for (let i = 0; i < chunks.length; i += 1) {
202
+ if (chunks[i].has_newline) return true;
203
+ }
204
+ return false;
205
+ };
206
+
207
+ /** @param {import('./types').Chunk[]} chunks */
208
+ const get_length = (chunks) => {
209
+ let total = 0;
210
+ for (let i = 0; i < chunks.length; i += 1) {
211
+ total += chunks[i].content.length;
212
+ }
213
+ return total;
214
+ };
215
+
216
+ /**
217
+ * @param {number} a
218
+ * @param {number} b
219
+ */
220
+ const sum = (a, b) => a + b;
221
+
222
+ /**
223
+ * @param {import('./types').Chunk[][]} nodes
224
+ * @param {import('./types').Chunk} separator
225
+ * @returns {import('./types').Chunk[]}
226
+ */
227
+ const join = (nodes, separator) => {
228
+ if (nodes.length === 0) return [];
229
+
230
+ const joined = [...nodes[0]];
231
+ for (let i = 1; i < nodes.length; i += 1) {
232
+ joined.push(separator);
233
+ push_array(joined, nodes[i]);
234
+ }
235
+ return joined;
236
+ };
237
+
238
+ /**
239
+ * @param {import('estree').Node[]} nodes
240
+ * @param {import('./types').State} state
241
+ */
242
+ const handle_body = (nodes, state) => {
243
+ const chunks = [];
244
+
245
+ const body = nodes
246
+ .filter((statement) => statement.type !== 'EmptyStatement')
247
+ .map((statement) => {
248
+ const chunks = handle(statement, {
249
+ ...state,
250
+ indent: state.indent
251
+ });
252
+
253
+ let add_newline = false;
254
+
255
+ while (state.comments.length) {
256
+ const comment = /** @type {import('estree').Comment} */ (state.comments.shift());
257
+ const prefix = add_newline ? `\n${state.indent}` : ` `;
258
+
259
+ chunks.push(
260
+ c(
261
+ comment.type === 'Block'
262
+ ? `${prefix}/*${comment.value}*/`
263
+ : `${prefix}//${comment.value}`
264
+ )
265
+ );
266
+
267
+ add_newline = comment.type === 'Line';
268
+ }
269
+
270
+ return chunks;
271
+ });
272
+
273
+ let needed_padding = false;
274
+
275
+ for (let i = 0; i < body.length; i += 1) {
276
+ const needs_padding = has_newline(body[i]);
277
+
278
+ if (i > 0) {
279
+ chunks.push(c(needs_padding || needed_padding ? `\n\n${state.indent}` : `\n${state.indent}`));
280
+ }
281
+
282
+ push_array(chunks, body[i]);
283
+
284
+ needed_padding = needs_padding;
285
+ }
286
+
287
+ return chunks;
288
+ };
289
+
290
+ /**
291
+ * @param {import('estree').VariableDeclaration} node
292
+ * @param {import('./types').State} state
293
+ */
294
+ const handle_var_declaration = (node, state) => {
295
+ const chunks = [c(`${node.kind} `)];
296
+
297
+ const declarators = node.declarations.map((d) =>
298
+ handle(d, {
299
+ ...state,
300
+ indent: state.indent + (node.declarations.length === 1 ? '' : '\t')
301
+ })
302
+ );
303
+
304
+ const multiple_lines =
305
+ declarators.some(has_newline) ||
306
+ declarators.map(get_length).reduce(sum, 0) +
307
+ (state.indent.length + declarators.length - 1) * 2 >
308
+ 80;
309
+
310
+ const separator = c(multiple_lines ? `,\n${state.indent}\t` : ', ');
311
+
312
+ push_array(chunks, join(declarators, separator));
313
+
314
+ return chunks;
315
+ };
316
+
317
+ const shared = {
318
+ /**
319
+ * @param {import('estree').ArrayExpression | import('estree').ArrayPattern} node
320
+ * @param {import('./types').State} state
321
+ */
322
+ 'ArrayExpression|ArrayPattern': (node, state) => {
323
+ const chunks = [c('[')];
324
+
325
+ /** @type {import('./types').Chunk[][]} */
326
+ const elements = [];
327
+
328
+ /** @type {import('./types').Chunk[]} */
329
+ let sparse_commas = [];
330
+
331
+ for (let i = 0; i < node.elements.length; i += 1) {
332
+ // can't use map/forEach because of sparse arrays
333
+ const element = node.elements[i];
334
+ if (element) {
335
+ elements.push([
336
+ ...sparse_commas,
337
+ ...handle(element, {
338
+ ...state,
339
+ indent: state.indent + '\t'
340
+ })
341
+ ]);
342
+ sparse_commas = [];
343
+ } else {
344
+ sparse_commas.push(c(','));
345
+ }
346
+ }
347
+
348
+ const multiple_lines =
349
+ elements.some(has_newline) ||
350
+ elements.map(get_length).reduce(sum, 0) + (state.indent.length + elements.length - 1) * 2 >
351
+ 80;
352
+
353
+ if (multiple_lines) {
354
+ chunks.push(c(`\n${state.indent}\t`));
355
+ push_array(chunks, join(elements, c(`,\n${state.indent}\t`)));
356
+ chunks.push(c(`\n${state.indent}`));
357
+ push_array(chunks, sparse_commas);
358
+ } else {
359
+ push_array(chunks, join(elements, c(', ')));
360
+ push_array(chunks, sparse_commas);
361
+ }
362
+
363
+ chunks.push(c(']'));
364
+
365
+ return chunks;
366
+ },
367
+
368
+ /**
369
+ * @param {import('estree').BinaryExpression | import('estree').LogicalExpression} node
370
+ * @param {import('./types').State} state
371
+ */
372
+ 'BinaryExpression|LogicalExpression': (node, state) => {
373
+ /**
374
+ * @type any[]
375
+ */
376
+ const chunks = [];
377
+
378
+ // TODO
379
+ // const is_in = node.operator === 'in';
380
+ // if (is_in) {
381
+ // // Avoids confusion in `for` loops initializers
382
+ // chunks.push(c('('));
383
+ // }
384
+
385
+ if (needs_parens(node.left, node, false)) {
386
+ chunks.push(c('('));
387
+ push_array(chunks, handle(node.left, state));
388
+ chunks.push(c(')'));
389
+ } else {
390
+ push_array(chunks, handle(node.left, state));
391
+ }
392
+
393
+ chunks.push(c(` ${node.operator} `));
394
+
395
+ if (needs_parens(node.right, node, true)) {
396
+ chunks.push(c('('));
397
+ push_array(chunks, handle(node.right, state));
398
+ chunks.push(c(')'));
399
+ } else {
400
+ push_array(chunks, handle(node.right, state));
401
+ }
402
+
403
+ return chunks;
404
+ },
405
+
406
+ /**
407
+ * @param {import('estree').BlockStatement | import('estree').ClassBody} node
408
+ * @param {import('./types').State} state
409
+ */
410
+ 'BlockStatement|ClassBody': (node, state) => {
411
+ return [
412
+ c(`{\n${state.indent}\t`),
413
+ ...handle_body(node.body, { ...state, indent: state.indent + '\t' }),
414
+ c(`\n${state.indent}}`)
415
+ ];
416
+ },
417
+
418
+ /**
419
+ * @param {import('estree').ClassDeclaration | import('estree').ClassExpression} node
420
+ * @param {import('./types').State} state
421
+ */
422
+ 'ClassDeclaration|ClassExpression': (node, state) => {
423
+ const chunks = [c('class ')];
424
+
425
+ if (node.id) {
426
+ push_array(chunks, handle(node.id, state));
427
+ chunks.push(c(' '));
428
+ }
429
+
430
+ if (node.superClass) {
431
+ chunks.push(c('extends '));
432
+ push_array(chunks, handle(node.superClass, state));
433
+ chunks.push(c(' '));
434
+ }
435
+
436
+ push_array(chunks, handle(node.body, state));
437
+
438
+ return chunks;
439
+ },
440
+
441
+ /**
442
+ * @param {import('estree').ForInStatement | import('estree').ForOfStatement} node
443
+ * @param {import('./types').State} state
444
+ */
445
+ 'ForInStatement|ForOfStatement': (node, state) => {
446
+ const chunks = [c(`for ${node.type === 'ForOfStatement' && node.await ? 'await ' : ''}(`)];
447
+
448
+ if (node.left.type === 'VariableDeclaration') {
449
+ push_array(chunks, handle_var_declaration(node.left, state));
450
+ } else {
451
+ push_array(chunks, handle(node.left, state));
452
+ }
453
+
454
+ chunks.push(c(node.type === 'ForInStatement' ? ` in ` : ` of `));
455
+ push_array(chunks, handle(node.right, state));
456
+ chunks.push(c(') '));
457
+ push_array(chunks, handle(node.body, state));
458
+
459
+ return chunks;
460
+ },
461
+
462
+ /**
463
+ * @param {import('estree').FunctionDeclaration | import('estree').FunctionExpression} node
464
+ * @param {import('./types').State} state
465
+ */
466
+ 'FunctionDeclaration|FunctionExpression': (node, state) => {
467
+ const chunks = [];
468
+
469
+ if (node.async) chunks.push(c('async '));
470
+ chunks.push(c(node.generator ? 'function* ' : 'function '));
471
+ if (node.id) push_array(chunks, handle(node.id, state));
472
+ chunks.push(c('('));
473
+
474
+ const params = node.params.map((p) =>
475
+ handle(p, {
476
+ ...state,
477
+ indent: state.indent + '\t'
478
+ })
479
+ );
480
+
481
+ const multiple_lines =
482
+ params.some(has_newline) ||
483
+ params.map(get_length).reduce(sum, 0) + (state.indent.length + params.length - 1) * 2 > 80;
484
+
485
+ const separator = c(multiple_lines ? `,\n${state.indent}` : ', ');
486
+
487
+ if (multiple_lines) {
488
+ chunks.push(c(`\n${state.indent}\t`));
489
+ push_array(chunks, join(params, separator));
490
+ chunks.push(c(`\n${state.indent}`));
491
+ } else {
492
+ push_array(chunks, join(params, separator));
493
+ }
494
+
495
+ chunks.push(c(') '));
496
+ push_array(chunks, handle(node.body, state));
497
+
498
+ return chunks;
499
+ },
500
+
501
+ /**
502
+ * @param {import('estree').RestElement | import('estree').SpreadElement} node
503
+ * @param {import('./types').State} state
504
+ */
505
+ 'RestElement|SpreadElement': (node, state) => {
506
+ return [c('...'), ...handle(node.argument, state)];
507
+ }
508
+ };
509
+
510
+ /** @type {import('./types').Handlers} */
511
+ const handlers = {
512
+ ArrayExpression: shared['ArrayExpression|ArrayPattern'],
513
+
514
+ ArrayPattern: shared['ArrayExpression|ArrayPattern'],
515
+
516
+ ArrowFunctionExpression: (node, state) => {
517
+ const chunks = [];
518
+
519
+ if (node.async) chunks.push(c('async '));
520
+
521
+ if (node.params.length === 1 && node.params[0].type === 'Identifier') {
522
+ push_array(chunks, handle(node.params[0], state));
523
+ } else {
524
+ const params = node.params.map((param) =>
525
+ handle(param, {
526
+ ...state,
527
+ indent: state.indent + '\t'
528
+ })
529
+ );
530
+
531
+ chunks.push(c('('));
532
+ push_array(chunks, join(params, c(', ')));
533
+ chunks.push(c(')'));
534
+ }
535
+
536
+ chunks.push(c(' => '));
537
+
538
+ if (
539
+ node.body.type === 'ObjectExpression' ||
540
+ (node.body.type === 'AssignmentExpression' && node.body.left.type === 'ObjectPattern')
541
+ ) {
542
+ chunks.push(c('('));
543
+ push_array(chunks, handle(node.body, state));
544
+ chunks.push(c(')'));
545
+ } else {
546
+ push_array(chunks, handle(node.body, state));
547
+ }
548
+
549
+ return chunks;
550
+ },
551
+
552
+ AssignmentExpression(node, state) {
553
+ return [...handle(node.left, state), c(` ${node.operator} `), ...handle(node.right, state)];
554
+ },
555
+
556
+ AssignmentPattern(node, state) {
557
+ return [...handle(node.left, state), c(` = `), ...handle(node.right, state)];
558
+ },
559
+
560
+ AwaitExpression(node, state) {
561
+ if (node.argument) {
562
+ const precedence = EXPRESSIONS_PRECEDENCE[node.argument.type];
563
+
564
+ if (precedence && precedence < EXPRESSIONS_PRECEDENCE.AwaitExpression) {
565
+ return [c('await ('), ...handle(node.argument, state), c(')')];
566
+ } else {
567
+ return [c('await '), ...handle(node.argument, state)];
568
+ }
569
+ }
570
+
571
+ return [c('await')];
572
+ },
573
+
574
+ BinaryExpression: shared['BinaryExpression|LogicalExpression'],
575
+
576
+ BlockStatement: shared['BlockStatement|ClassBody'],
577
+
578
+ BreakStatement(node, state) {
579
+ return node.label ? [c('break '), ...handle(node.label, state), c(';')] : [c('break;')];
580
+ },
581
+
582
+ CallExpression(node, state) {
583
+ /**
584
+ * @type any[]
585
+ */
586
+ const chunks = [];
587
+
588
+ if (EXPRESSIONS_PRECEDENCE[node.callee.type] < EXPRESSIONS_PRECEDENCE.CallExpression) {
589
+ chunks.push(c('('));
590
+ push_array(chunks, handle(node.callee, state));
591
+ chunks.push(c(')'));
592
+ } else {
593
+ push_array(chunks, handle(node.callee, state));
594
+ }
595
+
596
+ if (/** @type {import('estree').SimpleCallExpression} */ (node).optional) {
597
+ chunks.push(c('?.'));
598
+ }
599
+
600
+ let has_inline_comment = false;
601
+ let arg_chunks = [];
602
+ outer: for (const arg of node.arguments) {
603
+ const chunks = [];
604
+ while (state.comments.length) {
605
+ const comment = /** @type {import('estree').Comment} */ (state.comments.shift());
606
+ if (comment.type === 'Line') {
607
+ has_inline_comment = true;
608
+ break outer;
609
+ }
610
+ chunks.push(c(comment.type === 'Block' ? `/*${comment.value}*/ ` : `//${comment.value}`));
611
+ }
612
+ push_array(chunks, handle(arg, state));
613
+ arg_chunks.push(chunks);
614
+ }
615
+
616
+ const multiple_lines = has_inline_comment || arg_chunks.slice(0, -1).some(has_newline); // TODO or length exceeds 80
617
+ if (multiple_lines) {
618
+ // need to handle args again. TODO find alternative approach?
619
+ const args = node.arguments.map((arg, i) => {
620
+ const chunks = handle(arg, {
621
+ ...state,
622
+ indent: `${state.indent}\t`
623
+ });
624
+ if (i < node.arguments.length - 1) chunks.push(c(','));
625
+ while (state.comments.length) {
626
+ const comment = /** @type {import('estree').Comment} */ (state.comments.shift());
627
+ chunks.push(
628
+ c(comment.type === 'Block' ? ` /*${comment.value}*/ ` : ` //${comment.value}`)
629
+ );
630
+ }
631
+ return chunks;
632
+ });
633
+
634
+ chunks.push(c(`(\n${state.indent}\t`));
635
+ push_array(chunks, join(args, c(`\n${state.indent}\t`)));
636
+ chunks.push(c(`\n${state.indent})`));
637
+ } else {
638
+ chunks.push(c('('));
639
+ push_array(chunks, join(arg_chunks, c(', ')));
640
+ chunks.push(c(')'));
641
+ }
642
+
643
+ return chunks;
644
+ },
645
+
646
+ ChainExpression(node, state) {
647
+ return handle(node.expression, state);
648
+ },
649
+
650
+ ClassBody: shared['BlockStatement|ClassBody'],
651
+
652
+ ClassDeclaration: shared['ClassDeclaration|ClassExpression'],
653
+
654
+ ClassExpression: shared['ClassDeclaration|ClassExpression'],
655
+
656
+ ConditionalExpression(node, state) {
657
+ /**
658
+ * @type any[]
659
+ */
660
+ const chunks = [];
661
+
662
+ if (EXPRESSIONS_PRECEDENCE[node.test.type] > EXPRESSIONS_PRECEDENCE.ConditionalExpression) {
663
+ push_array(chunks, handle(node.test, state));
664
+ } else {
665
+ chunks.push(c('('));
666
+ push_array(chunks, handle(node.test, state));
667
+ chunks.push(c(')'));
668
+ }
669
+
670
+ const child_state = { ...state, indent: state.indent + '\t' };
671
+
672
+ const consequent = handle(node.consequent, child_state);
673
+ const alternate = handle(node.alternate, child_state);
674
+
675
+ const multiple_lines =
676
+ has_newline(consequent) ||
677
+ has_newline(alternate) ||
678
+ get_length(chunks) + get_length(consequent) + get_length(alternate) > 50;
679
+
680
+ if (multiple_lines) {
681
+ chunks.push(c(`\n${state.indent}? `));
682
+ push_array(chunks, consequent);
683
+ chunks.push(c(`\n${state.indent}: `));
684
+ push_array(chunks, alternate);
685
+ } else {
686
+ chunks.push(c(` ? `));
687
+ push_array(chunks, consequent);
688
+ chunks.push(c(` : `));
689
+ push_array(chunks, alternate);
690
+ }
691
+
692
+ return chunks;
693
+ },
694
+
695
+ ContinueStatement(node, state) {
696
+ return node.label ? [c('continue '), ...handle(node.label, state), c(';')] : [c('continue;')];
697
+ },
698
+
699
+ DebuggerStatement(node, state) {
700
+ return [c('debugger', node), c(';')];
701
+ },
702
+
703
+ DoWhileStatement(node, state) {
704
+ return [
705
+ c('do '),
706
+ ...handle(node.body, state),
707
+ c(' while ('),
708
+ ...handle(node.test, state),
709
+ c(');')
710
+ ];
711
+ },
712
+
713
+ EmptyStatement(node, state) {
714
+ return [c(';')];
715
+ },
716
+
717
+ ExportAllDeclaration(node, state) {
718
+ return [c(`export * from `), ...handle(node.source, state), c(`;`)];
719
+ },
720
+
721
+ ExportDefaultDeclaration(node, state) {
722
+ const chunks = [c(`export default `), ...handle(node.declaration, state)];
723
+
724
+ if (node.declaration.type !== 'FunctionDeclaration') {
725
+ chunks.push(c(';'));
726
+ }
727
+
728
+ return chunks;
729
+ },
730
+
731
+ ExportNamedDeclaration(node, state) {
732
+ const chunks = [c('export ')];
733
+
734
+ if (node.declaration) {
735
+ push_array(chunks, handle(node.declaration, state));
736
+ } else {
737
+ const specifiers = node.specifiers.map((specifier) => {
738
+ const name = handle(specifier.local, state)[0];
739
+ const as = handle(specifier.exported, state)[0];
740
+
741
+ if (name.content === as.content) {
742
+ return [name];
743
+ }
744
+
745
+ return [name, c(' as '), as];
746
+ });
747
+
748
+ const width = 7 + specifiers.map(get_length).reduce(sum, 0) + 2 * specifiers.length;
749
+
750
+ if (width > 80) {
751
+ chunks.push(c('{\n\t'));
752
+ push_array(chunks, join(specifiers, c(',\n\t')));
753
+ chunks.push(c('\n}'));
754
+ } else {
755
+ chunks.push(c('{ '));
756
+ push_array(chunks, join(specifiers, c(', ')));
757
+ chunks.push(c(' }'));
758
+ }
759
+
760
+ if (node.source) {
761
+ chunks.push(c(' from '));
762
+ push_array(chunks, handle(node.source, state));
763
+ }
764
+ }
765
+
766
+ chunks.push(c(';'));
767
+
768
+ return chunks;
769
+ },
770
+
771
+ ExpressionStatement(node, state) {
772
+ if (
773
+ node.expression.type === 'AssignmentExpression' &&
774
+ node.expression.left.type === 'ObjectPattern'
775
+ ) {
776
+ // is an AssignmentExpression to an ObjectPattern
777
+ return [c('('), ...handle(node.expression, state), c(');')];
778
+ }
779
+
780
+ return [...handle(node.expression, state), c(';')];
781
+ },
782
+
783
+ ForStatement: (node, state) => {
784
+ const chunks = [c('for (')];
785
+
786
+ if (node.init) {
787
+ if (node.init.type === 'VariableDeclaration') {
788
+ push_array(chunks, handle_var_declaration(node.init, state));
789
+ } else {
790
+ push_array(chunks, handle(node.init, state));
791
+ }
792
+ }
793
+
794
+ chunks.push(c('; '));
795
+ if (node.test) push_array(chunks, handle(node.test, state));
796
+ chunks.push(c('; '));
797
+ if (node.update) push_array(chunks, handle(node.update, state));
798
+
799
+ chunks.push(c(') '));
800
+ push_array(chunks, handle(node.body, state));
801
+
802
+ return chunks;
803
+ },
804
+
805
+ ForInStatement: shared['ForInStatement|ForOfStatement'],
806
+
807
+ ForOfStatement: shared['ForInStatement|ForOfStatement'],
808
+
809
+ FunctionDeclaration: shared['FunctionDeclaration|FunctionExpression'],
810
+
811
+ FunctionExpression: shared['FunctionDeclaration|FunctionExpression'],
812
+
813
+ Identifier(node) {
814
+ let name = node.name;
815
+ return [c(name, node)];
816
+ },
817
+
818
+ IfStatement(node, state) {
819
+ const chunks = [
820
+ c('if ('),
821
+ ...handle(node.test, state),
822
+ c(') '),
823
+ ...handle(node.consequent, state)
824
+ ];
825
+
826
+ if (node.alternate) {
827
+ chunks.push(c(' else '));
828
+ push_array(chunks, handle(node.alternate, state));
829
+ }
830
+
831
+ return chunks;
832
+ },
833
+
834
+ ImportDeclaration(node, state) {
835
+ const chunks = [c('import ')];
836
+
837
+ const { length } = node.specifiers;
838
+ const source = handle(node.source, state);
839
+
840
+ if (length > 0) {
841
+ let i = 0;
842
+
843
+ while (i < length) {
844
+ if (i > 0) {
845
+ chunks.push(c(', '));
846
+ }
847
+
848
+ const specifier = node.specifiers[i];
849
+
850
+ if (specifier.type === 'ImportDefaultSpecifier') {
851
+ chunks.push(c(specifier.local.name, specifier));
852
+ i += 1;
853
+ } else if (specifier.type === 'ImportNamespaceSpecifier') {
854
+ chunks.push(c('* as ' + specifier.local.name, specifier));
855
+ i += 1;
856
+ } else {
857
+ break;
858
+ }
859
+ }
860
+
861
+ if (i < length) {
862
+ // we have named specifiers
863
+ const specifiers = /** @type {import('estree').ImportSpecifier[]} */ (node.specifiers)
864
+ .slice(i)
865
+ .map((specifier) => {
866
+ const name = handle(specifier.imported, state)[0];
867
+ const as = handle(specifier.local, state)[0];
868
+
869
+ if (name.content === as.content) {
870
+ return [as];
871
+ }
872
+
873
+ return [name, c(' as '), as];
874
+ });
875
+
876
+ const width =
877
+ get_length(chunks) +
878
+ specifiers.map(get_length).reduce(sum, 0) +
879
+ 2 * specifiers.length +
880
+ 6 +
881
+ get_length(source);
882
+
883
+ if (width > 80) {
884
+ chunks.push(c(`{\n\t`));
885
+ push_array(chunks, join(specifiers, c(',\n\t')));
886
+ chunks.push(c('\n}'));
887
+ } else {
888
+ chunks.push(c(`{ `));
889
+ push_array(chunks, join(specifiers, c(', ')));
890
+ chunks.push(c(' }'));
891
+ }
892
+ }
893
+
894
+ chunks.push(c(' from '));
895
+ }
896
+
897
+ push_array(chunks, source);
898
+ chunks.push(c(';'));
899
+
900
+ return chunks;
901
+ },
902
+
903
+ ImportExpression(node, state) {
904
+ return [c('import('), ...handle(node.source, state), c(')')];
905
+ },
906
+
907
+ LabeledStatement(node, state) {
908
+ return [...handle(node.label, state), c(': '), ...handle(node.body, state)];
909
+ },
910
+
911
+ Literal(node, state) {
912
+ if (typeof node.value === 'string') {
913
+ return [
914
+ // TODO do we need to handle weird unicode characters somehow?
915
+ // str.replace(/\\u(\d{4})/g, (m, n) => String.fromCharCode(+n))
916
+ c(node.raw || JSON.stringify(node.value), node)
917
+ ];
918
+ }
919
+
920
+ return [c(node.raw || String(node.value), node)];
921
+ },
922
+
923
+ LogicalExpression: shared['BinaryExpression|LogicalExpression'],
924
+
925
+ MemberExpression(node, state) {
926
+ /**
927
+ * @type any[]
928
+ */
929
+ const chunks = [];
930
+
931
+ if (EXPRESSIONS_PRECEDENCE[node.object.type] < EXPRESSIONS_PRECEDENCE.MemberExpression) {
932
+ chunks.push(c('('));
933
+ push_array(chunks, handle(node.object, state));
934
+ chunks.push(c(')'));
935
+ } else {
936
+ push_array(chunks, handle(node.object, state));
937
+ }
938
+
939
+ if (node.computed) {
940
+ if (node.optional) {
941
+ chunks.push(c('?.'));
942
+ }
943
+ chunks.push(c('['));
944
+ push_array(chunks, handle(node.property, state));
945
+ chunks.push(c(']'));
946
+ } else {
947
+ chunks.push(c(node.optional ? '?.' : '.'));
948
+ push_array(chunks, handle(node.property, state));
949
+ }
950
+
951
+ return chunks;
952
+ },
953
+
954
+ MetaProperty(node, state) {
955
+ return [...handle(node.meta, state), c('.'), ...handle(node.property, state)];
956
+ },
957
+
958
+ MethodDefinition(node, state) {
959
+ const chunks = [];
960
+
961
+ if (node.static) {
962
+ chunks.push(c('static '));
963
+ }
964
+
965
+ if (node.kind === 'get' || node.kind === 'set') {
966
+ // Getter or setter
967
+ chunks.push(c(node.kind + ' '));
968
+ }
969
+
970
+ if (node.value.async) {
971
+ chunks.push(c('async '));
972
+ }
973
+
974
+ if (node.value.generator) {
975
+ chunks.push(c('*'));
976
+ }
977
+
978
+ if (node.computed) {
979
+ chunks.push(c('['));
980
+ push_array(chunks, handle(node.key, state));
981
+ chunks.push(c(']'));
982
+ } else {
983
+ push_array(chunks, handle(node.key, state));
984
+ }
985
+
986
+ chunks.push(c('('));
987
+
988
+ const { params } = node.value;
989
+ for (let i = 0; i < params.length; i += 1) {
990
+ push_array(chunks, handle(params[i], state));
991
+ if (i < params.length - 1) chunks.push(c(', '));
992
+ }
993
+
994
+ chunks.push(c(') '));
995
+ push_array(chunks, handle(node.value.body, state));
996
+
997
+ return chunks;
998
+ },
999
+
1000
+ NewExpression(node, state) {
1001
+ const chunks = [c('new ')];
1002
+
1003
+ if (
1004
+ EXPRESSIONS_PRECEDENCE[node.callee.type] < EXPRESSIONS_PRECEDENCE.CallExpression ||
1005
+ has_call_expression(node.callee)
1006
+ ) {
1007
+ chunks.push(c('('));
1008
+ push_array(chunks, handle(node.callee, state));
1009
+ chunks.push(c(')'));
1010
+ } else {
1011
+ push_array(chunks, handle(node.callee, state));
1012
+ }
1013
+
1014
+ // TODO this is copied from CallExpression — DRY it out
1015
+ const args = node.arguments.map((arg) =>
1016
+ handle(arg, {
1017
+ ...state,
1018
+ indent: state.indent + '\t'
1019
+ })
1020
+ );
1021
+
1022
+ const separator = args.some(has_newline) // TODO or length exceeds 80
1023
+ ? c(',\n' + state.indent)
1024
+ : c(', ');
1025
+
1026
+ chunks.push(c('('));
1027
+ push_array(chunks, join(args, separator));
1028
+ chunks.push(c(')'));
1029
+
1030
+ return chunks;
1031
+ },
1032
+
1033
+ ObjectExpression(node, state) {
1034
+ if (node.properties.length === 0) {
1035
+ return [c('{}')];
1036
+ }
1037
+
1038
+ let has_inline_comment = false;
1039
+
1040
+ /** @type {import('./types').Chunk[]} */
1041
+ const chunks = [];
1042
+ const separator = c(', ');
1043
+
1044
+ node.properties.forEach((p, i) => {
1045
+ push_array(
1046
+ chunks,
1047
+ handle(p, {
1048
+ ...state,
1049
+ indent: state.indent + '\t'
1050
+ })
1051
+ );
1052
+
1053
+ if (state.comments.length) {
1054
+ // TODO generalise this, so it works with ArrayExpressions and other things.
1055
+ // At present, stuff will just get appended to the closest statement/declaration
1056
+ chunks.push(c(', '));
1057
+
1058
+ while (state.comments.length) {
1059
+ const comment = /** @type {import('estree').Comment} */ (state.comments.shift());
1060
+
1061
+ chunks.push(
1062
+ c(
1063
+ comment.type === 'Block'
1064
+ ? `/*${comment.value}*/\n${state.indent}\t`
1065
+ : `//${comment.value}\n${state.indent}\t`
1066
+ )
1067
+ );
1068
+
1069
+ if (comment.type === 'Line') {
1070
+ has_inline_comment = true;
1071
+ }
1072
+ }
1073
+ } else {
1074
+ if (i < node.properties.length - 1) {
1075
+ chunks.push(separator);
1076
+ }
1077
+ }
1078
+ });
1079
+
1080
+ const multiple_lines = has_inline_comment || has_newline(chunks) || get_length(chunks) > 40;
1081
+
1082
+ if (multiple_lines) {
1083
+ separator.content = `,\n${state.indent}\t`;
1084
+ }
1085
+
1086
+ return [
1087
+ c(multiple_lines ? `{\n${state.indent}\t` : `{ `),
1088
+ ...chunks,
1089
+ c(multiple_lines ? `\n${state.indent}}` : ` }`)
1090
+ ];
1091
+ },
1092
+
1093
+ ObjectPattern(node, state) {
1094
+ const chunks = [c('{ ')];
1095
+
1096
+ for (let i = 0; i < node.properties.length; i += 1) {
1097
+ push_array(chunks, handle(node.properties[i], state));
1098
+ if (i < node.properties.length - 1) chunks.push(c(', '));
1099
+ }
1100
+
1101
+ chunks.push(c(' }'));
1102
+
1103
+ return chunks;
1104
+ },
1105
+
1106
+ // @ts-expect-error this isn't a real node type, but Acorn produces it
1107
+ ParenthesizedExpression(node, state) {
1108
+ return handle(node.expression, state);
1109
+ },
1110
+
1111
+ PrivateIdentifier(node, state) {
1112
+ const chunks = [c('#')];
1113
+
1114
+ push_array(chunks, [c(node.name, node)]);
1115
+
1116
+ return chunks;
1117
+ },
1118
+
1119
+ Program(node, state) {
1120
+ return handle_body(node.body, state);
1121
+ },
1122
+
1123
+ Property(node, state) {
1124
+ const value = handle(node.value, state);
1125
+
1126
+ if (node.key === node.value) {
1127
+ return value;
1128
+ }
1129
+
1130
+ // special case
1131
+ if (
1132
+ !node.computed &&
1133
+ node.value.type === 'AssignmentPattern' &&
1134
+ node.value.left.type === 'Identifier' &&
1135
+ node.key.type === 'Identifier' &&
1136
+ node.value.left.name === node.key.name
1137
+ ) {
1138
+ return value;
1139
+ }
1140
+
1141
+ if (
1142
+ !node.computed &&
1143
+ node.value.type === 'Identifier' &&
1144
+ ((node.key.type === 'Identifier' && node.key.name === value[0].content) ||
1145
+ (node.key.type === 'Literal' && node.key.value === value[0].content))
1146
+ ) {
1147
+ return value;
1148
+ }
1149
+
1150
+ const key = handle(node.key, state);
1151
+
1152
+ if (node.value.type === 'FunctionExpression' && !node.value.id) {
1153
+ const chunks = node.kind !== 'init' ? [c(`${node.kind} `)] : [];
1154
+
1155
+ if (node.value.async) {
1156
+ chunks.push(c('async '));
1157
+ }
1158
+ if (node.value.generator) {
1159
+ chunks.push(c('*'));
1160
+ }
1161
+
1162
+ push_array(chunks, node.computed ? [c('['), ...key, c(']')] : key);
1163
+ chunks.push(c('('));
1164
+ push_array(
1165
+ chunks,
1166
+ join(
1167
+ node.value.params.map((param) => handle(param, state)),
1168
+ c(', ')
1169
+ )
1170
+ );
1171
+ chunks.push(c(') '));
1172
+ push_array(chunks, handle(node.value.body, state));
1173
+
1174
+ return chunks;
1175
+ }
1176
+
1177
+ if (node.computed) {
1178
+ return [c('['), ...key, c(']: '), ...value];
1179
+ }
1180
+
1181
+ return [...key, c(': '), ...value];
1182
+ },
1183
+
1184
+ PropertyDefinition(node, state) {
1185
+ const chunks = [];
1186
+
1187
+ if (node.static) {
1188
+ chunks.push(c('static '));
1189
+ }
1190
+
1191
+ if (node.computed) {
1192
+ chunks.push(c('['), ...handle(node.key, state), c(']'));
1193
+ } else {
1194
+ chunks.push(...handle(node.key, state));
1195
+ }
1196
+
1197
+ if (node.value) {
1198
+ chunks.push(c(' = '));
1199
+
1200
+ chunks.push(...handle(node.value, state));
1201
+ }
1202
+
1203
+ chunks.push(c(';'));
1204
+
1205
+ return chunks;
1206
+ },
1207
+
1208
+ RestElement: shared['RestElement|SpreadElement'],
1209
+
1210
+ ReturnStatement(node, state) {
1211
+ if (node.argument) {
1212
+ const contains_comment =
1213
+ node.argument.leadingComments &&
1214
+ node.argument.leadingComments.some(
1215
+ (/** @type {any} */ comment) => comment.has_trailing_newline
1216
+ );
1217
+ return [
1218
+ c(contains_comment ? 'return (' : 'return '),
1219
+ ...handle(node.argument, state),
1220
+ c(contains_comment ? ');' : ';')
1221
+ ];
1222
+ } else {
1223
+ return [c('return;')];
1224
+ }
1225
+ },
1226
+
1227
+ SequenceExpression(node, state) {
1228
+ const expressions = node.expressions.map((e) => handle(e, state));
1229
+
1230
+ return [c('('), ...join(expressions, c(', ')), c(')')];
1231
+ },
1232
+
1233
+ SpreadElement: shared['RestElement|SpreadElement'],
1234
+
1235
+ StaticBlock(node, state) {
1236
+ return [
1237
+ c('static '),
1238
+ c(`{\n${state.indent}\t`),
1239
+ ...handle_body(node.body, { ...state, indent: state.indent + '\t' }),
1240
+ c(`\n${state.indent}}`)
1241
+ ];
1242
+ },
1243
+
1244
+ Super(node, state) {
1245
+ return [c('super', node)];
1246
+ },
1247
+
1248
+ SwitchStatement(node, state) {
1249
+ const chunks = [c('switch ('), ...handle(node.discriminant, state), c(') {')];
1250
+
1251
+ node.cases.forEach((block) => {
1252
+ if (block.test) {
1253
+ chunks.push(c(`\n${state.indent}\tcase `));
1254
+ push_array(chunks, handle(block.test, { ...state, indent: `${state.indent}\t` }));
1255
+ chunks.push(c(':'));
1256
+ } else {
1257
+ chunks.push(c(`\n${state.indent}\tdefault:`));
1258
+ }
1259
+
1260
+ block.consequent.forEach((statement) => {
1261
+ chunks.push(c(`\n${state.indent}\t\t`));
1262
+ push_array(chunks, handle(statement, { ...state, indent: `${state.indent}\t\t` }));
1263
+ });
1264
+ });
1265
+
1266
+ chunks.push(c(`\n${state.indent}}`));
1267
+
1268
+ return chunks;
1269
+ },
1270
+
1271
+ TaggedTemplateExpression(node, state) {
1272
+ return handle(node.tag, state).concat(handle(node.quasi, state));
1273
+ },
1274
+
1275
+ TemplateLiteral(node, state) {
1276
+ const chunks = [c('`')];
1277
+
1278
+ const { quasis, expressions } = node;
1279
+
1280
+ for (let i = 0; i < expressions.length; i++) {
1281
+ chunks.push(c(quasis[i].value.raw), c('${'));
1282
+ push_array(chunks, handle(expressions[i], state));
1283
+ chunks.push(c('}'));
1284
+ }
1285
+
1286
+ chunks.push(c(quasis[quasis.length - 1].value.raw), c('`'));
1287
+
1288
+ return chunks;
1289
+ },
1290
+
1291
+ ThisExpression(node, state) {
1292
+ return [c('this', node)];
1293
+ },
1294
+
1295
+ ThrowStatement(node, state) {
1296
+ return [c('throw '), ...handle(node.argument, state), c(';')];
1297
+ },
1298
+
1299
+ TryStatement(node, state) {
1300
+ const chunks = [c('try '), ...handle(node.block, state)];
1301
+
1302
+ if (node.handler) {
1303
+ if (node.handler.param) {
1304
+ chunks.push(c(' catch('));
1305
+ push_array(chunks, handle(node.handler.param, state));
1306
+ chunks.push(c(') '));
1307
+ } else {
1308
+ chunks.push(c(' catch '));
1309
+ }
1310
+
1311
+ push_array(chunks, handle(node.handler.body, state));
1312
+ }
1313
+
1314
+ if (node.finalizer) {
1315
+ chunks.push(c(' finally '));
1316
+ push_array(chunks, handle(node.finalizer, state));
1317
+ }
1318
+
1319
+ return chunks;
1320
+ },
1321
+
1322
+ UnaryExpression(node, state) {
1323
+ const chunks = [c(node.operator)];
1324
+
1325
+ if (node.operator.length > 1) {
1326
+ chunks.push(c(' '));
1327
+ }
1328
+
1329
+ if (EXPRESSIONS_PRECEDENCE[node.argument.type] < EXPRESSIONS_PRECEDENCE.UnaryExpression) {
1330
+ chunks.push(c('('));
1331
+ push_array(chunks, handle(node.argument, state));
1332
+ chunks.push(c(')'));
1333
+ } else {
1334
+ push_array(chunks, handle(node.argument, state));
1335
+ }
1336
+
1337
+ return chunks;
1338
+ },
1339
+
1340
+ UpdateExpression(node, state) {
1341
+ return node.prefix
1342
+ ? [c(node.operator), ...handle(node.argument, state)]
1343
+ : [...handle(node.argument, state), c(node.operator)];
1344
+ },
1345
+
1346
+ VariableDeclaration(node, state) {
1347
+ return handle_var_declaration(node, state).concat(c(';'));
1348
+ },
1349
+
1350
+ VariableDeclarator(node, state) {
1351
+ if (node.init) {
1352
+ return [...handle(node.id, state), c(' = '), ...handle(node.init, state)];
1353
+ } else {
1354
+ return handle(node.id, state);
1355
+ }
1356
+ },
1357
+
1358
+ WhileStatement(node, state) {
1359
+ return [c('while ('), ...handle(node.test, state), c(') '), ...handle(node.body, state)];
1360
+ },
1361
+
1362
+ WithStatement(node, state) {
1363
+ return [c('with ('), ...handle(node.object, state), c(') '), ...handle(node.body, state)];
1364
+ },
1365
+
1366
+ YieldExpression(node, state) {
1367
+ if (node.argument) {
1368
+ return [c(node.delegate ? `yield* ` : `yield `), ...handle(node.argument, state)];
1369
+ }
1370
+
1371
+ return [c(node.delegate ? `yield*` : `yield`)];
1372
+ }
1373
+ };