esrap 2.2.11 → 2.2.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esrap",
3
- "version": "2.2.11",
3
+ "version": "2.2.13",
4
4
  "description": "Parse in reverse",
5
5
  "repository": {
6
6
  "type": "git",
@@ -511,23 +511,11 @@ export default (options = {}) => {
511
511
  // // Avoids confusion in `for` loops initializers
512
512
  // chunks.write('(');
513
513
  // }
514
- if (needs_parens(node.left, node, false)) {
515
- context.write('(');
516
- context.visit(node.left);
517
- context.write(')');
518
- } else {
519
- context.visit(node.left);
520
- }
514
+ maybe_wrap(context, node.left, operand_needs_wrap(node.left, node, false));
521
515
 
522
516
  context.write(` ${node.operator} `);
523
517
 
524
- if (needs_parens(node.right, node, true)) {
525
- context.write('(');
526
- context.visit(node.right);
527
- context.write(')');
528
- } else {
529
- context.visit(node.right);
530
- }
518
+ maybe_wrap(context, node.right, operand_needs_wrap(node.right, node, true));
531
519
  },
532
520
 
533
521
  /**
@@ -575,17 +563,11 @@ export default (options = {}) => {
575
563
  write_keyword(context, node, 'new', ' ');
576
564
  }
577
565
 
578
- const needs_parens =
566
+ const wrap =
567
+ node.callee.type === 'ChainExpression' ||
579
568
  EXPRESSIONS_PRECEDENCE[node.callee.type] < EXPRESSIONS_PRECEDENCE.CallExpression ||
580
569
  (node.type === 'NewExpression' && has_call_expression(node.callee));
581
-
582
- if (needs_parens) {
583
- context.write('(');
584
- context.visit(node.callee);
585
- context.write(')');
586
- } else {
587
- context.visit(node.callee);
588
- }
570
+ maybe_wrap(context, node.callee, wrap);
589
571
 
590
572
  if (/** @type {TSESTree.CallExpression} */ (node).optional) {
591
573
  context.write('?.');
@@ -671,12 +653,25 @@ export default (options = {}) => {
671
653
 
672
654
  if (node.id) {
673
655
  context.visit(node.id);
656
+ }
657
+
658
+ if (node.typeParameters) {
659
+ context.visit(node.typeParameters);
660
+ }
661
+
662
+ if (node.id || node.typeParameters) {
674
663
  context.write(' ');
675
664
  }
676
665
 
677
666
  if (node.superClass) {
678
667
  context.write('extends ');
668
+ // the `extends` clause is a LeftHandSideExpression; anything lower (a
669
+ // logical/binary/conditional/etc.) must be parenthesized
670
+ const wrap_super =
671
+ EXPRESSIONS_PRECEDENCE[node.superClass.type] < EXPRESSIONS_PRECEDENCE.NewExpression;
672
+ if (wrap_super) context.write('(');
679
673
  context.visit(node.superClass);
674
+ if (wrap_super) context.write(')');
680
675
 
681
676
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
682
677
  var type_arguments = node.superTypeParameters ?? node.superTypeArguments;
@@ -813,6 +808,12 @@ export default (options = {}) => {
813
808
  context.visit(node.key);
814
809
  if (node.computed) context.write(']');
815
810
 
811
+ // optional method (`m?()`)
812
+ if (node.optional) context.write('?');
813
+
814
+ // @ts-expect-error `typeParameters` lives on the method node, not its value
815
+ if (node.typeParameters) context.visit(node.typeParameters);
816
+
816
817
  context.write('(');
817
818
  sequence(
818
819
  context,
@@ -845,6 +846,8 @@ export default (options = {}) => {
845
846
 
846
847
  const kw = create_keyword_write(context, node, field_modifiers_keywords_map_ok);
847
848
 
849
+ if (node.declare) kw('declare ');
850
+
848
851
  if (node.accessibility) {
849
852
  kw(node.accessibility + ' ');
850
853
  }
@@ -862,6 +865,10 @@ export default (options = {}) => {
862
865
  kw('static ');
863
866
  }
864
867
 
868
+ if (node.override) kw('override ');
869
+
870
+ if (node.readonly) kw('readonly ');
871
+
865
872
  if (
866
873
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
867
874
  node.accessor ||
@@ -879,6 +886,10 @@ export default (options = {}) => {
879
886
  context.visit(node.key);
880
887
  }
881
888
 
889
+ // `x?: T` (optional) / `x!: T` (definite assignment)
890
+ if (node.optional) context.write('?');
891
+ else if (node.definite) context.write('!');
892
+
882
893
  if (node.typeAnnotation) {
883
894
  if (node.type === 'AccessorProperty' || node.type === 'TSAbstractAccessorProperty') {
884
895
  context.visit(node.typeAnnotation);
@@ -926,7 +937,6 @@ export default (options = {}) => {
926
937
  }
927
938
 
928
939
  context.write('(');
929
-
930
940
  sequence(
931
941
  context,
932
942
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
@@ -953,7 +963,6 @@ export default (options = {}) => {
953
963
  if (node.typeParameters) context.visit(node.typeParameters);
954
964
 
955
965
  context.write('(');
956
-
957
966
  sequence(
958
967
  context,
959
968
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
@@ -964,8 +973,9 @@ export default (options = {}) => {
964
973
  null,
965
974
  false
966
975
  );
976
+ context.write(')');
967
977
 
968
- context.write(') => ');
978
+ context.write(' => ');
969
979
 
970
980
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
971
981
  context.visit(node.typeAnnotation?.typeAnnotation ?? node.returnType?.typeAnnotation);
@@ -1011,13 +1021,7 @@ export default (options = {}) => {
1011
1021
 
1012
1022
  context.write(' => ');
1013
1023
 
1014
- if (arrow_concise_body_needs_wrap(node.body)) {
1015
- context.write('(');
1016
- context.visit(node.body);
1017
- context.write(')');
1018
- } else {
1019
- context.visit(node.body);
1020
- }
1024
+ maybe_wrap(context, node.body, arrow_concise_body_needs_wrap(node.body));
1021
1025
  },
1022
1026
 
1023
1027
  AssignmentExpression(node, context) {
@@ -1076,13 +1080,9 @@ export default (options = {}) => {
1076
1080
  ClassExpression: shared['ClassDeclaration|ClassExpression'],
1077
1081
 
1078
1082
  ConditionalExpression(node, context) {
1079
- if (EXPRESSIONS_PRECEDENCE[node.test.type] > EXPRESSIONS_PRECEDENCE.ConditionalExpression) {
1080
- context.visit(node.test);
1081
- } else {
1082
- context.write('(');
1083
- context.visit(node.test);
1084
- context.write(')');
1085
- }
1083
+ const wrap =
1084
+ EXPRESSIONS_PRECEDENCE[node.test.type] <= EXPRESSIONS_PRECEDENCE.ConditionalExpression;
1085
+ maybe_wrap(context, node.test, wrap);
1086
1086
 
1087
1087
  const consequent = context.new();
1088
1088
  const alternate = context.new();
@@ -1130,7 +1130,14 @@ export default (options = {}) => {
1130
1130
 
1131
1131
  Decorator(node, context) {
1132
1132
  context.write('@');
1133
+ // a decorator must be an identifier/member/call (or parenthesized); anything
1134
+ // else (ternary, logical, assignment, unary, `as`, optional chain…) needs wrapping
1135
+ const wrap =
1136
+ /** @type {string} */ (node.expression.type) === 'ChainExpression' ||
1137
+ EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.CallExpression;
1138
+ if (wrap) context.write('(');
1133
1139
  context.visit(node.expression);
1140
+ if (wrap) context.write(')');
1134
1141
  context.newline();
1135
1142
  },
1136
1143
 
@@ -1240,20 +1247,9 @@ export default (options = {}) => {
1240
1247
  },
1241
1248
 
1242
1249
  ExpressionStatement(node, context) {
1243
- if (
1244
- node.expression.type === 'ObjectExpression' ||
1245
- (node.expression.type === 'AssignmentExpression' &&
1246
- node.expression.left.type === 'ObjectPattern') ||
1247
- node.expression.type === 'FunctionExpression'
1248
- ) {
1249
- // is an AssignmentExpression to an ObjectPattern
1250
- context.write('(');
1251
- context.visit(node.expression);
1252
- context.write(');');
1253
- return;
1254
- }
1255
-
1256
- context.visit(node.expression);
1250
+ // would otherwise be parsed as a block / function / class declaration
1251
+ const wrap = leads_with_curly_or_keyword(node.expression);
1252
+ maybe_wrap(context, node.expression, wrap);
1257
1253
  context.write(';');
1258
1254
  },
1259
1255
 
@@ -1289,6 +1285,9 @@ export default (options = {}) => {
1289
1285
  let name = node.name;
1290
1286
  context.write(name, node);
1291
1287
 
1288
+ // optional parameters (`a?: T`) carry `optional` on the identifier
1289
+ if (node.optional) context.write('?');
1290
+
1292
1291
  if (node.typeAnnotation) context.visit(node.typeAnnotation);
1293
1292
  },
1294
1293
 
@@ -1436,13 +1435,10 @@ export default (options = {}) => {
1436
1435
  LogicalExpression: shared['BinaryExpression|LogicalExpression'],
1437
1436
 
1438
1437
  MemberExpression(node, context) {
1439
- if (EXPRESSIONS_PRECEDENCE[node.object.type] < EXPRESSIONS_PRECEDENCE.MemberExpression) {
1440
- context.write('(');
1441
- context.visit(node.object);
1442
- context.write(')');
1443
- } else {
1444
- context.visit(node.object);
1445
- }
1438
+ const wrap =
1439
+ node.object.type === 'ChainExpression' ||
1440
+ EXPRESSIONS_PRECEDENCE[node.object.type] < EXPRESSIONS_PRECEDENCE.MemberExpression;
1441
+ maybe_wrap(context, node.object, wrap);
1446
1442
 
1447
1443
  if (node.computed) {
1448
1444
  if (node.optional) {
@@ -1483,9 +1479,7 @@ export default (options = {}) => {
1483
1479
 
1484
1480
  // @ts-expect-error this isn't a real node type, but Acorn produces it
1485
1481
  ParenthesizedExpression(node, context) {
1486
- context.write('(');
1487
- context.visit(node.expression);
1488
- context.write(')');
1482
+ maybe_wrap(context, node.expression, true);
1489
1483
  },
1490
1484
 
1491
1485
  PrivateIdentifier(node, context) {
@@ -1634,7 +1628,12 @@ export default (options = {}) => {
1634
1628
  },
1635
1629
 
1636
1630
  TaggedTemplateExpression(node, context) {
1637
- context.visit(node.tag);
1631
+ // the tag is a LeftHandSideExpression; a lower-precedence tag (logical,
1632
+ // conditional, arrow, `as`, unary…) or an optional chain must be wrapped
1633
+ const wrap =
1634
+ /** @type {string} */ (node.tag.type) === 'ChainExpression' ||
1635
+ EXPRESSIONS_PRECEDENCE[node.tag.type] < EXPRESSIONS_PRECEDENCE.CallExpression;
1636
+ maybe_wrap(context, node.tag, wrap);
1638
1637
  context.visit(node.quasi);
1639
1638
  },
1640
1639
 
@@ -1707,15 +1706,19 @@ export default (options = {}) => {
1707
1706
 
1708
1707
  if (node.operator.length > 1) {
1709
1708
  context.write(' ');
1709
+ } else if (
1710
+ (node.operator === '+' || node.operator === '-') &&
1711
+ ((node.argument.type === 'UnaryExpression' && node.argument.operator === node.operator) ||
1712
+ (node.argument.type === 'UpdateExpression' &&
1713
+ node.argument.prefix &&
1714
+ node.argument.operator[0] === node.operator))
1715
+ ) {
1716
+ context.write(' ');
1710
1717
  }
1711
1718
 
1712
- if (EXPRESSIONS_PRECEDENCE[node.argument.type] < EXPRESSIONS_PRECEDENCE.UnaryExpression) {
1713
- context.write('(');
1714
- context.visit(node.argument);
1715
- context.write(')');
1716
- } else {
1717
- context.visit(node.argument);
1718
- }
1719
+ const wrap =
1720
+ EXPRESSIONS_PRECEDENCE[node.argument.type] < EXPRESSIONS_PRECEDENCE.UnaryExpression;
1721
+ maybe_wrap(context, node.argument, wrap);
1719
1722
  },
1720
1723
 
1721
1724
  UpdateExpression(node, context) {
@@ -1883,7 +1886,9 @@ export default (options = {}) => {
1883
1886
  },
1884
1887
 
1885
1888
  TSPropertySignature(node, context) {
1889
+ if (node.computed) context.write('[');
1886
1890
  context.visit(node.key);
1891
+ if (node.computed) context.write(']');
1887
1892
  if (node.optional) context.write('?');
1888
1893
  if (node.typeAnnotation) context.visit(node.typeAnnotation);
1889
1894
  },
@@ -1951,13 +1956,9 @@ export default (options = {}) => {
1951
1956
  context.write('<');
1952
1957
  context.visit(node.typeAnnotation);
1953
1958
  context.write('>');
1954
- if (EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.TSTypeAssertion) {
1955
- context.write('(');
1956
- context.visit(node.expression);
1957
- context.write(')');
1958
- } else {
1959
- context.visit(node.expression);
1960
- }
1959
+ const wrap =
1960
+ EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.TSTypeAssertion;
1961
+ maybe_wrap(context, node.expression, wrap);
1961
1962
  },
1962
1963
 
1963
1964
  TSTypeParameterInstantiation(node, context) {
@@ -1979,6 +1980,11 @@ export default (options = {}) => {
1979
1980
  },
1980
1981
 
1981
1982
  TSTypeParameter(node, context) {
1983
+ // modifiers: `const T`, `in T` / `out T` (variance)
1984
+ if (node.const) context.write('const ');
1985
+ if (node.in) context.write('in ');
1986
+ if (node.out) context.write('out ');
1987
+
1982
1988
  if (node.name && node.name.type) context.visit(node.name);
1983
1989
  // @ts-expect-error type mismatch TSESTree and acorn-typescript?
1984
1990
  else context.write(node.name, node);
@@ -1995,19 +2001,14 @@ export default (options = {}) => {
1995
2001
  },
1996
2002
 
1997
2003
  TSTypePredicate(node, context) {
1998
- if (node.parameterName) {
1999
- context.visit(node.parameterName);
2000
- } else if (node.typeAnnotation) {
2001
- context.visit(node.typeAnnotation);
2002
- }
2004
+ // `asserts` precedes the parameter name; `is <type>` follows it. Forms:
2005
+ // `x is T`, `asserts x is T`, `asserts x`
2006
+ if (node.asserts) context.write('asserts ');
2003
2007
 
2004
- if (node.asserts) {
2005
- context.write(' asserts ');
2006
- } else {
2007
- context.write(' is ');
2008
- }
2008
+ if (node.parameterName) context.visit(node.parameterName);
2009
2009
 
2010
2010
  if (node.typeAnnotation) {
2011
+ context.write(' is ');
2011
2012
  context.visit(node.typeAnnotation.typeAnnotation);
2012
2013
  }
2013
2014
  },
@@ -2045,7 +2046,16 @@ export default (options = {}) => {
2045
2046
  },
2046
2047
 
2047
2048
  TSMappedType(node, context) {
2048
- context.write('{[');
2049
+ context.write('{');
2050
+
2051
+ // `readonly` / `+readonly` / `-readonly` modifier
2052
+ if (node.readonly) {
2053
+ context.write(
2054
+ node.readonly === '-' ? '-readonly ' : node.readonly === '+' ? '+readonly ' : 'readonly '
2055
+ );
2056
+ }
2057
+
2058
+ context.write('[');
2049
2059
 
2050
2060
  const legacy_type_parameter = node.typeParameter;
2051
2061
  const key = node.key ?? legacy_type_parameter?.name;
@@ -2062,8 +2072,19 @@ export default (options = {}) => {
2062
2072
  context.visit(constraint);
2063
2073
  }
2064
2074
 
2075
+ // `as` key remapping
2076
+ if (node.nameType) {
2077
+ context.write(' as ');
2078
+ context.visit(node.nameType);
2079
+ }
2080
+
2065
2081
  context.write(']');
2066
2082
 
2083
+ // `?` / `+?` / `-?` optionality modifier
2084
+ if (node.optional) {
2085
+ context.write(node.optional === '-' ? '-?' : node.optional === '+' ? '+?' : '?');
2086
+ }
2087
+
2067
2088
  if (node.typeAnnotation) {
2068
2089
  context.write(': ');
2069
2090
  context.visit(node.typeAnnotation);
@@ -2073,10 +2094,11 @@ export default (options = {}) => {
2073
2094
  },
2074
2095
 
2075
2096
  TSMethodSignature(node, context) {
2097
+ if (node.computed) context.write('[');
2076
2098
  context.visit(node.key);
2099
+ if (node.computed) context.write(']');
2077
2100
 
2078
2101
  context.write('(');
2079
-
2080
2102
  sequence(
2081
2103
  context,
2082
2104
  // @ts-expect-error `acorn-typescript` and `@typescript-eslint/types` have slightly different type definitions
@@ -2190,16 +2212,9 @@ export default (options = {}) => {
2190
2212
 
2191
2213
  TSAsExpression(node, context) {
2192
2214
  if (node.expression) {
2193
- const needs_parens =
2215
+ const wrap =
2194
2216
  EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.TSAsExpression;
2195
-
2196
- if (needs_parens) {
2197
- context.write('(');
2198
- context.visit(node.expression);
2199
- context.write(')');
2200
- } else {
2201
- context.visit(node.expression);
2202
- }
2217
+ maybe_wrap(context, node.expression, wrap);
2203
2218
  }
2204
2219
  context.write(' as ');
2205
2220
  context.visit(node.typeAnnotation);
@@ -2239,21 +2254,24 @@ export default (options = {}) => {
2239
2254
  context.visit(node.id);
2240
2255
  }
2241
2256
 
2242
- if (!node.body) return;
2243
- context.visit(node.body);
2257
+ // a qualified name (`namespace A.B.C`) is represented as nested
2258
+ // `TSModuleDeclaration`s whose body is the next name part, not a block
2259
+ let body = /** @type {any} */ (node.body);
2260
+ while (body && body.type === 'TSModuleDeclaration') {
2261
+ context.write('.');
2262
+ context.visit(body.id);
2263
+ body = body.body;
2264
+ }
2265
+
2266
+ if (!body) return;
2267
+ context.visit(body);
2244
2268
  },
2245
2269
 
2246
2270
  TSNonNullExpression(node, context) {
2247
2271
  // operator expressions can't take a postfix `!` directly: `(0 as number)!`, `(await x)!`
2248
- if (
2249
- EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.TSNonNullExpression
2250
- ) {
2251
- context.write('(');
2252
- context.visit(node.expression);
2253
- context.write(')');
2254
- } else {
2255
- context.visit(node.expression);
2256
- }
2272
+ const wrap =
2273
+ EXPRESSIONS_PRECEDENCE[node.expression.type] < EXPRESSIONS_PRECEDENCE.TSNonNullExpression;
2274
+ maybe_wrap(context, node.expression, wrap);
2257
2275
  context.write('!');
2258
2276
  },
2259
2277
 
@@ -2287,24 +2305,15 @@ export default (options = {}) => {
2287
2305
 
2288
2306
  //@ts-expect-error I don't know why, but this is relied upon in the tests, but doesn't exist in the TSESTree types
2289
2307
  TSParenthesizedType(node, context) {
2290
- context.write('(');
2291
- context.visit(node.typeAnnotation);
2292
- context.write(')');
2308
+ maybe_wrap(context, node.typeAnnotation, true);
2293
2309
  },
2294
2310
 
2295
2311
  TSSatisfiesExpression(node, context) {
2296
2312
  if (node.expression) {
2297
- const needs_parens =
2313
+ const wrap =
2298
2314
  EXPRESSIONS_PRECEDENCE[node.expression.type] <
2299
2315
  EXPRESSIONS_PRECEDENCE.TSSatisfiesExpression;
2300
-
2301
- if (needs_parens) {
2302
- context.write('(');
2303
- context.visit(node.expression);
2304
- context.write(')');
2305
- } else {
2306
- context.visit(node.expression);
2307
- }
2316
+ maybe_wrap(context, node.expression, wrap);
2308
2317
  }
2309
2318
  context.write(' satisfies ');
2310
2319
  context.visit(node.typeAnnotation);
@@ -2364,7 +2373,7 @@ function arrow_concise_body_needs_wrap(body) {
2364
2373
  * @param {boolean} is_right
2365
2374
  * @returns
2366
2375
  */
2367
- function needs_parens(node, parent, is_right) {
2376
+ function operand_needs_wrap(node, parent, is_right) {
2368
2377
  if (node.type === 'PrivateIdentifier') return false;
2369
2378
 
2370
2379
  if (!is_right && (node.type === 'TSAsExpression' || node.type === 'TSSatisfiesExpression')) {
@@ -2383,42 +2392,46 @@ function needs_parens(node, parent, is_right) {
2383
2392
  return true;
2384
2393
  }
2385
2394
 
2395
+ // `**` can't take a unary/await/assertion left operand: `-2 ** 2`, `await x ** 2`,
2396
+ // `<T>x ** 2` are syntax errors
2397
+ const unary_base_of_pow =
2398
+ !is_right &&
2399
+ parent.operator === '**' &&
2400
+ (node.type === 'UnaryExpression' ||
2401
+ node.type === 'AwaitExpression' ||
2402
+ node.type === 'TSTypeAssertion');
2403
+ if (unary_base_of_pow) return true;
2404
+
2386
2405
  const precedence = EXPRESSIONS_PRECEDENCE[node.type];
2387
2406
  const parent_precedence = EXPRESSIONS_PRECEDENCE[parent.type];
2388
-
2389
- if (precedence !== parent_precedence) {
2390
- // Different node types
2391
- return (
2392
- (!is_right && precedence === 15 && parent_precedence === 14 && parent.operator === '**') ||
2393
- precedence < parent_precedence
2394
- );
2395
- }
2396
-
2397
- if (precedence !== 12 && precedence !== 14) {
2398
- // Not a `LogicalExpression` or `BinaryExpression`
2399
- return false;
2400
- }
2401
-
2402
- if (
2403
- /** @type {TSESTree.BinaryExpression} */ (node).operator === '**' &&
2404
- parent.operator === '**'
2405
- ) {
2406
- // Exponentiation operator has right-to-left associativity
2407
+ if (precedence !== parent_precedence) return precedence < parent_precedence;
2408
+ const operator = /** @type {TSESTree.BinaryExpression} */ (node).operator;
2409
+ if (operator === '**' && parent.operator === '**') {
2410
+ // exponentiation is right-associative
2407
2411
  return !is_right;
2408
2412
  }
2409
2413
 
2410
2414
  if (is_right) {
2411
- // Parenthesis are used if both operators have the same precedence
2412
- return (
2413
- OPERATOR_PRECEDENCE[/** @type {TSESTree.BinaryExpression} */ (node).operator] <=
2414
- OPERATOR_PRECEDENCE[parent.operator]
2415
- );
2415
+ // parentheses are needed when both operators have the same precedence
2416
+ return OPERATOR_PRECEDENCE[operator] <= OPERATOR_PRECEDENCE[parent.operator];
2416
2417
  }
2417
2418
 
2418
- return (
2419
- OPERATOR_PRECEDENCE[/** @type {TSESTree.BinaryExpression} */ (node).operator] <
2420
- OPERATOR_PRECEDENCE[parent.operator]
2421
- );
2419
+ return OPERATOR_PRECEDENCE[operator] < OPERATOR_PRECEDENCE[parent.operator];
2420
+ }
2421
+
2422
+ /**
2423
+ * @param {Context} context
2424
+ * @param {TSESTree.Node} node
2425
+ * @param {boolean} wrap
2426
+ */
2427
+ function maybe_wrap(context, node, wrap) {
2428
+ if (wrap) {
2429
+ context.write('(');
2430
+ context.visit(node);
2431
+ context.write(')');
2432
+ } else {
2433
+ context.visit(node);
2434
+ }
2422
2435
  }
2423
2436
 
2424
2437
  /** @param {TSESTree.Node} node */
@@ -2432,6 +2445,96 @@ function has_call_expression(node) {
2432
2445
  return false;
2433
2446
  }
2434
2447
  }
2448
+ return false;
2449
+ }
2450
+
2451
+ /**
2452
+ * True when printing `node` as an expression statement would begin with `{`,
2453
+ * `function`, or `class` — which the parser would misread as a block, function
2454
+ * declaration, or class declaration. Walks the left spine following the same
2455
+ * parenthesization the visitors apply, so it stops as soon as a child position
2456
+ * would already be wrapped.
2457
+ * @param {TSESTree.Node} node
2458
+ * @returns {boolean}
2459
+ */
2460
+ function leads_with_curly_or_keyword(node) {
2461
+ while (node) {
2462
+ switch (node.type) {
2463
+ case 'ObjectExpression':
2464
+ case 'ObjectPattern':
2465
+ case 'FunctionExpression':
2466
+ case 'ClassExpression':
2467
+ return true;
2468
+
2469
+ case 'BinaryExpression':
2470
+ case 'LogicalExpression':
2471
+ if (operand_needs_wrap(node.left, node, false)) return false;
2472
+ node = node.left;
2473
+ continue;
2474
+
2475
+ case 'AssignmentExpression':
2476
+ node = node.left;
2477
+ continue;
2478
+
2479
+ case 'ConditionalExpression':
2480
+ if (
2481
+ EXPRESSIONS_PRECEDENCE[node.test.type] <= EXPRESSIONS_PRECEDENCE.ConditionalExpression
2482
+ ) {
2483
+ return false;
2484
+ }
2485
+ node = node.test;
2486
+ continue;
2487
+
2488
+ case 'MemberExpression':
2489
+ if (
2490
+ /** @type {string} */ (node.object.type) === 'ChainExpression' ||
2491
+ EXPRESSIONS_PRECEDENCE[node.object.type] < EXPRESSIONS_PRECEDENCE.MemberExpression
2492
+ ) {
2493
+ return false;
2494
+ }
2495
+ node = node.object;
2496
+ continue;
2497
+
2498
+ case 'CallExpression':
2499
+ if (
2500
+ /** @type {string} */ (node.callee.type) === 'ChainExpression' ||
2501
+ EXPRESSIONS_PRECEDENCE[node.callee.type] < EXPRESSIONS_PRECEDENCE.CallExpression
2502
+ ) {
2503
+ return false;
2504
+ }
2505
+ node = node.callee;
2506
+ continue;
2507
+
2508
+ case 'TaggedTemplateExpression':
2509
+ if (
2510
+ /** @type {string} */ (node.tag.type) === 'ChainExpression' ||
2511
+ EXPRESSIONS_PRECEDENCE[node.tag.type] < EXPRESSIONS_PRECEDENCE.CallExpression
2512
+ ) {
2513
+ return false;
2514
+ }
2515
+ node = node.tag;
2516
+ continue;
2517
+
2518
+ case 'UpdateExpression':
2519
+ if (node.prefix) return false;
2520
+ node = node.argument;
2521
+ continue;
2522
+
2523
+ case 'TSAsExpression':
2524
+ case 'TSSatisfiesExpression':
2525
+ case 'TSNonNullExpression':
2526
+ node = node.expression;
2527
+ continue;
2528
+
2529
+ // a sequence expression always prints its own wrapping parens
2530
+ case 'SequenceExpression':
2531
+ return false;
2532
+
2533
+ default:
2534
+ return false;
2535
+ }
2536
+ }
2537
+ return false;
2435
2538
  }
2436
2539
 
2437
2540
  /**