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