@ttsc/factory 0.19.3 → 0.20.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/README.md +2 -0
- package/lib/TsPrinter.d.ts +124 -17
- package/lib/TsPrinter.js +414 -106
- package/lib/TsPrinter.js.map +1 -1
- package/lib/TsPrinter.mjs +413 -107
- package/lib/TsPrinter.mjs.map +1 -1
- package/lib/ast/expressions/Expression.d.ts +5 -1
- package/lib/ast/imports/ImportClause.d.ts +10 -2
- package/lib/ast/jsdoc/JSDocImportTag.d.ts +3 -0
- package/lib/ast/types/ImportTypeNode.d.ts +3 -0
- package/lib/factory/expressions/createComma.d.ts +3 -3
- package/lib/factory/expressions/createComma.js +3 -3
- package/lib/factory/expressions/createComma.mjs +3 -3
- package/lib/factory/imports/createImportClause.d.ts +9 -3
- package/lib/factory/imports/createImportClause.js +8 -3
- package/lib/factory/imports/createImportClause.js.map +1 -1
- package/lib/factory/imports/createImportClause.mjs +8 -3
- package/lib/factory/imports/createImportClause.mjs.map +1 -1
- package/lib/factory/jsdoc/createJSDocImportTag.d.ts +3 -2
- package/lib/factory/jsdoc/createJSDocImportTag.js +3 -1
- package/lib/factory/jsdoc/createJSDocImportTag.js.map +1 -1
- package/lib/factory/jsdoc/createJSDocImportTag.mjs +3 -1
- package/lib/factory/jsdoc/createJSDocImportTag.mjs.map +1 -1
- package/lib/factory/types/createImportTypeNode.d.ts +8 -2
- package/lib/factory/types/createImportTypeNode.js +13 -1
- package/lib/factory/types/createImportTypeNode.js.map +1 -1
- package/lib/factory/types/createImportTypeNode.mjs +13 -1
- package/lib/factory/types/createImportTypeNode.mjs.map +1 -1
- package/lib/internal/doc.d.ts +13 -0
- package/lib/internal/doc.js +31 -4
- package/lib/internal/doc.js.map +1 -1
- package/lib/internal/doc.mjs +30 -4
- package/lib/internal/doc.mjs.map +1 -1
- package/lib/syntax/NodeFlags.d.ts +6 -4
- package/lib/syntax/NodeFlags.js +6 -4
- package/lib/syntax/NodeFlags.js.map +1 -1
- package/lib/syntax/NodeFlags.mjs +6 -4
- package/lib/syntax/NodeFlags.mjs.map +1 -1
- package/lib/syntax/SyntaxKind.d.ts +2 -0
- package/lib/syntax/SyntaxKind.js +6 -0
- package/lib/syntax/SyntaxKind.js.map +1 -1
- package/lib/syntax/SyntaxKind.mjs +6 -0
- package/lib/syntax/SyntaxKind.mjs.map +1 -1
- package/package.json +1 -1
- package/src/TsPrinter.ts +489 -121
- package/src/ast/expressions/Expression.ts +8 -0
- package/src/ast/imports/ImportClause.ts +10 -2
- package/src/ast/jsdoc/JSDocImportTag.ts +4 -0
- package/src/ast/types/ImportTypeNode.ts +4 -0
- package/src/factory/expressions/createComma.ts +3 -3
- package/src/factory/imports/createImportClause.ts +12 -6
- package/src/factory/jsdoc/createJSDocImportTag.ts +5 -1
- package/src/factory/types/createImportTypeNode.ts +21 -3
- package/src/internal/doc.ts +30 -3
- package/src/syntax/NodeFlags.ts +6 -4
- package/src/syntax/SyntaxKind.ts +7 -0
package/lib/TsPrinter.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSyntheticLeadingComments, getSyntheticTrailingComments } from './comments.mjs';
|
|
2
|
-
import { join, hardline, printDocToString, line, softline, group, concat, indent, ifBreak } from './internal/doc.mjs';
|
|
2
|
+
import { join, hardline, printDocToString, line, softline, group, concat, indent, ifBreak, raw } from './internal/doc.mjs';
|
|
3
3
|
import { SyntaxKind } from './syntax/SyntaxKind.mjs';
|
|
4
4
|
import { NodeFlags } from './syntax/NodeFlags.mjs';
|
|
5
5
|
|
|
@@ -73,7 +73,11 @@ class TsPrinter {
|
|
|
73
73
|
return group(concat([
|
|
74
74
|
open,
|
|
75
75
|
indent(concat([ln, join(concat([",", line]), items)])),
|
|
76
|
-
opts.trailingComma
|
|
76
|
+
opts.trailingComma === "always"
|
|
77
|
+
? ","
|
|
78
|
+
: opts.trailingComma === "onBreak"
|
|
79
|
+
? ifBreak(",")
|
|
80
|
+
: "",
|
|
77
81
|
ln,
|
|
78
82
|
close,
|
|
79
83
|
]), opts.forceBreak === true);
|
|
@@ -129,28 +133,75 @@ class TsPrinter {
|
|
|
129
133
|
// type-argument / type-parameter lists disallow a trailing comma (TS1009)
|
|
130
134
|
return args && args.length
|
|
131
135
|
? this.delim("<", args.map((a) => this.emit(a)), ">", {
|
|
132
|
-
trailingComma:
|
|
136
|
+
trailingComma: "never",
|
|
133
137
|
})
|
|
134
138
|
: "";
|
|
135
139
|
}
|
|
136
140
|
/**
|
|
137
|
-
*
|
|
138
|
-
* trailing comma after its last element.
|
|
141
|
+
* Trailing-comma policy for a parameter list or binding pattern.
|
|
139
142
|
*
|
|
140
|
-
* A
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
143
|
+
* A comma the printer adds only because a group broke must never change
|
|
144
|
+
* whether the text parses, nor what it parses to. After a rest element
|
|
145
|
+
* (`...rest`) it changes the first: a trailing comma there is a syntax error
|
|
146
|
+
* (TS1013 / V8 `SyntaxError`). After a trailing elision it changes the
|
|
147
|
+
* second: `[a, ,]` has one more hole than `[a, ]`, so the flat and broken
|
|
148
|
+
* layouts of the same node would disagree. A binding pattern is the one place
|
|
149
|
+
* where dropping that hole is lossless, since a trailing hole binds nothing;
|
|
150
|
+
* {@link literalTrailingComma} materializes it instead, because in an array
|
|
151
|
+
* literal the hole is a value.
|
|
146
152
|
*/
|
|
147
153
|
listTrailingComma(nodes) {
|
|
148
154
|
const last = nodes[nodes.length - 1];
|
|
149
155
|
if (last === undefined)
|
|
150
|
-
return
|
|
156
|
+
return "onBreak";
|
|
151
157
|
if (last.kind === "OmittedExpression")
|
|
152
|
-
return
|
|
153
|
-
return
|
|
158
|
+
return "never";
|
|
159
|
+
return "dotDotDotToken" in last && last.dotDotDotToken !== undefined
|
|
160
|
+
? "never"
|
|
161
|
+
: "onBreak";
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Trailing-comma policy for a call or `new` argument list.
|
|
165
|
+
*
|
|
166
|
+
* A trailing `OmittedExpression` prints as nothing, so the list already ends
|
|
167
|
+
* in the separator comma of its last real argument: `f(a, )`, which is what
|
|
168
|
+
* the legacy printer emits too and parses as one argument. Adding the break
|
|
169
|
+
* comma on top produces `f(a, ,)`, which is a syntax error. A trailing spread
|
|
170
|
+
* is unaffected — a comma after it is legal in an argument list.
|
|
171
|
+
*/
|
|
172
|
+
argsTrailingComma(args) {
|
|
173
|
+
const last = args[args.length - 1];
|
|
174
|
+
return last !== undefined && last.kind === "OmittedExpression"
|
|
175
|
+
? "never"
|
|
176
|
+
: "onBreak";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Trailing-comma policy for an array or object literal.
|
|
180
|
+
*
|
|
181
|
+
* Two positions make the comma load-bearing rather than cosmetic.
|
|
182
|
+
*
|
|
183
|
+
* A trailing elision is a **value**: the comma is the token that materializes
|
|
184
|
+
* the hole, so `["a", ]` has one element and `["a", ,]` has two. The legacy
|
|
185
|
+
* printer emits it in every layout, so this printer emits it in every layout
|
|
186
|
+
* too; leaving it to the break would make the same node mean different things
|
|
187
|
+
* at different widths.
|
|
188
|
+
*
|
|
189
|
+
* A destructuring **assignment target** is the same node kind as an rvalue
|
|
190
|
+
* literal, but ECMAScript forbids a comma after its `AssignmentRestElement` /
|
|
191
|
+
* `AssignmentRestProperty`: `[a, ...rest,] = source` is a syntax error, while
|
|
192
|
+
* the identical rvalue `[a, ...rest,]` is legal. Only the target position
|
|
193
|
+
* suppresses it, so the rvalue twin keeps its break comma.
|
|
194
|
+
*/
|
|
195
|
+
literalTrailingComma(elements, assignmentTarget) {
|
|
196
|
+
const last = elements[elements.length - 1];
|
|
197
|
+
if (last === undefined)
|
|
198
|
+
return "onBreak";
|
|
199
|
+
if (last.kind === "OmittedExpression")
|
|
200
|
+
return "always";
|
|
201
|
+
return assignmentTarget &&
|
|
202
|
+
(last.kind === "SpreadElement" || last.kind === "SpreadAssignment")
|
|
203
|
+
? "never"
|
|
204
|
+
: "onBreak";
|
|
154
205
|
}
|
|
155
206
|
params(params) {
|
|
156
207
|
return this.delim("(", params.map((p) => this.emit(p)), ")", {
|
|
@@ -159,7 +210,7 @@ class TsPrinter {
|
|
|
159
210
|
}
|
|
160
211
|
args(args) {
|
|
161
212
|
return this.delim("(", args.map((a) => this.expressionForDisallowedComma(a)), ")", {
|
|
162
|
-
trailingComma:
|
|
213
|
+
trailingComma: this.argsTrailingComma(args),
|
|
163
214
|
});
|
|
164
215
|
}
|
|
165
216
|
modifiers(mods, decoratorsOnNewLine) {
|
|
@@ -183,14 +234,56 @@ class TsPrinter {
|
|
|
183
234
|
])
|
|
184
235
|
: "";
|
|
185
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Lay out a JSX element's or fragment's children.
|
|
239
|
+
*
|
|
240
|
+
* A line break between JSX children is not cosmetic. JSX deletes a
|
|
241
|
+
* whitespace-only text child that contains a newline and trims
|
|
242
|
+
* whitespace-carrying-a-newline off both edges of every other text child, so
|
|
243
|
+
* a break introduced only because the group did not fit changes what the
|
|
244
|
+
* component renders: `<div>Hello there, {name}!</div>` becomes `Hello
|
|
245
|
+
* there,NAME!`, and the separator in `<div>{a} {b}</div>` disappears
|
|
246
|
+
* outright.
|
|
247
|
+
*
|
|
248
|
+
* Children are therefore laid out across lines only when the break survives
|
|
249
|
+
* that transformation unchanged: every text child must carry non-whitespace
|
|
250
|
+
* content, must not begin or end with whitespace, and must not sit next to
|
|
251
|
+
* another text child, since inserting a newline between two of them would
|
|
252
|
+
* merge into one text with a space in the middle. Otherwise the children are
|
|
253
|
+
* emitted verbatim on one line, whatever `printWidth` says — width may choose
|
|
254
|
+
* a layout, never a meaning.
|
|
255
|
+
*/
|
|
256
|
+
jsxChildren(open, children, close) {
|
|
257
|
+
if (!this.jsxChildrenMayBreak(children))
|
|
258
|
+
return concat([open, concat(children.map((c) => this.emit(c))), close]);
|
|
259
|
+
return group(concat([
|
|
260
|
+
open,
|
|
261
|
+
indent(concat(children.map((c) => concat([softline, this.emit(c)])))),
|
|
262
|
+
softline,
|
|
263
|
+
close,
|
|
264
|
+
]));
|
|
265
|
+
}
|
|
266
|
+
jsxChildrenMayBreak(children) {
|
|
267
|
+
return children.every((child, index) => child.kind !== "JsxText" ||
|
|
268
|
+
(isBreakSafeJsxText(child.text) &&
|
|
269
|
+
children[index + 1]?.kind !== "JsxText"));
|
|
270
|
+
}
|
|
186
271
|
optType(type) {
|
|
187
272
|
return type ? concat([": ", this.emit(type)]) : "";
|
|
188
273
|
}
|
|
189
274
|
optBody(body) {
|
|
190
275
|
return body ? concat([" ", this.emit(body)]) : ";";
|
|
191
276
|
}
|
|
192
|
-
|
|
193
|
-
|
|
277
|
+
/**
|
|
278
|
+
* @param assignmentTarget Whether `node` occupies destructuring
|
|
279
|
+
* assignment-target position, where an array or object literal is a pattern
|
|
280
|
+
* rather than a value. The flag is set by the assignment and `for…in` /
|
|
281
|
+
* `for…of` cases, forwarded by every node that is transparent to it (a
|
|
282
|
+
* spread, a property's initializer, a parenthesis, an `=` default), and
|
|
283
|
+
* dropped by every other node.
|
|
284
|
+
*/
|
|
285
|
+
emit(node, assignmentTarget = false) {
|
|
286
|
+
const body = this.emitNode(node, assignmentTarget);
|
|
194
287
|
const leading = getSyntheticLeadingComments(node);
|
|
195
288
|
const trailing = getSyntheticTrailingComments(node);
|
|
196
289
|
if ((leading === undefined || leading.length === 0) &&
|
|
@@ -234,7 +327,7 @@ class TsPrinter {
|
|
|
234
327
|
"*/",
|
|
235
328
|
]);
|
|
236
329
|
}
|
|
237
|
-
emitNode(node) {
|
|
330
|
+
emitNode(node, assignmentTarget) {
|
|
238
331
|
switch (node.kind) {
|
|
239
332
|
/* names & tokens */
|
|
240
333
|
case "Identifier":
|
|
@@ -246,7 +339,7 @@ class TsPrinter {
|
|
|
246
339
|
case "Token":
|
|
247
340
|
return node.token;
|
|
248
341
|
case "Decorator":
|
|
249
|
-
return concat(["@", this.leftSideExpression(node.expression)]);
|
|
342
|
+
return concat(["@", this.leftSideExpression(node.expression, false)]);
|
|
250
343
|
/* literals */
|
|
251
344
|
case "StringLiteral":
|
|
252
345
|
return escapeString(node.text, node.singleQuote);
|
|
@@ -256,18 +349,21 @@ class TsPrinter {
|
|
|
256
349
|
return node.text;
|
|
257
350
|
/* expressions */
|
|
258
351
|
case "ArrayLiteralExpression":
|
|
259
|
-
return this.delim("[", node.elements.map((e) => this.expressionForDisallowedComma(e)), "]", {
|
|
352
|
+
return this.delim("[", node.elements.map((e) => this.expressionForDisallowedComma(e, assignmentTarget)), "]", {
|
|
353
|
+
trailingComma: this.literalTrailingComma(node.elements, assignmentTarget),
|
|
354
|
+
forceBreak: node.multiLine === true,
|
|
355
|
+
});
|
|
260
356
|
case "ObjectLiteralExpression":
|
|
261
|
-
return this.delim("{", node.properties.map((p) => this.emit(p)), "}", {
|
|
357
|
+
return this.delim("{", node.properties.map((p) => this.emit(p, assignmentTarget)), "}", {
|
|
262
358
|
space: true,
|
|
263
|
-
trailingComma:
|
|
359
|
+
trailingComma: this.literalTrailingComma(node.properties, assignmentTarget),
|
|
264
360
|
forceBreak: node.multiLine === true,
|
|
265
361
|
});
|
|
266
362
|
case "PropertyAssignment":
|
|
267
363
|
return concat([
|
|
268
364
|
this.emit(node.name),
|
|
269
365
|
": ",
|
|
270
|
-
this.expressionForDisallowedComma(node.initializer),
|
|
366
|
+
this.expressionForDisallowedComma(node.initializer, assignmentTarget),
|
|
271
367
|
]);
|
|
272
368
|
case "ShorthandPropertyAssignment":
|
|
273
369
|
return concat([
|
|
@@ -282,24 +378,24 @@ class TsPrinter {
|
|
|
282
378
|
case "SpreadAssignment":
|
|
283
379
|
return concat([
|
|
284
380
|
"...",
|
|
285
|
-
this.expressionForDisallowedComma(node.expression),
|
|
381
|
+
this.expressionForDisallowedComma(node.expression, assignmentTarget),
|
|
286
382
|
]);
|
|
287
383
|
case "PropertyAccessExpression":
|
|
288
384
|
return concat([
|
|
289
|
-
this.leftSideExpression(node.expression),
|
|
385
|
+
this.leftSideExpression(node.expression, false),
|
|
290
386
|
".",
|
|
291
387
|
this.emit(node.name),
|
|
292
388
|
]);
|
|
293
389
|
case "ElementAccessExpression":
|
|
294
390
|
return concat([
|
|
295
|
-
this.leftSideExpression(node.expression),
|
|
391
|
+
this.leftSideExpression(node.expression, false),
|
|
296
392
|
"[",
|
|
297
393
|
this.expressionForDisallowedComma(node.argumentExpression),
|
|
298
394
|
"]",
|
|
299
395
|
]);
|
|
300
396
|
case "CallExpression":
|
|
301
397
|
return concat([
|
|
302
|
-
this.leftSideExpression(node.expression),
|
|
398
|
+
this.leftSideExpression(node.expression, false),
|
|
303
399
|
this.typeArguments(node.typeArguments),
|
|
304
400
|
this.args(node.arguments),
|
|
305
401
|
]);
|
|
@@ -311,11 +407,19 @@ class TsPrinter {
|
|
|
311
407
|
this.args(node.arguments ?? []),
|
|
312
408
|
]);
|
|
313
409
|
case "ParenthesizedExpression":
|
|
314
|
-
return concat(["(", this.emit(node.expression), ")"]);
|
|
410
|
+
return concat(["(", this.emit(node.expression, assignmentTarget), ")"]);
|
|
315
411
|
case "BinaryExpression":
|
|
412
|
+
// the left side of `=` is a destructuring assignment target, both for a
|
|
413
|
+
// top-level assignment and for a `[a = init]` default inside one
|
|
316
414
|
return group(concat([
|
|
317
|
-
this.binaryOperand(node.operator, node.left, true),
|
|
318
|
-
|
|
415
|
+
this.binaryOperand(node.operator, node.left, true, undefined, node.operator === SyntaxKind.EqualsToken),
|
|
416
|
+
// Every operator but the comma is written with a space on each
|
|
417
|
+
// side. The comma is punctuation that attaches to what precedes it:
|
|
418
|
+
// `CommaListExpression` joins with ", ", the legacy printer and the
|
|
419
|
+
// repository's pinned Prettier both emit `a, b`, and this factory's
|
|
420
|
+
// own JSDoc for `createComma` shows `(a, b)`. Only the printer
|
|
421
|
+
// disagreed, with `a , b`.
|
|
422
|
+
node.operator === SyntaxKind.CommaToken ? "" : " ",
|
|
319
423
|
node.operator,
|
|
320
424
|
indent(concat([
|
|
321
425
|
line,
|
|
@@ -375,11 +479,11 @@ class TsPrinter {
|
|
|
375
479
|
this.emit(node.type),
|
|
376
480
|
]);
|
|
377
481
|
case "NonNullExpression":
|
|
378
|
-
return concat([this.leftSideExpression(node.expression), "!"]);
|
|
482
|
+
return concat([this.leftSideExpression(node.expression, false), "!"]);
|
|
379
483
|
case "SpreadElement":
|
|
380
484
|
return concat([
|
|
381
485
|
"...",
|
|
382
|
-
this.expressionForDisallowedComma(node.expression),
|
|
486
|
+
this.expressionForDisallowedComma(node.expression, assignmentTarget),
|
|
383
487
|
]);
|
|
384
488
|
case "AwaitExpression":
|
|
385
489
|
return concat(["await ", this.prefixUnaryOperand(node.expression)]);
|
|
@@ -412,7 +516,7 @@ class TsPrinter {
|
|
|
412
516
|
]);
|
|
413
517
|
case "TupleTypeNode":
|
|
414
518
|
return this.delim("[", node.elements.map((e) => this.emit(e)), "]", {
|
|
415
|
-
trailingComma:
|
|
519
|
+
trailingComma: "onBreak",
|
|
416
520
|
});
|
|
417
521
|
case "ParenthesizedTypeNode":
|
|
418
522
|
return concat(["(", this.emit(node.type), ")"]);
|
|
@@ -432,8 +536,11 @@ class TsPrinter {
|
|
|
432
536
|
case "TypeQueryNode":
|
|
433
537
|
return concat(["typeof ", this.emit(node.exprName)]);
|
|
434
538
|
case "ExpressionWithTypeArguments":
|
|
539
|
+
// heritage clauses take a LeftHandSideExpression: `class A extends
|
|
540
|
+
// (X || Y) {}` does not parse without the parentheses, and a bare comma
|
|
541
|
+
// sequence silently becomes two base classes
|
|
435
542
|
return concat([
|
|
436
|
-
this.
|
|
543
|
+
this.leftSideExpression(node.expression, false),
|
|
437
544
|
this.typeArguments(node.typeArguments),
|
|
438
545
|
]);
|
|
439
546
|
case "PropertySignature":
|
|
@@ -684,10 +791,16 @@ class TsPrinter {
|
|
|
684
791
|
named.push(this.emit(node.name));
|
|
685
792
|
if (node.namedBindings)
|
|
686
793
|
named.push(this.emit(node.namedBindings));
|
|
687
|
-
|
|
794
|
+
// The phase modifier is the keyword itself, so it prints as written —
|
|
795
|
+
// `type` and `defer` both reach here, where a boolean could only ever
|
|
796
|
+
// have produced the first.
|
|
797
|
+
return concat([
|
|
798
|
+
node.phaseModifier ? `${node.phaseModifier} ` : "",
|
|
799
|
+
join(", ", named),
|
|
800
|
+
]);
|
|
688
801
|
}
|
|
689
802
|
case "NamedImports":
|
|
690
|
-
return this.delim("{", node.elements.map((e) => this.emit(e)), "}", { space: true, trailingComma:
|
|
803
|
+
return this.delim("{", node.elements.map((e) => this.emit(e)), "}", { space: true, trailingComma: "onBreak" });
|
|
691
804
|
case "ImportSpecifier":
|
|
692
805
|
return concat([
|
|
693
806
|
node.isTypeOnly ? "type " : "",
|
|
@@ -710,7 +823,7 @@ class TsPrinter {
|
|
|
710
823
|
";",
|
|
711
824
|
]);
|
|
712
825
|
case "NamedExports":
|
|
713
|
-
return this.delim("{", node.elements.map((e) => this.emit(e)), "}", { space: true, trailingComma:
|
|
826
|
+
return this.delim("{", node.elements.map((e) => this.emit(e)), "}", { space: true, trailingComma: "onBreak" });
|
|
714
827
|
case "ExportSpecifier":
|
|
715
828
|
return concat([
|
|
716
829
|
node.isTypeOnly ? "type " : "",
|
|
@@ -747,7 +860,7 @@ class TsPrinter {
|
|
|
747
860
|
case "ForInStatement":
|
|
748
861
|
return concat([
|
|
749
862
|
"for (",
|
|
750
|
-
this.emit(node.initializer),
|
|
863
|
+
this.emit(node.initializer, true),
|
|
751
864
|
" in ",
|
|
752
865
|
this.emit(node.expression),
|
|
753
866
|
") ",
|
|
@@ -758,7 +871,7 @@ class TsPrinter {
|
|
|
758
871
|
"for ",
|
|
759
872
|
node.awaitModifier ? "await " : "",
|
|
760
873
|
"(",
|
|
761
|
-
this.emit(node.initializer),
|
|
874
|
+
this.emit(node.initializer, true),
|
|
762
875
|
" of ",
|
|
763
876
|
this.emit(node.expression),
|
|
764
877
|
") ",
|
|
@@ -863,7 +976,17 @@ class TsPrinter {
|
|
|
863
976
|
case "ModuleDeclaration":
|
|
864
977
|
return concat([
|
|
865
978
|
this.modifiers(node.modifiers, true),
|
|
866
|
-
|
|
979
|
+
// A string-literal name is always `module "…"`; the flag says nothing
|
|
980
|
+
// there. For an identifier the flag is what chooses, which is the
|
|
981
|
+
// upstream rule and the one `createModuleDeclaration` documents:
|
|
982
|
+
// `namespace A` with `NodeFlags.Namespace`, `module A` without it.
|
|
983
|
+
// The printer used to read the name kind alone, so an identifier
|
|
984
|
+
// always printed `namespace` and the flag it published was inert.
|
|
985
|
+
node.name.kind === "StringLiteral"
|
|
986
|
+
? "module "
|
|
987
|
+
: node.flags === NodeFlags.Namespace
|
|
988
|
+
? "namespace "
|
|
989
|
+
: "module ",
|
|
867
990
|
this.emit(node.name),
|
|
868
991
|
node.body ? concat([" ", this.emit(node.body)]) : ";",
|
|
869
992
|
]);
|
|
@@ -970,6 +1093,19 @@ class TsPrinter {
|
|
|
970
1093
|
node.isTypeOf ? "typeof " : "",
|
|
971
1094
|
"import(",
|
|
972
1095
|
this.emit(node.argument),
|
|
1096
|
+
// An import type spells its attributes as a second call argument —
|
|
1097
|
+
// `import("m", { with: { … } }).T` — not as the trailing `with { … }`
|
|
1098
|
+
// an import declaration uses, so the elements are wrapped here rather
|
|
1099
|
+
// than emitted through the attributes node's own form.
|
|
1100
|
+
node.attributes && node.attributes.elements.length > 0
|
|
1101
|
+
? concat([
|
|
1102
|
+
", { ",
|
|
1103
|
+
node.attributes.token,
|
|
1104
|
+
": { ",
|
|
1105
|
+
join(", ", node.attributes.elements.map((e) => this.emit(e))),
|
|
1106
|
+
" } }",
|
|
1107
|
+
])
|
|
1108
|
+
: "",
|
|
973
1109
|
")",
|
|
974
1110
|
node.qualifier ? concat([".", this.emit(node.qualifier)]) : "",
|
|
975
1111
|
this.typeArguments(node.typeArguments),
|
|
@@ -1009,7 +1145,7 @@ class TsPrinter {
|
|
|
1009
1145
|
]);
|
|
1010
1146
|
case "TaggedTemplateExpression":
|
|
1011
1147
|
return concat([
|
|
1012
|
-
this.leftSideExpression(node.tag),
|
|
1148
|
+
this.leftSideExpression(node.tag, false),
|
|
1013
1149
|
this.typeArguments(node.typeArguments),
|
|
1014
1150
|
this.emit(node.template),
|
|
1015
1151
|
]);
|
|
@@ -1077,13 +1213,13 @@ class TsPrinter {
|
|
|
1077
1213
|
]);
|
|
1078
1214
|
case "PropertyAccessChain":
|
|
1079
1215
|
return concat([
|
|
1080
|
-
this.leftSideExpression(node.expression),
|
|
1216
|
+
this.leftSideExpression(node.expression, true),
|
|
1081
1217
|
node.questionDotToken ? "?." : ".",
|
|
1082
1218
|
this.emit(node.name),
|
|
1083
1219
|
]);
|
|
1084
1220
|
case "ElementAccessChain":
|
|
1085
1221
|
return concat([
|
|
1086
|
-
this.leftSideExpression(node.expression),
|
|
1222
|
+
this.leftSideExpression(node.expression, true),
|
|
1087
1223
|
node.questionDotToken ? "?." : "",
|
|
1088
1224
|
"[",
|
|
1089
1225
|
this.expressionForDisallowedComma(node.argumentExpression),
|
|
@@ -1091,21 +1227,16 @@ class TsPrinter {
|
|
|
1091
1227
|
]);
|
|
1092
1228
|
case "CallChain":
|
|
1093
1229
|
return concat([
|
|
1094
|
-
this.leftSideExpression(node.expression),
|
|
1230
|
+
this.leftSideExpression(node.expression, true),
|
|
1095
1231
|
node.questionDotToken ? "?." : "",
|
|
1096
1232
|
this.typeArguments(node.typeArguments),
|
|
1097
1233
|
this.args(node.arguments),
|
|
1098
1234
|
]);
|
|
1099
1235
|
case "NonNullChain":
|
|
1100
|
-
return concat([this.leftSideExpression(node.expression), "!"]);
|
|
1236
|
+
return concat([this.leftSideExpression(node.expression, true), "!"]);
|
|
1101
1237
|
/* jsx */
|
|
1102
1238
|
case "JsxElement":
|
|
1103
|
-
return
|
|
1104
|
-
this.emit(node.openingElement),
|
|
1105
|
-
indent(concat(node.children.map((c) => concat([softline, this.emit(c)])))),
|
|
1106
|
-
softline,
|
|
1107
|
-
this.emit(node.closingElement),
|
|
1108
|
-
]));
|
|
1239
|
+
return this.jsxChildren(this.emit(node.openingElement), node.children, this.emit(node.closingElement));
|
|
1109
1240
|
case "JsxSelfClosingElement":
|
|
1110
1241
|
return concat([
|
|
1111
1242
|
"<",
|
|
@@ -1125,18 +1256,15 @@ class TsPrinter {
|
|
|
1125
1256
|
case "JsxClosingElement":
|
|
1126
1257
|
return concat(["</", this.emit(node.tagName), ">"]);
|
|
1127
1258
|
case "JsxFragment":
|
|
1128
|
-
return
|
|
1129
|
-
this.emit(node.openingFragment),
|
|
1130
|
-
indent(concat(node.children.map((c) => concat([softline, this.emit(c)])))),
|
|
1131
|
-
softline,
|
|
1132
|
-
this.emit(node.closingFragment),
|
|
1133
|
-
]));
|
|
1259
|
+
return this.jsxChildren(this.emit(node.openingFragment), node.children, this.emit(node.closingFragment));
|
|
1134
1260
|
case "JsxOpeningFragment":
|
|
1135
1261
|
return "<>";
|
|
1136
1262
|
case "JsxClosingFragment":
|
|
1137
1263
|
return "</>";
|
|
1138
1264
|
case "JsxText":
|
|
1139
|
-
|
|
1265
|
+
// the one node emitted as unquoted source text: its trailing spaces are
|
|
1266
|
+
// rendered content, so they must survive the layout engine's line trim
|
|
1267
|
+
return raw(node.text);
|
|
1140
1268
|
case "JsxAttribute":
|
|
1141
1269
|
return node.initializer === undefined
|
|
1142
1270
|
? this.emit(node.name)
|
|
@@ -1353,6 +1481,10 @@ class TsPrinter {
|
|
|
1353
1481
|
? concat([this.emit(node.importClause), " from "])
|
|
1354
1482
|
: "",
|
|
1355
1483
|
this.emit(node.moduleSpecifier),
|
|
1484
|
+
// `@import { a } from "m" with { type: "json" }` — the same trailing
|
|
1485
|
+
// form an import declaration uses, which is why the attributes node
|
|
1486
|
+
// emits itself here rather than being unwrapped.
|
|
1487
|
+
node.attributes ? concat([" ", this.emit(node.attributes)]) : "",
|
|
1356
1488
|
this.jsDocComment(node.comment),
|
|
1357
1489
|
]);
|
|
1358
1490
|
case "JSDocTemplateTag":
|
|
@@ -1384,19 +1516,67 @@ class TsPrinter {
|
|
|
1384
1516
|
* re-associate — matching the legacy printer's parenthesizer rules.
|
|
1385
1517
|
*/
|
|
1386
1518
|
parenthesizedExpression(expression) {
|
|
1387
|
-
return expression.kind ===
|
|
1519
|
+
return this.skipPartiallyEmittedExpressions(expression).kind ===
|
|
1520
|
+
"ParenthesizedExpression"
|
|
1388
1521
|
? this.emit(expression)
|
|
1389
1522
|
: concat(["(", this.emit(expression), ")"]);
|
|
1390
1523
|
}
|
|
1391
|
-
|
|
1524
|
+
/**
|
|
1525
|
+
* The partial-emission wrapper carries transform provenance but emits no
|
|
1526
|
+
* syntax of its own, so every grammar predicate must inspect its inner node.
|
|
1527
|
+
*/
|
|
1528
|
+
skipPartiallyEmittedExpressions(expression) {
|
|
1529
|
+
while (expression.kind === "PartiallyEmittedExpression")
|
|
1530
|
+
expression = expression.expression;
|
|
1531
|
+
return expression;
|
|
1532
|
+
}
|
|
1533
|
+
expressionForDisallowedComma(expression, assignmentTarget = false) {
|
|
1392
1534
|
return this.expressionPrecedence(expression) > ExpressionPrecedence.Comma
|
|
1393
|
-
? this.emit(expression)
|
|
1535
|
+
? this.emit(expression, assignmentTarget)
|
|
1394
1536
|
: this.parenthesizedExpression(expression);
|
|
1395
1537
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1538
|
+
/**
|
|
1539
|
+
* Emit an operand the grammar requires to be a `LeftHandSideExpression`,
|
|
1540
|
+
* mirroring the legacy parenthesizer's
|
|
1541
|
+
* `parenthesizeLeftSideOfAccess(expression, optionalChain)`.
|
|
1542
|
+
*
|
|
1543
|
+
* `optionalChain` is the **consuming** node's own chain-ness, not the
|
|
1544
|
+
* operand's. An optional chain may be emitted bare only when the node
|
|
1545
|
+
* consuming it continues the same chain: `a?.b?.()` is one chain, while
|
|
1546
|
+
* `(a?.b)()` is a plain call on the chain's value. Emitting the second as
|
|
1547
|
+
* `a?.b()` re-parses as the first, which stops throwing on a nullish head,
|
|
1548
|
+
* and in `new`, tagged-template and decorator position it does not compile at
|
|
1549
|
+
* all.
|
|
1550
|
+
*/
|
|
1551
|
+
leftSideExpression(expression, optionalChain) {
|
|
1552
|
+
return this.leftSideNeedsParentheses(expression, optionalChain)
|
|
1553
|
+
? this.parenthesizedExpression(expression)
|
|
1554
|
+
: this.emit(expression);
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Whether {@link leftSideExpression} wraps this operand.
|
|
1558
|
+
*
|
|
1559
|
+
* The legacy rule also parenthesizes an argument-less `new` here, because it
|
|
1560
|
+
* prints `new X` bare and `new X.y` would re-parse with `y` on the target.
|
|
1561
|
+
* This printer always emits the argument list, so `new X().y` already says
|
|
1562
|
+
* what the tree says and needs no wrapper.
|
|
1563
|
+
*/
|
|
1564
|
+
leftSideNeedsParentheses(expression, optionalChain) {
|
|
1565
|
+
if (!this.isLeftHandSideExpression(expression))
|
|
1566
|
+
return true;
|
|
1567
|
+
return !optionalChain && this.isOptionalChain(expression);
|
|
1568
|
+
}
|
|
1569
|
+
isOptionalChain(expression) {
|
|
1570
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1571
|
+
switch (expression.kind) {
|
|
1572
|
+
case "CallChain":
|
|
1573
|
+
case "ElementAccessChain":
|
|
1574
|
+
case "NonNullChain":
|
|
1575
|
+
case "PropertyAccessChain":
|
|
1576
|
+
return true;
|
|
1577
|
+
default:
|
|
1578
|
+
return false;
|
|
1579
|
+
}
|
|
1400
1580
|
}
|
|
1401
1581
|
newExpressionTarget(expression) {
|
|
1402
1582
|
return this.newExpressionTargetNeedsParentheses(expression)
|
|
@@ -1407,20 +1587,69 @@ class TsPrinter {
|
|
|
1407
1587
|
* Whether a `new` target must be parenthesized to keep its call arguments
|
|
1408
1588
|
* from re-binding to the `new` — mirroring the legacy printer's
|
|
1409
1589
|
* `parenthesizeExpressionOfNew`. A `new` target is grammatically a
|
|
1410
|
-
* `MemberExpression`, so a call anywhere on the target's left spine
|
|
1411
|
-
* a direct one: `new (f().bar)()`, `new (a.b().c)()`) would
|
|
1412
|
-
* re-parse with the call's arguments consumed by the `new` — a
|
|
1413
|
-
* program. Argument-less `new` on the spine is kept parenthesized
|
|
1414
|
-
* continuity with the direct case, though this printer always prints an
|
|
1415
|
-
* argument list, which already disambiguates it.
|
|
1590
|
+
* `MemberExpression`, so a call anywhere on the target's printed left spine
|
|
1591
|
+
* (not just a direct one: `new (f().bar)()`, `new (a.b().c)()`) would
|
|
1592
|
+
* otherwise re-parse with the call's arguments consumed by the `new` — a
|
|
1593
|
+
* different program. Argument-less `new` on the spine is kept parenthesized
|
|
1594
|
+
* for continuity with the direct case, though this printer always prints an
|
|
1595
|
+
* argument list, which already disambiguates it. Anything else falls back to
|
|
1596
|
+
* the shared left-side rule, which is what parenthesizes an optional-chain
|
|
1597
|
+
* target (`new (a?.b)()`, TS1209 without it).
|
|
1416
1598
|
*/
|
|
1417
1599
|
newExpressionTargetNeedsParentheses(expression) {
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
leftmost.kind === "
|
|
1423
|
-
|
|
1600
|
+
const leftmost = this.leftmostPrintedExpression(expression);
|
|
1601
|
+
if (leftmost !== undefined) {
|
|
1602
|
+
if (leftmost.kind === "CallExpression" || leftmost.kind === "CallChain")
|
|
1603
|
+
return true;
|
|
1604
|
+
if (leftmost.kind === "NewExpression")
|
|
1605
|
+
return leftmost.arguments === undefined;
|
|
1606
|
+
}
|
|
1607
|
+
return this.leftSideNeedsParentheses(expression, false);
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* The node whose own text opens `expression`'s printed form, or `undefined`
|
|
1611
|
+
* when that text opens with a printer-inserted `(`.
|
|
1612
|
+
*
|
|
1613
|
+
* The legacy factory parenthesizes each operand as it builds the node, so its
|
|
1614
|
+
* `getLeftmostExpression` walk halts on the resulting
|
|
1615
|
+
* `ParenthesizedExpression`. This printer decides the same parentheses at
|
|
1616
|
+
* emit time instead, so the walk has to ask {@link leftSideNeedsParentheses}
|
|
1617
|
+
* the same question directly; otherwise `new` re-wraps a target whose call is
|
|
1618
|
+
* already behind parentheses, and `new (f?.()).bar()` comes out as `new
|
|
1619
|
+
* ((f?.()).bar)()`. Calls halt the walk, matching the legacy
|
|
1620
|
+
* `stopAtCallExpressions` mode this predicate is the only user of.
|
|
1621
|
+
*/
|
|
1622
|
+
leftmostPrintedExpression(expression) {
|
|
1623
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1624
|
+
switch (expression.kind) {
|
|
1625
|
+
case "CallExpression":
|
|
1626
|
+
case "CallChain":
|
|
1627
|
+
return expression;
|
|
1628
|
+
case "ElementAccessExpression":
|
|
1629
|
+
case "NonNullExpression":
|
|
1630
|
+
case "PropertyAccessExpression":
|
|
1631
|
+
return this.leftmostPrintedLeftSide(expression.expression, false);
|
|
1632
|
+
case "ElementAccessChain":
|
|
1633
|
+
case "NonNullChain":
|
|
1634
|
+
case "PropertyAccessChain":
|
|
1635
|
+
return this.leftmostPrintedLeftSide(expression.expression, true);
|
|
1636
|
+
case "TaggedTemplateExpression":
|
|
1637
|
+
return this.leftmostPrintedLeftSide(expression.tag, false);
|
|
1638
|
+
case "AsExpression":
|
|
1639
|
+
case "SatisfiesExpression":
|
|
1640
|
+
return this.leftmostPrintedExpression(expression.expression);
|
|
1641
|
+
case "BinaryExpression":
|
|
1642
|
+
return this.leftmostPrintedExpression(expression.left);
|
|
1643
|
+
case "ConditionalExpression":
|
|
1644
|
+
return this.leftmostPrintedExpression(expression.condition);
|
|
1645
|
+
default:
|
|
1646
|
+
return expression;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
leftmostPrintedLeftSide(operand, optionalChain) {
|
|
1650
|
+
return this.leftSideNeedsParentheses(operand, optionalChain)
|
|
1651
|
+
? undefined
|
|
1652
|
+
: this.leftmostPrintedExpression(operand);
|
|
1424
1653
|
}
|
|
1425
1654
|
prefixUnaryOperand(operand, operator) {
|
|
1426
1655
|
const body = this.isUnaryExpression(operand)
|
|
@@ -1469,42 +1698,45 @@ class TsPrinter {
|
|
|
1469
1698
|
? this.expressionForDisallowedComma(expression)
|
|
1470
1699
|
: this.parenthesizedExpression(expression);
|
|
1471
1700
|
}
|
|
1472
|
-
binaryOperand(operator, operand, isLeftSide, leftOperand) {
|
|
1701
|
+
binaryOperand(operator, operand, isLeftSide, leftOperand, assignmentTarget = false) {
|
|
1473
1702
|
return this.binaryOperandNeedsParentheses(operator, operand, isLeftSide, leftOperand)
|
|
1474
1703
|
? this.parenthesizedExpression(operand)
|
|
1475
|
-
: this.emit(operand);
|
|
1704
|
+
: this.emit(operand, assignmentTarget);
|
|
1476
1705
|
}
|
|
1477
1706
|
binaryOperandNeedsParentheses(operator, operand, isLeftSide, leftOperand) {
|
|
1478
|
-
|
|
1707
|
+
const emittedOperand = this.skipPartiallyEmittedExpressions(operand);
|
|
1708
|
+
if (emittedOperand.kind === "ParenthesizedExpression")
|
|
1479
1709
|
return false;
|
|
1480
1710
|
if (operator === SyntaxKind.AsteriskAsteriskToken &&
|
|
1481
1711
|
isLeftSide &&
|
|
1482
|
-
this.expressionPrecedence(
|
|
1712
|
+
this.expressionPrecedence(emittedOperand) === ExpressionPrecedence.Unary)
|
|
1483
1713
|
return true;
|
|
1484
|
-
if (
|
|
1485
|
-
this.mixingBinaryOperatorsRequiresParentheses(operator,
|
|
1714
|
+
if (emittedOperand.kind === "BinaryExpression" &&
|
|
1715
|
+
this.mixingBinaryOperatorsRequiresParentheses(operator, emittedOperand.operator))
|
|
1486
1716
|
return true;
|
|
1487
1717
|
const operatorPrecedence = this.binaryOperatorPrecedence(operator);
|
|
1488
|
-
const operandPrecedence = this.expressionPrecedence(
|
|
1718
|
+
const operandPrecedence = this.expressionPrecedence(emittedOperand);
|
|
1489
1719
|
if (operandPrecedence < operatorPrecedence)
|
|
1490
1720
|
return true;
|
|
1491
1721
|
if (operandPrecedence > operatorPrecedence)
|
|
1492
1722
|
return false;
|
|
1493
1723
|
if (isLeftSide)
|
|
1494
1724
|
return this.binaryOperatorAssociativity(operator) === Associativity.Right;
|
|
1495
|
-
if (
|
|
1725
|
+
if (emittedOperand.kind === "BinaryExpression" &&
|
|
1726
|
+
emittedOperand.operator === operator) {
|
|
1496
1727
|
if (this.operatorHasAssociativeProperty(operator))
|
|
1497
1728
|
return false;
|
|
1498
1729
|
if (operator === SyntaxKind.PlusToken &&
|
|
1499
1730
|
leftOperand !== undefined &&
|
|
1500
1731
|
this.literalKindOfBinaryPlusOperand(leftOperand) !== undefined &&
|
|
1501
1732
|
this.literalKindOfBinaryPlusOperand(leftOperand) ===
|
|
1502
|
-
this.literalKindOfBinaryPlusOperand(
|
|
1733
|
+
this.literalKindOfBinaryPlusOperand(emittedOperand))
|
|
1503
1734
|
return false;
|
|
1504
1735
|
}
|
|
1505
|
-
return this.expressionAssociativity(
|
|
1736
|
+
return this.expressionAssociativity(emittedOperand) === Associativity.Left;
|
|
1506
1737
|
}
|
|
1507
1738
|
expressionPrecedence(expression) {
|
|
1739
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1508
1740
|
switch (expression.kind) {
|
|
1509
1741
|
case "CommaListExpression":
|
|
1510
1742
|
return ExpressionPrecedence.Comma;
|
|
@@ -1546,6 +1778,7 @@ class TsPrinter {
|
|
|
1546
1778
|
}
|
|
1547
1779
|
}
|
|
1548
1780
|
expressionAssociativity(expression) {
|
|
1781
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1549
1782
|
switch (expression.kind) {
|
|
1550
1783
|
case "NewExpression":
|
|
1551
1784
|
return expression.arguments === undefined
|
|
@@ -1649,6 +1882,7 @@ class TsPrinter {
|
|
|
1649
1882
|
operator === SyntaxKind.CommaToken);
|
|
1650
1883
|
}
|
|
1651
1884
|
literalKindOfBinaryPlusOperand(expression) {
|
|
1885
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1652
1886
|
switch (expression.kind) {
|
|
1653
1887
|
case "StringLiteral":
|
|
1654
1888
|
case "NumericLiteral":
|
|
@@ -1671,6 +1905,7 @@ class TsPrinter {
|
|
|
1671
1905
|
return this.expressionPrecedence(expression) >= ExpressionPrecedence.Unary;
|
|
1672
1906
|
}
|
|
1673
1907
|
isLeftHandSideExpression(expression) {
|
|
1908
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1674
1909
|
switch (expression.kind) {
|
|
1675
1910
|
case "ArrowFunction":
|
|
1676
1911
|
case "ClassExpression":
|
|
@@ -1701,18 +1936,19 @@ class TsPrinter {
|
|
|
1701
1936
|
}
|
|
1702
1937
|
/**
|
|
1703
1938
|
* Walk to the expression's leftmost node — the one that starts its printed
|
|
1704
|
-
* text
|
|
1705
|
-
*
|
|
1706
|
-
*
|
|
1939
|
+
* text — matching the legacy `getLeftmostExpression`.
|
|
1940
|
+
*
|
|
1941
|
+
* Used by the statement, concise-body and export-default predicates, which
|
|
1942
|
+
* ask only whether the text opens with a `function`, `class` or `{` token.
|
|
1943
|
+
* The `new`-target predicate needs the printed left edge instead and uses
|
|
1944
|
+
* {@link leftmostPrintedExpression}.
|
|
1707
1945
|
*/
|
|
1708
|
-
leftmostExpression(expression
|
|
1946
|
+
leftmostExpression(expression) {
|
|
1947
|
+
expression = this.skipPartiallyEmittedExpressions(expression);
|
|
1709
1948
|
switch (expression.kind) {
|
|
1949
|
+
case "AsExpression":
|
|
1710
1950
|
case "CallExpression":
|
|
1711
1951
|
case "CallChain":
|
|
1712
|
-
if (stopAtCall)
|
|
1713
|
-
return expression;
|
|
1714
|
-
return this.leftmostExpression(expression.expression, stopAtCall);
|
|
1715
|
-
case "AsExpression":
|
|
1716
1952
|
case "ElementAccessExpression":
|
|
1717
1953
|
case "ElementAccessChain":
|
|
1718
1954
|
case "NonNullExpression":
|
|
@@ -1720,18 +1956,19 @@ class TsPrinter {
|
|
|
1720
1956
|
case "PropertyAccessExpression":
|
|
1721
1957
|
case "PropertyAccessChain":
|
|
1722
1958
|
case "SatisfiesExpression":
|
|
1723
|
-
return this.leftmostExpression(expression.expression
|
|
1959
|
+
return this.leftmostExpression(expression.expression);
|
|
1724
1960
|
case "BinaryExpression":
|
|
1725
|
-
return this.leftmostExpression(expression.left
|
|
1961
|
+
return this.leftmostExpression(expression.left);
|
|
1726
1962
|
case "ConditionalExpression":
|
|
1727
|
-
return this.leftmostExpression(expression.condition
|
|
1963
|
+
return this.leftmostExpression(expression.condition);
|
|
1728
1964
|
case "TaggedTemplateExpression":
|
|
1729
|
-
return this.leftmostExpression(expression.tag
|
|
1965
|
+
return this.leftmostExpression(expression.tag);
|
|
1730
1966
|
default:
|
|
1731
1967
|
return expression;
|
|
1732
1968
|
}
|
|
1733
1969
|
}
|
|
1734
1970
|
needsPrefixUnaryGap(operator, operand) {
|
|
1971
|
+
operand = this.skipPartiallyEmittedExpressions(operand);
|
|
1735
1972
|
if (operator === undefined || operand.kind !== "PrefixUnaryExpression")
|
|
1736
1973
|
return false;
|
|
1737
1974
|
return ((operator === SyntaxKind.PlusToken &&
|
|
@@ -1851,15 +2088,84 @@ const escapeTemplateText = (text) => text
|
|
|
1851
2088
|
.replace(/\$\{/g, "\\${")
|
|
1852
2089
|
.replace(/\r\n/g, "\\r\\n")
|
|
1853
2090
|
.replace(/\r/g, "\\r");
|
|
2091
|
+
/**
|
|
2092
|
+
* Whether a JSX text child means the same thing with a line break and
|
|
2093
|
+
* indentation around it.
|
|
2094
|
+
*
|
|
2095
|
+
* JSX drops a whitespace-only child that contains a newline and trims an edge
|
|
2096
|
+
* whose whitespace contains one, so only a child with non-whitespace content
|
|
2097
|
+
* and no edge whitespace survives being moved onto its own line. Newlines
|
|
2098
|
+
* _inside_ the text are unaffected, because JSX collapses each interior line
|
|
2099
|
+
* break to a single space in either layout.
|
|
2100
|
+
*/
|
|
2101
|
+
const isBreakSafeJsxText = (text) => text.length !== 0 && !/^\s/.test(text) && !/\s$/.test(text);
|
|
2102
|
+
/**
|
|
2103
|
+
* Escape a string literal's text so the printed program holds the value the AST
|
|
2104
|
+
* carries.
|
|
2105
|
+
*
|
|
2106
|
+
* The old set was the backslash, LF, CR, TAB and the active quote. Everything
|
|
2107
|
+
* else was emitted raw, which is three separate hazards rather than a cosmetic
|
|
2108
|
+
* gap: a C0 control or DEL lands in the generated file as itself; U+2028 and
|
|
2109
|
+
* U+2029 terminate a string literal in any JavaScript engine predating ES2019,
|
|
2110
|
+
* so the emitted program does not parse; and a lone surrogate becomes U+FFFD
|
|
2111
|
+
* the moment the text is written as UTF-8, so the generated program holds a
|
|
2112
|
+
* different string than the caller built.
|
|
2113
|
+
*
|
|
2114
|
+
* Iterated by code point rather than matched by a pattern. That is what makes
|
|
2115
|
+
* the surrogate case fall out instead of needing a rule: a well-formed pair
|
|
2116
|
+
* arrives as one two-unit string and passes through, and a lone surrogate
|
|
2117
|
+
* arrives as a single unit whose code point is in the surrogate range.
|
|
2118
|
+
*/
|
|
1854
2119
|
const escapeString = (text, singleQuote) => {
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
2120
|
+
const quote = singleQuote === true ? "'" : '"';
|
|
2121
|
+
let escaped = "";
|
|
2122
|
+
for (const ch of text) {
|
|
2123
|
+
if (ch === "\\") {
|
|
2124
|
+
escaped += "\\\\";
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
if (ch === quote) {
|
|
2128
|
+
escaped += "\\" + ch;
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
// The inactive quote is ordinary text and stays as written.
|
|
2132
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
2133
|
+
const lone = code >= 0xd800 && code <= 0xdfff;
|
|
2134
|
+
if (ch.length === 2 ||
|
|
2135
|
+
(code >= 0x20 &&
|
|
2136
|
+
code !== 0x7f &&
|
|
2137
|
+
code !== 0x2028 &&
|
|
2138
|
+
code !== 0x2029 &&
|
|
2139
|
+
!lone)) {
|
|
2140
|
+
escaped += ch;
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
switch (code) {
|
|
2144
|
+
case 0x08:
|
|
2145
|
+
escaped += "\\b";
|
|
2146
|
+
continue;
|
|
2147
|
+
case 0x09:
|
|
2148
|
+
escaped += "\\t";
|
|
2149
|
+
continue;
|
|
2150
|
+
case 0x0a:
|
|
2151
|
+
escaped += "\\n";
|
|
2152
|
+
continue;
|
|
2153
|
+
case 0x0b:
|
|
2154
|
+
escaped += "\\v";
|
|
2155
|
+
continue;
|
|
2156
|
+
case 0x0c:
|
|
2157
|
+
escaped += "\\f";
|
|
2158
|
+
continue;
|
|
2159
|
+
case 0x0d:
|
|
2160
|
+
escaped += "\\r";
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
escaped +=
|
|
2164
|
+
code > 0xff
|
|
2165
|
+
? "\\u" + code.toString(16).padStart(4, "0")
|
|
2166
|
+
: "\\x" + code.toString(16).padStart(2, "0");
|
|
2167
|
+
}
|
|
2168
|
+
return `${quote}${escaped}${quote}`;
|
|
1863
2169
|
};
|
|
1864
2170
|
const ExpressionPrecedence = {
|
|
1865
2171
|
Comma: 0,
|