@ttsc/factory 0.18.3 → 0.19.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.
Files changed (37) hide show
  1. package/lib/TsPrinter.d.ts +30 -0
  2. package/lib/TsPrinter.js +90 -24
  3. package/lib/TsPrinter.js.map +1 -1
  4. package/lib/TsPrinter.mjs +90 -24
  5. package/lib/TsPrinter.mjs.map +1 -1
  6. package/lib/ast/expressions/NoSubstitutionTemplateLiteral.d.ts +2 -0
  7. package/lib/ast/expressions/TemplateMiddle.d.ts +2 -0
  8. package/lib/ast/expressions/TemplateTail.d.ts +2 -0
  9. package/lib/factory/literals/createNoSubstitutionTemplateLiteral.d.ts +7 -4
  10. package/lib/factory/literals/createNoSubstitutionTemplateLiteral.js +7 -4
  11. package/lib/factory/literals/createNoSubstitutionTemplateLiteral.js.map +1 -1
  12. package/lib/factory/literals/createNoSubstitutionTemplateLiteral.mjs +7 -4
  13. package/lib/factory/literals/createNoSubstitutionTemplateLiteral.mjs.map +1 -1
  14. package/lib/factory/literals/createTemplateHead.d.ts +4 -3
  15. package/lib/factory/literals/createTemplateHead.js +4 -3
  16. package/lib/factory/literals/createTemplateHead.js.map +1 -1
  17. package/lib/factory/literals/createTemplateHead.mjs +4 -3
  18. package/lib/factory/literals/createTemplateHead.mjs.map +1 -1
  19. package/lib/factory/literals/createTemplateMiddle.d.ts +8 -4
  20. package/lib/factory/literals/createTemplateMiddle.js +8 -4
  21. package/lib/factory/literals/createTemplateMiddle.js.map +1 -1
  22. package/lib/factory/literals/createTemplateMiddle.mjs +8 -4
  23. package/lib/factory/literals/createTemplateMiddle.mjs.map +1 -1
  24. package/lib/factory/literals/createTemplateTail.d.ts +8 -4
  25. package/lib/factory/literals/createTemplateTail.js +8 -4
  26. package/lib/factory/literals/createTemplateTail.js.map +1 -1
  27. package/lib/factory/literals/createTemplateTail.mjs +8 -4
  28. package/lib/factory/literals/createTemplateTail.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/src/TsPrinter.ts +97 -26
  31. package/src/ast/expressions/NoSubstitutionTemplateLiteral.ts +3 -0
  32. package/src/ast/expressions/TemplateMiddle.ts +3 -0
  33. package/src/ast/expressions/TemplateTail.ts +3 -0
  34. package/src/factory/literals/createNoSubstitutionTemplateLiteral.ts +8 -4
  35. package/src/factory/literals/createTemplateHead.ts +4 -3
  36. package/src/factory/literals/createTemplateMiddle.ts +11 -5
  37. package/src/factory/literals/createTemplateTail.ts +11 -5
@@ -7,9 +7,10 @@ const make_1 = require("../internal/make");
7
7
  * the leading backtick up to the first `${`.
8
8
  *
9
9
  * The `text` is the cooked content of that span. The optional `rawText` carries
10
- * the source spelling before escape processing; when omitted, the cooked text
11
- * is used. A head is not a complete expression on its own, it is one piece of a
12
- * larger template literal.
10
+ * the source spelling before escape processing; the printer emits it verbatim
11
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
12
+ * same value. A head is not a complete expression on its own, it is one piece
13
+ * of a larger template literal.
13
14
  *
14
15
  * The printer emits the opening backtick, the content, then the `${` that opens
15
16
  * the first substitution. With `text` of `head`, this prints:
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateHead.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateHead.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,MAAM,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,EACF,EAAE,CAAC,IAAA,WAAI,EAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAH9C,QAAA,kBAAkB,GAAlB,kBAAkB,CAG4B"}
1
+ {"version":3,"file":"createTemplateHead.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateHead.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,MAAM,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,EACF,EAAE,CAAC,IAAA,WAAI,EAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAH9C,QAAA,kBAAkB,GAAlB,kBAAkB,CAG4B"}
@@ -5,9 +5,10 @@ import { make } from '../internal/make.mjs';
5
5
  * the leading backtick up to the first `${`.
