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