6
6
  *
7
7
  * The `text` is the cooked content of that span. The optional `rawText` carries
8
- * the source spelling before escape processing; when omitted, the cooked text
9
- * is used. A head is not a complete expression on its own, it is one piece of a
10
- * larger template literal.
8
+ * the source spelling before escape processing; the printer emits it verbatim
9
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
10
+ * same value. A head is not a complete expression on its own, it is one piece
11
+ * of a larger template literal.
11
12
  *
12
13
  * The printer emits the opening backtick, the content, then the `${` that opens
13
14
  * the first substitution. With `text` of `head`, this prints:
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateHead.mjs","sources":["../../../src/factory/literals/createTemplateHead.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;AAoBG;MACU,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,KACC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;;;;"}
1
+ {"version":3,"file":"createTemplateHead.mjs","sources":["../../../src/factory/literals/createTemplateHead.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;;AAqBG;MACU,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,KACC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;;;;"}
@@ -3,9 +3,12 @@ import type { TemplateMiddle } from "../../ast";
3
3
  * Create a {@link TemplateMiddle}: a span of a template expression that sits
4
4
  * between two substitutions, from one `}` to the next `${`.
5
5
  *
6
- * The `text` is the cooked content of that span. A middle span only appears in
7
- * a template literal that has two or more substitutions, and it is one piece of
8
- * that larger literal rather than a complete expression.
6
+ * The `text` is the cooked content of that span. The optional `rawText` carries
7
+ * the source spelling before escape processing; the printer emits it verbatim
8
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
9
+ * same value. A middle span only appears in a template literal that has two or
10
+ * more substitutions, and it is one piece of that larger literal rather than a
11
+ * complete expression.
9
12
  *
10
13
  * The printer emits the closing `}` of the preceding substitution, the content,
11
14
  * then the `${` that opens the next one. With `text` of `mid`, this prints:
@@ -16,6 +19,7 @@ import type { TemplateMiddle } from "../../ast";
16
19
  *
17
20
  * @author Jeongho Nam - https://github.com/samchon
18
21
  * @param text The text.
22
+ * @param rawText The rawText.
19
23
  * @returns The created node.
20
24
  */
21
- export declare const createTemplateMiddle: (text: string) => TemplateMiddle;
25
+ export declare const createTemplateMiddle: (text: string, rawText?: string) => TemplateMiddle;
@@ -6,9 +6,12 @@ const make_1 = require("../internal/make");
6
6
  * Create a {@link TemplateMiddle}: a span of a template expression that sits
7
7
  * between two substitutions, from one `}` to the next `${`.
8
8
  *
9
- * The `text` is the cooked content of that span. A middle span only appears in
10
- * a template literal that has two or more substitutions, and it is one piece of
11
- * that larger literal rather than a complete expression.
9
+ * The `text` is the cooked content of that span. The optional `rawText` carries
10
+ * the source spelling before escape processing; the printer emits it verbatim
11
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
12
+ * same value. A middle span only appears in a template literal that has two or
13
+ * more substitutions, and it is one piece of that larger literal rather than a
14
+ * complete expression.
12
15
  *
13
16
  * The printer emits the closing `}` of the preceding substitution, the content,
14
17
  * then the `${` that opens the next one. With `text` of `mid`, this prints:
@@ -19,8 +22,9 @@ const make_1 = require("../internal/make");
19
22
  *
20
23
  * @author Jeongho Nam - https://github.com/samchon
21
24
  * @param text The text.
25
+ * @param rawText The rawText.
22
26
  * @returns The created node.
23
27
  */
24
- const createTemplateMiddle = (text) => (0, make_1.make)("TemplateMiddle", { text });
28
+ const createTemplateMiddle = (text, rawText) => (0, make_1.make)("TemplateMiddle", { text, rawText });
25
29
  exports.createTemplateMiddle = createTemplateMiddle;
26
30
  //# sourceMappingURL=createTemplateMiddle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateMiddle.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateMiddle.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACI,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAkB,EAAE,CACnE,IAAA,WAAI,EAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AADtB,QAAA,oBAAoB,GAApB,oBAAoB,CACE"}
1
+ {"version":3,"file":"createTemplateMiddle.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateMiddle.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACI,MAAM,oBAAoB,GAAG,CAClC,IAAY,EACZ,OAAgB,EACA,EAAE,CAAC,IAAA,WAAI,EAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAHlD,QAAA,oBAAoB,GAApB,oBAAoB,CAG8B"}
@@ -4,9 +4,12 @@ import { make } from '../internal/make.mjs';
4
4
  * Create a {@link TemplateMiddle}: a span of a template expression that sits
5
5
  * between two substitutions, from one `}` to the next `${`.
6
6
  *
7
- * The `text` is the cooked content of that span. A middle span only appears in
8
- * a template literal that has two or more substitutions, and it is one piece of
9
- * that larger literal rather than a complete expression.
7
+ * The `text` is the cooked content of that span. The optional `rawText` carries
8
+ * the source spelling before escape processing; the printer emits it verbatim
9
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
10
+ * same value. A middle span only appears in a template literal that has two or
11
+ * more substitutions, and it is one piece of that larger literal rather than a
12
+ * complete expression.
10
13
  *
11
14
  * The printer emits the closing `}` of the preceding substitution, the content,
12
15
  * then the `${` that opens the next one. With `text` of `mid`, this prints:
@@ -17,9 +20,10 @@ import { make } from '../internal/make.mjs';
17
20
  *
18
21
  * @author Jeongho Nam - https://github.com/samchon
19
22
  * @param text The text.
23
+ * @param rawText The rawText.
20
24
  * @returns The created node.
21
25
  */
22
- const createTemplateMiddle = (text) => make("TemplateMiddle", { text });
26
+ const createTemplateMiddle = (text, rawText) => make("TemplateMiddle", { text, rawText });
23
27
 
24
28
  export { createTemplateMiddle };
25
29
  //# sourceMappingURL=createTemplateMiddle.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateMiddle.mjs","sources":["../../../src/factory/literals/createTemplateMiddle.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;AAkBG;AACI,MAAM,oBAAoB,GAAG,CAAC,IAAY,KAC/C,IAAI,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE;;;;"}
1
+ {"version":3,"file":"createTemplateMiddle.mjs","sources":["../../../src/factory/literals/createTemplateMiddle.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;;;AAsBG;MACU,oBAAoB,GAAG,CAClC,IAAY,EACZ,OAAgB,KACG,IAAI,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;;;;"}
@@ -3,9 +3,12 @@ import type { TemplateTail } from "../../ast";
3
3
  * Create a {@link TemplateTail}: the closing span of a template expression, from
4
4
  * the last `}` to the trailing backtick.
5
5
  *
6
- * The `text` is the cooked content of that span. A tail closes a template
7
- * literal that has at least one substitution, and it is one piece of that
8
- * larger literal rather than a complete expression.
6
+ * The `text` is the cooked content of that span. The optional `rawText` carries
7
+ * the source spelling before escape processing; the printer emits it verbatim
8
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
9
+ * same value. A tail closes a template literal that has at least one
10
+ * substitution, and it is one piece of that larger literal rather than a
11
+ * complete expression.
9
12
  *
10
13
  * The printer emits the closing `}` of the final substitution, the content,
11
14
  * then the trailing backtick. With `text` of `tail`, this prints:
@@ -16,6 +19,7 @@ import type { TemplateTail } from "../../ast";
16
19
  *
17
20
  * @author Jeongho Nam - https://github.com/samchon
18
21
  * @param text The text.
22
+ * @param rawText The rawText.
19
23
  * @returns The created node.
20
24
  */
21
- export declare const createTemplateTail: (text: string) => TemplateTail;
25
+ export declare const createTemplateTail: (text: string, rawText?: string) => TemplateTail;
@@ -6,9 +6,12 @@ const make_1 = require("../internal/make");
6
6
  * Create a {@link TemplateTail}: the closing span of a template expression, from
7
7
  * the last `}` to the trailing backtick.
8
8
  *
9
- * The `text` is the cooked content of that span. A tail closes a template
10
- * literal that has at least one substitution, and it is one piece of that
11
- * larger literal rather than a complete expression.
9
+ * The `text` is the cooked content of that span. The optional `rawText` carries
10
+ * the source spelling before escape processing; the printer emits it verbatim
11
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
12
+ * same value. A tail closes a template literal that has at least one
13
+ * substitution, and it is one piece of that larger literal rather than a
14
+ * complete expression.
12
15
  *
13
16
  * The printer emits the closing `}` of the final substitution, the content,
14
17
  * then the trailing backtick. With `text` of `tail`, this prints:
@@ -19,8 +22,9 @@ const make_1 = require("../internal/make");
19
22
  *
20
23
  * @author Jeongho Nam - https://github.com/samchon
21
24
  * @param text The text.
25
+ * @param rawText The rawText.
22
26
  * @returns The created node.
23
27
  */
24
- const createTemplateTail = (text) => (0, make_1.make)("TemplateTail", { text });
28
+ const createTemplateTail = (text, rawText) => (0, make_1.make)("TemplateTail", { text, rawText });
25
29
  exports.createTemplateTail = createTemplateTail;
26
30
  //# sourceMappingURL=createTemplateTail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateTail.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateTail.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;GAkBG;AACI,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAgB,EAAE,CAC/D,IAAA,WAAI,EAAC,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AADpB,QAAA,kBAAkB,GAAlB,kBAAkB,CACE"}
1
+ {"version":3,"file":"createTemplateTail.js","sourceRoot":"","sources":["../../../src/factory/literals/createTemplateTail.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACI,MAAM,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,EACF,EAAE,CAAC,IAAA,WAAI,EAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAH9C,QAAA,kBAAkB,GAAlB,kBAAkB,CAG4B"}
@@ -4,9 +4,12 @@ import { make } from '../internal/make.mjs';
4
4
  * Create a {@link TemplateTail}: the closing span of a template expression, from
5
5
  * the last `}` to the trailing backtick.
6
6
  *
7
- * The `text` is the cooked content of that span. A tail closes a template
8
- * literal that has at least one substitution, and it is one piece of that
9
- * larger literal rather than a complete expression.
7
+ * The `text` is the cooked content of that span. The optional `rawText` carries
8
+ * the source spelling before escape processing; the printer emits it verbatim
9
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
10
+ * same value. A tail closes a template literal that has at least one
11
+ * substitution, and it is one piece of that larger literal rather than a
12
+ * complete expression.
10
13
  *
11
14
  * The printer emits the closing `}` of the final substitution, the content,
12
15
  * then the trailing backtick. With `text` of `tail`, this prints:
@@ -17,9 +20,10 @@ import { make } from '../internal/make.mjs';
17
20
  *
18
21
  * @author Jeongho Nam - https://github.com/samchon
19
22
  * @param text The text.
23
+ * @param rawText The rawText.
20
24
  * @returns The created node.
21
25
  */
22
- const createTemplateTail = (text) => make("TemplateTail", { text });
26
+ const createTemplateTail = (text, rawText) => make("TemplateTail", { text, rawText });
23
27
 
24
28
  export { createTemplateTail };
25
29
  //# sourceMappingURL=createTemplateTail.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"createTemplateTail.mjs","sources":["../../../src/factory/literals/createTemplateTail.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;AAkBG;AACI,MAAM,kBAAkB,GAAG,CAAC,IAAY,KAC7C,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE;;;;"}
1
+ {"version":3,"file":"createTemplateTail.mjs","sources":["../../../src/factory/literals/createTemplateTail.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;;;AAsBG;MACU,kBAAkB,GAAG,CAChC,IAAY,EACZ,OAAgB,KACC,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttsc/factory",
3
- "version": "0.18.3",
3
+ "version": "0.19.0",
4
4
  "description": "Self-contained legacy-style TypeScript AST factory and printer for source code generation, surviving the TypeScript-Go (tsgo) migration. Zero dependencies, no `typescript` import.",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
package/src/TsPrinter.ts CHANGED
@@ -197,13 +197,31 @@ export class TsPrinter {
197
197
  : "";
198
198
  }
199
199
 
200
+ /**
201
+ * Whether a broken parameter list / binding pattern may append a synthetic
202
+ * trailing comma after its last element.
203
+ *
204
+ * A trailing comma after a rest element (`...rest`) is a syntax error (TS1013
205
+ * / V8 `SyntaxError`), and one after a trailing elision (`OmittedExpression`)
206
+ * is not cosmetic: `[a, ,]` parses to one more hole than `[a, ]`, so the flat
207
+ * and broken layouts of the same node would disagree. Call arguments and
208
+ * array / object literals are unaffected — a trailing comma after a spread is
209
+ * legal there.
210
+ */
211
+ private listTrailingComma(nodes: readonly Node[]): boolean {
212
+ const last: Node | undefined = nodes[nodes.length - 1];
213
+ if (last === undefined) return true;
214
+ if (last.kind === "OmittedExpression") return false;
215
+ return !("dotDotDotToken" in last && last.dotDotDotToken !== undefined);
216
+ }
217
+
200
218
  private params(params: readonly Node[]): Doc {
201
219
  return this.delim(
202
220
  "(",
203
221
  params.map((p) => this.emit(p)),
204
222
  ")",
205
223
  {
206
- trailingComma: true,
224
+ trailingComma: this.listTrailingComma(params),
207
225
  },
208
226
  );
209
227
  }
@@ -1156,13 +1174,13 @@ export class TsPrinter {
1156
1174
 
1157
1175
  /* template literals */
1158
1176
  case "TemplateHead":
1159
- return concat(["`", node.text, "${"]);
1177
+ return concat(["`", templateText(node), "${"]);
1160
1178
  case "TemplateMiddle":
1161
- return concat(["}", node.text, "${"]);
1179
+ return concat(["}", templateText(node), "${"]);
1162
1180
  case "TemplateTail":
1163
- return concat(["}", node.text, "`"]);
1181
+ return concat(["}", templateText(node), "`"]);
1164
1182
  case "NoSubstitutionTemplateLiteral":
1165
- return concat(["`", node.text, "`"]);
1183
+ return concat(["`", templateText(node), "`"]);
1166
1184
 
1167
1185
  /* template & misc expressions */
1168
1186
  case "TemplateExpression":
@@ -1237,14 +1255,17 @@ export class TsPrinter {
1237
1255
  "{",
1238
1256
  node.elements.map((e) => this.emit(e)),
1239
1257
  "}",
1240
- { space: true, trailingComma: true },
1258
+ {
1259
+ space: true,
1260
+ trailingComma: this.listTrailingComma(node.elements),
1261
+ },
1241
1262
  );
1242
1263
  case "ArrayBindingPattern":
1243
1264
  return this.delim(
1244
1265
  "[",
1245
1266
  node.elements.map((e) => this.emit(e)),
1246
1267
  "]",
1247
- { trailingComma: true },
1268
+ { trailingComma: this.listTrailingComma(node.elements) },
1248
1269
  );
1249
1270
  case "TypeAssertion":
1250
1271
  return concat([
@@ -1618,13 +1639,30 @@ export class TsPrinter {
1618
1639
  }
1619
1640
 
1620
1641
  private newExpressionTarget(expression: Expression): Doc {
1621
- return expression.kind !== "CallExpression" &&
1622
- expression.kind !== "CallChain" &&
1623
- (expression.kind !== "NewExpression" ||
1624
- expression.arguments !== undefined) &&
1625
- this.isLeftHandSideExpression(expression)
1626
- ? this.emit(expression)
1627
- : this.parenthesizedExpression(expression);
1642
+ return this.newExpressionTargetNeedsParentheses(expression)
1643
+ ? this.parenthesizedExpression(expression)
1644
+ : this.emit(expression);
1645
+ }
1646
+
1647
+ /**
1648
+ * Whether a `new` target must be parenthesized to keep its call arguments
1649
+ * from re-binding to the `new` — mirroring the legacy printer's
1650
+ * `parenthesizeExpressionOfNew`. A `new` target is grammatically a
1651
+ * `MemberExpression`, so a call anywhere on the target's left spine (not just
1652
+ * a direct one: `new (f().bar)()`, `new (a.b().c)()`) would otherwise
1653
+ * re-parse with the call's arguments consumed by the `new` — a different
1654
+ * program. Argument-less `new` on the spine is kept parenthesized for
1655
+ * continuity with the direct case, though this printer always prints an
1656
+ * argument list, which already disambiguates it.
1657
+ */
1658
+ private newExpressionTargetNeedsParentheses(expression: Expression): boolean {
1659
+ if (!this.isLeftHandSideExpression(expression)) return true;
1660
+ const leftmost: Expression = this.leftmostExpression(expression, true);
1661
+ return (
1662
+ leftmost.kind === "CallExpression" ||
1663
+ leftmost.kind === "CallChain" ||
1664
+ (leftmost.kind === "NewExpression" && leftmost.arguments === undefined)
1665
+ );
1628
1666
  }
1629
1667
 
1630
1668
  private prefixUnaryOperand(operand: Expression, operator?: SyntaxKind): Doc {
@@ -1724,12 +1762,6 @@ export class TsPrinter {
1724
1762
  this.binaryOperatorPrecedence(operator);
1725
1763
  const operandPrecedence: ExpressionPrecedence =
1726
1764
  this.expressionPrecedence(operand);
1727
- if (
1728
- !isLeftSide &&
1729
- operand.kind === "ArrowFunction" &&
1730
- operatorPrecedence > ExpressionPrecedence.Assignment
1731
- )
1732
- return true;
1733
1765
  if (operandPrecedence < operatorPrecedence) return true;
1734
1766
  if (operandPrecedence > operatorPrecedence) return false;
1735
1767
 
@@ -1755,6 +1787,8 @@ export class TsPrinter {
1755
1787
  return ExpressionPrecedence.Comma;
1756
1788
  case "YieldExpression":
1757
1789
  return ExpressionPrecedence.Yield;
1790
+ case "ArrowFunction":
1791
+ return ExpressionPrecedence.Assignment;
1758
1792
  case "ConditionalExpression":
1759
1793
  return ExpressionPrecedence.Conditional;
1760
1794
  case "BinaryExpression":
@@ -1802,6 +1836,7 @@ export class TsPrinter {
1802
1836
  case "AwaitExpression":
1803
1837
  case "ConditionalExpression":
1804
1838
  case "YieldExpression":
1839
+ case "ArrowFunction":
1805
1840
  return Associativity.Right;
1806
1841
  case "BinaryExpression":
1807
1842
  return this.binaryOperatorAssociativity(expression.operator);
@@ -1977,11 +2012,22 @@ export class TsPrinter {
1977
2012
  );
1978
2013
  }
1979
2014
 
1980
- private leftmostExpression(expression: Expression): Expression {
2015
+ /**
2016
+ * Walk to the expression's leftmost node — the one that starts its printed
2017
+ * text. With `stopAtCall`, calls terminate the walk instead of being walked
2018
+ * through, matching the legacy `getLeftmostExpression`'s
2019
+ * `stopAtCallExpressions` mode used by the `new`-target parenthesizer.
2020
+ */
2021
+ private leftmostExpression(
2022
+ expression: Expression,
2023
+ stopAtCall: boolean = false,
2024
+ ): Expression {
1981
2025
  switch (expression.kind) {
1982
- case "AsExpression":
1983
2026
  case "CallExpression":
1984
2027
  case "CallChain":
2028
+ if (stopAtCall) return expression;
2029
+ return this.leftmostExpression(expression.expression, stopAtCall);
2030
+ case "AsExpression":
1985
2031
  case "ElementAccessExpression":
1986
2032
  case "ElementAccessChain":
1987
2033
  case "NonNullExpression":
@@ -1989,13 +2035,13 @@ export class TsPrinter {
1989
2035
  case "PropertyAccessExpression":
1990
2036
  case "PropertyAccessChain":
1991
2037
  case "SatisfiesExpression":
1992
- return this.leftmostExpression(expression.expression);
2038
+ return this.leftmostExpression(expression.expression, stopAtCall);
1993
2039
  case "BinaryExpression":
1994
- return this.leftmostExpression(expression.left);
2040
+ return this.leftmostExpression(expression.left, stopAtCall);
1995
2041
  case "ConditionalExpression":
1996
- return this.leftmostExpression(expression.condition);
2042
+ return this.leftmostExpression(expression.condition, stopAtCall);
1997
2043
  case "TaggedTemplateExpression":
1998
- return this.leftmostExpression(expression.tag);
2044
+ return this.leftmostExpression(expression.tag, stopAtCall);
1999
2045
  default:
2000
2046
  return expression;
2001
2047
  }
@@ -2153,6 +2199,31 @@ export namespace TsPrinter {
2153
2199
  }
2154
2200
  }
2155
2201
 
2202
+ /**
2203
+ * Source text of a template span: `rawText` verbatim when the author provided
2204
+ * one (raw fidelity is theirs to own, mirroring the legacy TypeScript emitter),
2205
+ * otherwise the cooked `text` escaped for template context.
2206
+ */
2207
+ const templateText = (node: { text: string; rawText?: string }): string =>
2208
+ typeof node.rawText === "string"
2209
+ ? node.rawText
2210
+ : escapeTemplateText(node.text);
2211
+
2212
+ /**
2213
+ * Escape cooked text so it re-parses to the same cooked value inside a template
2214
+ * literal: backslashes, backticks, and `${` sequences (a `$` not followed by
2215
+ * `{` stays literal). CR and CRLF are escaped because the scanner normalizes
2216
+ * raw template line terminators to LF; a lone LF is legal template text and
2217
+ * stays literal.
2218
+ */
2219
+ const escapeTemplateText = (text: string): string =>
2220
+ text
2221
+ .replace(/\\/g, "\\\\")
2222
+ .replace(/`/g, "\\`")
2223
+ .replace(/\$\{/g, "\\${")
2224
+ .replace(/\r\n/g, "\\r\\n")
2225
+ .replace(/\r/g, "\\r");
2226
+
2156
2227
  const escapeString = (text: string, singleQuote?: boolean): string => {
2157
2228
  const escaped: string = text
2158
2229
  .replace(/\\/g, "\\\\")
@@ -11,4 +11,7 @@ export interface NoSubstitutionTemplateLiteral {
11
11
 
12
12
  /** Text. */
13
13
  text: string;
14
+
15
+ /** RawText. */
16
+ rawText?: string;
14
17
  }
@@ -11,4 +11,7 @@ export interface TemplateMiddle {
11
11
 
12
12
  /** Text. */
13
13
  text: string;
14
+
15
+ /** RawText. */
16
+ rawText?: string;
14
17
  }
@@ -11,4 +11,7 @@ export interface TemplateTail {
11
11
 
12
12
  /** Text. */
13
13
  text: string;
14
+
15
+ /** RawText. */
16
+ rawText?: string;
14
17
  }
@@ -5,9 +5,11 @@ import { make } from "../internal/make";
5
5
  * Create a {@link NoSubstitutionTemplateLiteral}: a backtick template string
6
6
  * with no `${...}` substitutions.
7
7
  *
8
- * The `text` is the literal content between the backticks. Because there are no
9
- * placeholders, the whole literal is a single span and the printer wraps the
10
- * content in backticks unchanged.
8
+ * The `text` is the cooked content between the backticks. Because there are no
9
+ * placeholders, the whole literal is a single span. The optional `rawText`
10
+ * carries the source spelling before escape processing; the printer emits it
11
+ * verbatim when present, and otherwise escapes the cooked `text` so it
12
+ * re-parses to the same value.
11
13
  *
12
14
  * With `text` of `hello`, this prints:
13
15
  *
@@ -17,9 +19,11 @@ import { make } from "../internal/make";
17
19
  *
18
20
  * @author Jeongho Nam - https://github.com/samchon
19
21
  * @param text The text.
22
+ * @param rawText The rawText.
20
23
  * @returns The created node.
21
24
  */
22
25
  export const createNoSubstitutionTemplateLiteral = (
23
26
  text: string,
27
+ rawText?: string,
24
28
  ): NoSubstitutionTemplateLiteral =>
25
- make("NoSubstitutionTemplateLiteral", { text });
29
+ make("NoSubstitutionTemplateLiteral", { text, rawText });
@@ -6,9 +6,10 @@ import { make } from "../internal/make";
6
6
  * the leading backtick up to the first `${`.
7
7
  *
8
8
  * The `text` is the cooked content of that span. The optional `rawText` carries
9
- * the source spelling before escape processing; when omitted, the cooked text
10
- * is used. A head is not a complete expression on its own, it is one piece of a
11
- * larger template literal.
9
+ * the source spelling before escape processing; the printer emits it verbatim
10
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
11
+ * same value. A head is not a complete expression on its own, it is one piece
12
+ * of a larger template literal.
12
13
  *
13
14
  * The printer emits the opening backtick, the content, then the `${` that opens
14
15
  * the first substitution. With `text` of `head`, this prints:
@@ -5,9 +5,12 @@ import { make } from "../internal/make";
5
5
  * Create a {@link TemplateMiddle}: a span of a template expression that sits
6
6
  * between two substitutions, from one `}` to the next `${`.
7
7
  *
8
- * The `text` is the cooked content of that span. A middle span only appears in
9
- * a template literal that has two or more substitutions, and it is one piece of
10
- * that larger literal rather than a complete expression.
8
+ * The `text` is the cooked content of that span. The optional `rawText` carries
9
+ * the source spelling before escape processing; the printer emits it verbatim
10
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
11
+ * same value. A middle span only appears in a template literal that has two or
12
+ * more substitutions, and it is one piece of that larger literal rather than a
13
+ * complete expression.
11
14
  *
12
15
  * The printer emits the closing `}` of the preceding substitution, the content,
13
16
  * then the `${` that opens the next one. With `text` of `mid`, this prints:
@@ -18,7 +21,10 @@ import { make } from "../internal/make";
18
21
  *
19
22
  * @author Jeongho Nam - https://github.com/samchon
20
23
  * @param text The text.
24
+ * @param rawText The rawText.
21
25
  * @returns The created node.
22
26
  */
23
- export const createTemplateMiddle = (text: string): TemplateMiddle =>
24
- make("TemplateMiddle", { text });
27
+ export const createTemplateMiddle = (
28
+ text: string,
29
+ rawText?: string,
30
+ ): TemplateMiddle => make("TemplateMiddle", { text, rawText });
@@ -5,9 +5,12 @@ import { make } from "../internal/make";
5
5
  * Create a {@link TemplateTail}: the closing span of a template expression, from
6
6
  * the last `}` to the trailing backtick.
7
7
  *
8
- * The `text` is the cooked content of that span. A tail closes a template
9
- * literal that has at least one substitution, and it is one piece of that
10
- * larger literal rather than a complete expression.
8
+ * The `text` is the cooked content of that span. The optional `rawText` carries
9
+ * the source spelling before escape processing; the printer emits it verbatim
10
+ * when present, and otherwise escapes the cooked `text` so it re-parses to the
11
+ * same value. A tail closes a template literal that has at least one
12
+ * substitution, and it is one piece of that larger literal rather than a
13
+ * complete expression.
11
14
  *
12
15
  * The printer emits the closing `}` of the final substitution, the content,
13
16
  * then the trailing backtick. With `text` of `tail`, this prints:
@@ -18,7 +21,10 @@ import { make } from "../internal/make";
18
21
  *
19
22
  * @author Jeongho Nam - https://github.com/samchon
20
23
  * @param text The text.
24
+ * @param rawText The rawText.
21
25
  * @returns The created node.
22
26
  */
23
- export const createTemplateTail = (text: string): TemplateTail =>
24
- make("TemplateTail", { text });
27
+ export const createTemplateTail = (
28
+ text: string,
29
+ rawText?: string,
30
+ ): TemplateTail => make("TemplateTail", { text, rawText });