@ohos-ports/ember-estree 0.6.11-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/print.js ADDED
@@ -0,0 +1,1095 @@
1
+ /**
2
+ * Prints a comment as it appeared in source.
3
+ * @param {object} comment - `{ type: "Line" | "Block", value }`
4
+ * @return {string}
5
+ */
6
+ function printComment(comment) {
7
+ return comment.type === "Block" ? `/*${comment.value}*/` : `//${comment.value}`;
8
+ }
9
+
10
+ // Comment-weaving state for `print(File)`. `print` is synchronous and
11
+ // single-threaded, so module-level state is safe; `printFile` saves and
12
+ // restores it so nested calls stay well-behaved.
13
+ let fileComments = null;
14
+ let commentCursor = 0;
15
+
16
+ /**
17
+ * Prints and consumes any not-yet-emitted comments that start before
18
+ * `position` in the original source.
19
+ *
20
+ * `fileComments` is sorted and `commentCursor` only ever advances, so the
21
+ * total flushing work across an entire `print(File)` is O(comments) — each
22
+ * comment is visited exactly once, no matter how many nodes are printed.
23
+ *
24
+ * @param {number} position
25
+ * @return {string}
26
+ */
27
+ function flushCommentsBefore(position) {
28
+ let out = "";
29
+ while (commentCursor < fileComments.length && fileComments[commentCursor].start < position) {
30
+ out += `${printComment(fileComments[commentCursor])}\n`;
31
+ commentCursor += 1;
32
+ }
33
+ return out;
34
+ }
35
+
36
+ /**
37
+ * Prints a File node: its program, with `file.comments` woven back in
38
+ * before the nearest printed node that follows them in the original
39
+ * source (and any remaining comments appended at the end of the file).
40
+ *
41
+ * Placement is approximate -- a same-line trailing comment becomes a
42
+ * leading comment of the next node -- so pair the output with a formatter
43
+ * (e.g. prettier) when exact layout matters.
44
+ *
45
+ * @param {object} file
46
+ * @return {string}
47
+ */
48
+ function printFile(file) {
49
+ const previousComments = fileComments;
50
+ const previousCursor = commentCursor;
51
+
52
+ // `filter` already yields a fresh array, so sorting in place is safe and
53
+ // the caller's `file.comments` is never mutated. (oxc emits comments
54
+ // pre-sorted, making the sort a cheap single pass.)
55
+ const comments = file.comments?.length
56
+ ? file.comments
57
+ .filter((comment) => typeof comment.start === "number")
58
+ .sort((a, b) => a.start - b.start)
59
+ : null;
60
+
61
+ fileComments = comments?.length ? comments : null;
62
+ commentCursor = 0;
63
+
64
+ try {
65
+ let output = print(file.program);
66
+ const trailing = fileComments ? flushCommentsBefore(Infinity) : "";
67
+
68
+ if (trailing) {
69
+ output = output ? `${output}\n${trailing}` : trailing;
70
+ }
71
+
72
+ return output;
73
+ } finally {
74
+ fileComments = previousComments;
75
+ commentCursor = previousCursor;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Recursive AST printer that handles ESTree, TypeScript, and
81
+ * Glimmer template node types.
82
+ *
83
+ * JSX nodes are not supported — Ember uses Glimmer templates instead.
84
+ *
85
+ * Tools like zmod use span-based patching (preserving the original source
86
+ * for unchanged regions), so this printer is typically only invoked for
87
+ * newly-created AST nodes (via builders) — with one exception: a `File`
88
+ * node (as returned by `toTree`) is printed in full, with its `comments`
89
+ * woven back into the output.
90
+ *
91
+ * @param {object} node - The AST node to print
92
+ * @return {string}
93
+ */
94
+ export function print(node) {
95
+ if (!node) return "";
96
+ if (typeof node === "string") return node;
97
+
98
+ // Comment weaving — active only while a comment-carrying `print(File)`
99
+ // is in flight. When any comments start before this node in the original
100
+ // source, emit them first, then re-enter (the cursor has advanced past
101
+ // them, so the recursion falls straight through to the switch). Outside
102
+ // of `print(File)` the guard short-circuits on its first check, so
103
+ // standalone printing pays a single null test.
104
+ if (
105
+ fileComments !== null &&
106
+ commentCursor < fileComments.length &&
107
+ typeof node.start === "number" &&
108
+ fileComments[commentCursor].start < node.start
109
+ ) {
110
+ return flushCommentsBefore(node.start) + print(node);
111
+ }
112
+
113
+ switch (node.type) {
114
+ // ── File (root of `toTree`) ───────────────────────────────────
115
+ case "File":
116
+ return printFile(node);
117
+
118
+ // ── Identifiers & Literals ────────────────────────────────────
119
+ case "Identifier":
120
+ return printTypeAnnotated(node.name, node);
121
+
122
+ case "PrivateIdentifier":
123
+ return `#${node.name}`;
124
+
125
+ case "Literal":
126
+ case "StringLiteral":
127
+ if (typeof node.value === "string") {
128
+ // Prefer the original source: it preserves escape sequences
129
+ // (`\n`, `\t`, `\uXXXX`, escaped quotes) and the original quote
130
+ // style exactly. Emitting the cooked `value` turns escapes into raw
131
+ // characters and corrupts the string (e.g. `'\n'` -> a real newline,
132
+ // which breaks a single-quoted literal).
133
+ const raw = node.extra?.raw ?? node.raw;
134
+ if (raw != null) return raw;
135
+ // Synthesized node with no source — quote and escape the value.
136
+ return JSON.stringify(node.value);
137
+ }
138
+ if (node.raw != null) return node.raw;
139
+ return String(node.value);
140
+
141
+ case "NumericLiteral":
142
+ return String(node.value);
143
+
144
+ case "BooleanLiteral":
145
+ return String(node.value);
146
+
147
+ case "NullLiteral":
148
+ return "null";
149
+
150
+ case "RegExpLiteral":
151
+ return `/${node.pattern}/${node.flags ?? ""}`;
152
+
153
+ case "TemplateLiteral": {
154
+ const quasis = node.quasis ?? [];
155
+ const exprs = node.expressions ?? [];
156
+ let result = "`";
157
+ for (let i = 0; i < quasis.length; i++) {
158
+ result += quasis[i].value?.raw ?? quasis[i].value?.cooked ?? "";
159
+ if (i < exprs.length) {
160
+ result += "${" + print(exprs[i]) + "}";
161
+ }
162
+ }
163
+ return result + "`";
164
+ }
165
+
166
+ case "TemplateElement":
167
+ return node.value?.raw ?? "";
168
+
169
+ // ── Expressions ────────────────────────────────────────────────
170
+ case "CallExpression":
171
+ case "OptionalCallExpression": {
172
+ const callee = print(node.callee);
173
+ const typeArgs = node.typeParameters ? print(node.typeParameters) : "";
174
+ const args = (node.arguments ?? []).map(print).join(", ");
175
+ const opt = node.optional ? "?." : "";
176
+ return `${callee}${opt}${typeArgs}(${args})`;
177
+ }
178
+
179
+ case "MemberExpression":
180
+ case "OptionalMemberExpression": {
181
+ const obj = print(node.object);
182
+ const prop = print(node.property);
183
+ if (node.computed) return `${obj}[${prop}]`;
184
+ const opt = node.optional ? "?." : ".";
185
+ return `${obj}${opt}${prop}`;
186
+ }
187
+
188
+ case "ChainExpression":
189
+ return print(node.expression);
190
+
191
+ case "V8IntrinsicExpression": {
192
+ const name = typeof node.name === "string" ? node.name : print(node.name);
193
+ const args = (node.arguments ?? []).map(print).join(", ");
194
+ return `%${name}(${args})`;
195
+ }
196
+
197
+ case "ParenthesizedExpression":
198
+ return `(${print(node.expression)})`;
199
+
200
+ case "ArrowFunctionExpression": {
201
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
202
+ const params = (node.params ?? []).map(print).join(", ");
203
+ const returnType = node.returnType ? print(node.returnType) : "";
204
+ const body = print(node.body);
205
+ const async = node.async ? "async " : "";
206
+ return `${async}${typeParams}(${params})${returnType} => ${body}`;
207
+ }
208
+
209
+ case "FunctionExpression": {
210
+ const id = node.id ? " " + print(node.id) : "";
211
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
212
+ const params = (node.params ?? []).map(print).join(", ");
213
+ const returnType = node.returnType ? print(node.returnType) : "";
214
+ const body = print(node.body);
215
+ const async = node.async ? "async " : "";
216
+ const gen = node.generator ? "*" : "";
217
+ return `${async}function${gen}${id}${typeParams}(${params})${returnType} ${body}`;
218
+ }
219
+
220
+ case "AssignmentExpression":
221
+ return `${print(node.left)} ${node.operator} ${print(node.right)}`;
222
+
223
+ case "BinaryExpression":
224
+ case "LogicalExpression":
225
+ return `${print(node.left)} ${node.operator} ${print(node.right)}`;
226
+
227
+ case "UnaryExpression":
228
+ if (node.prefix) {
229
+ const space = node.operator.length > 1 ? " " : "";
230
+ return `${node.operator}${space}${print(node.argument)}`;
231
+ }
232
+ return `${print(node.argument)}${node.operator}`;
233
+
234
+ case "UpdateExpression":
235
+ return node.prefix
236
+ ? `${node.operator}${print(node.argument)}`
237
+ : `${print(node.argument)}${node.operator}`;
238
+
239
+ case "ConditionalExpression":
240
+ return `${print(node.test)} ? ${print(node.consequent)} : ${print(node.alternate)}`;
241
+
242
+ case "SequenceExpression":
243
+ return (node.expressions ?? []).map(print).join(", ");
244
+
245
+ case "SpreadElement":
246
+ case "ExperimentalSpreadProperty":
247
+ return `...${print(node.argument)}`;
248
+
249
+ case "YieldExpression":
250
+ return node.delegate ? `yield* ${print(node.argument)}` : `yield ${print(node.argument)}`;
251
+
252
+ case "AwaitExpression":
253
+ return `await ${print(node.argument)}`;
254
+
255
+ case "TaggedTemplateExpression":
256
+ return `${print(node.tag)}${print(node.quasi)}`;
257
+
258
+ case "NewExpression": {
259
+ const callee = print(node.callee);
260
+ const typeArgs = node.typeParameters ? print(node.typeParameters) : "";
261
+ const args = (node.arguments ?? []).map(print).join(", ");
262
+ return `new ${callee}${typeArgs}(${args})`;
263
+ }
264
+
265
+ case "ThisExpression":
266
+ return "this";
267
+
268
+ case "Super":
269
+ return "super";
270
+
271
+ case "MetaProperty":
272
+ return `${print(node.meta)}.${print(node.property)}`;
273
+
274
+ case "ImportExpression": {
275
+ const source = print(node.source);
276
+ return `import(${source})`;
277
+ }
278
+
279
+ // ── Patterns ───────────────────────────────────────────────────
280
+ case "ArrayExpression":
281
+ case "ArrayPattern": {
282
+ const elems = (node.elements ?? []).map((e) => (e ? print(e) : "")).join(", ");
283
+ return `[${elems}]`;
284
+ }
285
+
286
+ case "ObjectExpression":
287
+ case "ObjectPattern": {
288
+ const props = (node.properties ?? []).map(print).join(", ");
289
+ return `{ ${props} }`;
290
+ }
291
+
292
+ case "Property": {
293
+ const key = print(node.key);
294
+ if (node.shorthand) return key;
295
+ if (node.method) {
296
+ const params = (node.value?.params ?? []).map(print).join(", ");
297
+ const body = print(node.value?.body);
298
+ return `${key}(${params}) ${body}`;
299
+ }
300
+ return `${key}: ${print(node.value)}`;
301
+ }
302
+
303
+ case "RestElement":
304
+ case "ExperimentalRestProperty":
305
+ return `...${print(node.argument)}`;
306
+
307
+ case "AssignmentPattern":
308
+ return `${print(node.left)} = ${print(node.right)}`;
309
+
310
+ // ── Statements ─────────────────────────────────────────────────
311
+ case "ExpressionStatement":
312
+ return print(node.expression) + ";";
313
+
314
+ case "BlockStatement":
315
+ case "StaticBlock": {
316
+ const body = (node.body ?? []).map(print).join("\n");
317
+ return braceBlock(body);
318
+ }
319
+
320
+ case "EmptyStatement":
321
+ return ";";
322
+
323
+ case "DebuggerStatement":
324
+ return "debugger;";
325
+
326
+ case "ReturnStatement":
327
+ return node.argument ? `return ${print(node.argument)};` : "return;";
328
+
329
+ case "BreakStatement":
330
+ return node.label ? `break ${print(node.label)};` : "break;";
331
+
332
+ case "ContinueStatement":
333
+ return node.label ? `continue ${print(node.label)};` : "continue;";
334
+
335
+ case "LabeledStatement":
336
+ return `${print(node.label)}: ${print(node.body)}`;
337
+
338
+ case "VariableDeclaration": {
339
+ const decls = (node.declarations ?? []).map(print).join(", ");
340
+ const declare = node.declare ? "declare " : "";
341
+ return `${declare}${node.kind} ${decls};`;
342
+ }
343
+
344
+ case "VariableDeclarator": {
345
+ const id = print(node.id);
346
+ return node.init ? `${id} = ${print(node.init)}` : id;
347
+ }
348
+
349
+ case "IfStatement": {
350
+ let result = `if (${print(node.test)}) ${print(node.consequent)}`;
351
+ if (node.alternate) result += ` else ${print(node.alternate)}`;
352
+ return result;
353
+ }
354
+
355
+ case "SwitchStatement": {
356
+ const disc = print(node.discriminant);
357
+ const cases = (node.cases ?? []).map(print).join("\n");
358
+ return `switch (${disc}) ${braceBlock(cases)}`;
359
+ }
360
+
361
+ case "SwitchCase": {
362
+ const test = node.test ? `case ${print(node.test)}:` : "default:";
363
+ const body = (node.consequent ?? []).map(print).join("\n");
364
+ return body ? `${test}\n${indent(body)}` : test;
365
+ }
366
+
367
+ case "ThrowStatement":
368
+ return `throw ${print(node.argument)};`;
369
+
370
+ case "TryStatement": {
371
+ let result = `try ${print(node.block)}`;
372
+ if (node.handler) result += ` ${print(node.handler)}`;
373
+ if (node.finalizer) result += ` finally ${print(node.finalizer)}`;
374
+ return result;
375
+ }
376
+
377
+ case "CatchClause": {
378
+ const param = node.param ? `(${print(node.param)})` : "";
379
+ return `catch${param ? " " + param : ""} ${print(node.body)}`;
380
+ }
381
+
382
+ case "WhileStatement":
383
+ return `while (${print(node.test)}) ${print(node.body)}`;
384
+
385
+ case "DoWhileStatement":
386
+ return `do ${print(node.body)} while (${print(node.test)});`;
387
+
388
+ case "ForStatement": {
389
+ const init = node.init ? print(node.init).replace(/;$/, "") : "";
390
+ const test = node.test ? print(node.test) : "";
391
+ const update = node.update ? print(node.update) : "";
392
+ return `for (${init}; ${test}; ${update}) ${print(node.body)}`;
393
+ }
394
+
395
+ case "ForInStatement":
396
+ return `for (${print(node.left)} in ${print(node.right)}) ${print(node.body)}`;
397
+
398
+ case "ForOfStatement": {
399
+ const aw = node.await ? "await " : "";
400
+ return `for ${aw}(${print(node.left)} of ${print(node.right)}) ${print(node.body)}`;
401
+ }
402
+
403
+ case "WithStatement":
404
+ return `with (${print(node.object)}) ${print(node.body)}`;
405
+
406
+ // ── Declarations ───────────────────────────────────────────────
407
+ case "FunctionDeclaration":
408
+ case "TSDeclareFunction": {
409
+ const id = node.id ? print(node.id) : "";
410
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
411
+ const params = (node.params ?? []).map(print).join(", ");
412
+ const returnType = node.returnType ? print(node.returnType) : "";
413
+ const body = node.body ? " " + print(node.body) : ";";
414
+ const async = node.async ? "async " : "";
415
+ const gen = node.generator ? "*" : "";
416
+ const declare = node.declare ? "declare " : "";
417
+ return `${declare}${async}function${gen} ${id}${typeParams}(${params})${returnType}${body}`;
418
+ }
419
+
420
+ case "ClassDeclaration":
421
+ case "ClassExpression": {
422
+ const decorators = (node.decorators ?? []).map(print).join("\n");
423
+ const prefix = decorators ? decorators + "\n" : "";
424
+ const declare = node.declare ? "declare " : "";
425
+ const abstract = node.abstract ? "abstract " : "";
426
+ const id = node.id ? ` ${print(node.id)}` : "";
427
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
428
+ const superClass = node.superClass ? ` extends ${print(node.superClass)}` : "";
429
+ const superTypeParams = node.superTypeParameters ? print(node.superTypeParameters) : "";
430
+ const impls = (node.implements ?? []).map(print);
431
+ const implStr = impls.length ? ` implements ${impls.join(", ")}` : "";
432
+ const body = print(node.body);
433
+ return `${prefix}${declare}${abstract}class${id}${typeParams}${superClass}${superTypeParams}${implStr} ${body}`;
434
+ }
435
+
436
+ case "ClassBody": {
437
+ const body = (node.body ?? []).map(print).join("\n");
438
+ return braceBlock(body);
439
+ }
440
+
441
+ case "MethodDefinition":
442
+ case "TSAbstractMethodDefinition": {
443
+ const decorators = (node.decorators ?? []).map(print).join("\n");
444
+ const prefix = decorators ? decorators + "\n" : "";
445
+ const key = print(node.key);
446
+ const value = node.value;
447
+ const typeParams = value?.typeParameters ? print(value.typeParameters) : "";
448
+ const params = (value?.params ?? []).map(print).join(", ");
449
+ const returnType = value?.returnType ? print(value.returnType) : "";
450
+ const body = value?.body ? " " + print(value.body) : ";";
451
+ const staticKw = node.static ? "static " : "";
452
+ const kind = node.kind === "get" ? "get " : node.kind === "set" ? "set " : "";
453
+ const accessibility = node.accessibility ? node.accessibility + " " : "";
454
+ const override = node.override ? "override " : "";
455
+ const abstract = node.type === "TSAbstractMethodDefinition" ? "abstract " : "";
456
+ return `${prefix}${accessibility}${abstract}${override}${staticKw}${kind}${key}${typeParams}(${params})${returnType}${body}`;
457
+ }
458
+
459
+ case "PropertyDefinition":
460
+ case "AccessorProperty":
461
+ case "TSAbstractPropertyDefinition":
462
+ case "TSAbstractAccessorProperty": {
463
+ const decorators = (node.decorators ?? []).map(print).join("\n");
464
+ const prefix = decorators ? decorators + "\n" : "";
465
+ const key = print(node.key);
466
+ const staticKw = node.static ? "static " : "";
467
+ const accessibility = node.accessibility ? node.accessibility + " " : "";
468
+ const override = node.override ? "override " : "";
469
+ const readonly = node.readonly ? "readonly " : "";
470
+ const abstract = node.type.startsWith("TSAbstract") ? "abstract " : "";
471
+ const accessor = node.type.includes("Accessor") ? "accessor " : "";
472
+ const typeAnnotation = node.typeAnnotation ? print(node.typeAnnotation) : "";
473
+ const init = node.value ? ` = ${print(node.value)}` : "";
474
+ return `${prefix}${accessibility}${abstract}${override}${staticKw}${readonly}${accessor}${key}${typeAnnotation}${init};`;
475
+ }
476
+
477
+ case "Decorator":
478
+ return `@${print(node.expression)}`;
479
+
480
+ // ── Imports/Exports ────────────────────────────────────────────
481
+ case "ImportDeclaration": {
482
+ const specs = (node.specifiers ?? []).map(print);
483
+ const source = print(node.source);
484
+ const attrs = (node.attributes ?? []).map(print);
485
+ const attrStr = attrs.length ? ` with { ${attrs.join(", ")} }` : "";
486
+ if (specs.length === 0) return `import ${source}${attrStr};`;
487
+ // `import type { ... }` — a type-only import declaration.
488
+ const typeKind = node.importKind === "type" ? "type " : "";
489
+ const defaultSpec = specs.find(
490
+ (_, i) => node.specifiers[i].type === "ImportDefaultSpecifier",
491
+ );
492
+ const nsSpec = node.specifiers.find((s) => s.type === "ImportNamespaceSpecifier");
493
+ const namedSpecs = node.specifiers.filter((s) => s.type === "ImportSpecifier").map(print);
494
+ const parts = [];
495
+ if (defaultSpec) parts.push(defaultSpec);
496
+ if (nsSpec) parts.push(print(nsSpec));
497
+ if (namedSpecs.length) parts.push(`{ ${namedSpecs.join(", ")} }`);
498
+ return `import ${typeKind}${parts.join(", ")} from ${source}${attrStr};`;
499
+ }
500
+
501
+ case "ImportDefaultSpecifier":
502
+ return print(node.local);
503
+
504
+ case "ImportSpecifier": {
505
+ const imported = print(node.imported);
506
+ const local = print(node.local);
507
+ const spec = imported === local ? imported : `${imported} as ${local}`;
508
+ // Inline `import { type Foo }` modifier.
509
+ return node.importKind === "type" ? `type ${spec}` : spec;
510
+ }
511
+
512
+ case "ImportNamespaceSpecifier":
513
+ return `* as ${print(node.local)}`;
514
+
515
+ case "ImportAttribute":
516
+ return `${print(node.key)}: ${print(node.value)}`;
517
+
518
+ case "ExportDefaultDeclaration":
519
+ return `export default ${print(node.declaration)}`;
520
+
521
+ case "ExportNamedDeclaration":
522
+ if (node.declaration) return `export ${print(node.declaration)}`;
523
+ if (node.specifiers?.length) {
524
+ const specs = node.specifiers.map(print).join(", ");
525
+ const from = node.source ? ` from ${print(node.source)}` : "";
526
+ // `export type { ... }` — a type-only export declaration.
527
+ const typeKind = node.exportKind === "type" ? "type " : "";
528
+ return `export ${typeKind}{ ${specs} }${from};`;
529
+ }
530
+ return "";
531
+
532
+ case "ExportAllDeclaration": {
533
+ const exported = node.exported ? ` as ${print(node.exported)}` : "";
534
+ const typeKind = node.exportKind === "type" ? "type " : "";
535
+ return `export ${typeKind}*${exported} from ${print(node.source)};`;
536
+ }
537
+
538
+ case "ExportSpecifier": {
539
+ const local = print(node.local);
540
+ const exported = print(node.exported);
541
+ const spec = local === exported ? local : `${local} as ${exported}`;
542
+ // Inline `export { type Foo }` modifier.
543
+ return node.exportKind === "type" ? `type ${spec}` : spec;
544
+ }
545
+
546
+ // ── JSX (unsupported — Ember uses Glimmer templates) ─────────
547
+ case "JSXElement":
548
+ case "JSXOpeningElement":
549
+ case "JSXClosingElement":
550
+ case "JSXOpeningFragment":
551
+ case "JSXClosingFragment":
552
+ case "JSXIdentifier":
553
+ case "JSXNamespacedName":
554
+ case "JSXMemberExpression":
555
+ case "JSXAttribute":
556
+ case "JSXExpressionContainer":
557
+ case "JSXEmptyExpression":
558
+ case "JSXText":
559
+ case "JSXSpreadAttribute":
560
+ case "JSXSpreadChild":
561
+ case "JSXFragment":
562
+ throw new Error(
563
+ `ember-estree print: unsupported JSX node type '${node.type}' (use Glimmer template nodes instead)`,
564
+ );
565
+
566
+ // ── TypeScript: type keywords ──────────────────────────────────
567
+ case "TSAnyKeyword":
568
+ return "any";
569
+ case "TSBigIntKeyword":
570
+ return "bigint";
571
+ case "TSBooleanKeyword":
572
+ return "boolean";
573
+ case "TSIntrinsicKeyword":
574
+ return "intrinsic";
575
+ case "TSNeverKeyword":
576
+ return "never";
577
+ case "TSNullKeyword":
578
+ return "null";
579
+ case "TSNumberKeyword":
580
+ return "number";
581
+ case "TSObjectKeyword":
582
+ return "object";
583
+ case "TSStringKeyword":
584
+ return "string";
585
+ case "TSSymbolKeyword":
586
+ return "symbol";
587
+ case "TSUndefinedKeyword":
588
+ return "undefined";
589
+ case "TSUnknownKeyword":
590
+ return "unknown";
591
+ case "TSVoidKeyword":
592
+ return "void";
593
+ case "TSThisType":
594
+ return "this";
595
+
596
+ // ── TypeScript: modifier keywords ──────────────────────────────
597
+ case "TSAbstractKeyword":
598
+ return "abstract";
599
+ case "TSAsyncKeyword":
600
+ return "async";
601
+ case "TSDeclareKeyword":
602
+ return "declare";
603
+ case "TSExportKeyword":
604
+ return "export";
605
+ case "TSPrivateKeyword":
606
+ return "private";
607
+ case "TSProtectedKeyword":
608
+ return "protected";
609
+ case "TSPublicKeyword":
610
+ return "public";
611
+ case "TSReadonlyKeyword":
612
+ return "readonly";
613
+ case "TSStaticKeyword":
614
+ return "static";
615
+
616
+ // ── TypeScript: type annotations & references ──────────────────
617
+ case "TSTypeAnnotation":
618
+ return `: ${print(node.typeAnnotation)}`;
619
+
620
+ case "TSTypeReference": {
621
+ const name = print(node.typeName);
622
+ const params = node.typeParameters ? print(node.typeParameters) : "";
623
+ return `${name}${params}`;
624
+ }
625
+
626
+ case "TSQualifiedName":
627
+ return `${print(node.left)}.${print(node.right)}`;
628
+
629
+ case "TSTypeParameterDeclaration":
630
+ case "TSTypeParameterInstantiation": {
631
+ const params = (node.params ?? []).map(print).join(", ");
632
+ return `<${params}>`;
633
+ }
634
+
635
+ case "TSTypeParameter": {
636
+ const name = typeof node.name === "string" ? node.name : print(node.name);
637
+ const constraint = node.constraint ? ` extends ${print(node.constraint)}` : "";
638
+ const def = node.default ? ` = ${print(node.default)}` : "";
639
+ const inKw = node.in ? "in " : "";
640
+ const outKw = node.out ? "out " : "";
641
+ const constKw = node.const ? "const " : "";
642
+ return `${constKw}${inKw}${outKw}${name}${constraint}${def}`;
643
+ }
644
+
645
+ // ── TypeScript: type operators & combinators ───────────────────
646
+ case "TSUnionType":
647
+ return (node.types ?? []).map(print).join(" | ");
648
+
649
+ case "TSIntersectionType":
650
+ return (node.types ?? []).map(print).join(" & ");
651
+
652
+ case "TSArrayType":
653
+ return `${print(node.elementType)}[]`;
654
+
655
+ case "TSParenthesizedType":
656
+ return `(${print(node.typeAnnotation)})`;
657
+
658
+ // JSDoc type syntax (`?Foo`, `!Foo`, `?`) — `postfix` flips prefix/suffix.
659
+ case "TSJSDocNullableType":
660
+ return node.postfix ? `${print(node.typeAnnotation)}?` : `?${print(node.typeAnnotation)}`;
661
+
662
+ case "TSJSDocNonNullableType":
663
+ return node.postfix ? `${print(node.typeAnnotation)}!` : `!${print(node.typeAnnotation)}`;
664
+
665
+ case "TSJSDocUnknownType":
666
+ return "?";
667
+
668
+ case "TSTupleType": {
669
+ const elems = (node.elementTypes ?? []).map(print).join(", ");
670
+ return `[${elems}]`;
671
+ }
672
+
673
+ case "TSNamedTupleMember": {
674
+ const label = print(node.label);
675
+ const optional = node.optional ? "?" : "";
676
+ return `${label}${optional}: ${print(node.elementType)}`;
677
+ }
678
+
679
+ case "TSOptionalType":
680
+ return `${print(node.typeAnnotation)}?`;
681
+
682
+ case "TSRestType":
683
+ return `...${print(node.typeAnnotation)}`;
684
+
685
+ case "TSTypeOperator": {
686
+ const op = node.operator ?? "";
687
+ return `${op} ${print(node.typeAnnotation)}`;
688
+ }
689
+
690
+ case "TSIndexedAccessType":
691
+ return `${print(node.objectType)}[${print(node.indexType)}]`;
692
+
693
+ case "TSConditionalType":
694
+ return `${print(node.checkType)} extends ${print(node.extendsType)} ? ${print(node.trueType)} : ${print(node.falseType)}`;
695
+
696
+ case "TSInferType":
697
+ return `infer ${print(node.typeParameter)}`;
698
+
699
+ case "TSLiteralType":
700
+ return print(node.literal);
701
+
702
+ case "TSTemplateLiteralType": {
703
+ const quasis = node.quasis ?? [];
704
+ const types = node.types ?? [];
705
+ let result = "`";
706
+ for (let i = 0; i < quasis.length; i++) {
707
+ result += quasis[i].value?.raw ?? quasis[i].value?.cooked ?? "";
708
+ if (i < types.length) {
709
+ result += "${" + print(types[i]) + "}";
710
+ }
711
+ }
712
+ return result + "`";
713
+ }
714
+
715
+ // ── TypeScript: function & constructor types ───────────────────
716
+ case "TSFunctionType":
717
+ case "TSConstructorType": {
718
+ const newKw = node.type === "TSConstructorType" ? "new " : "";
719
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
720
+ const params = (node.params ?? []).map(print).join(", ");
721
+ const returnType = node.returnType ? print(node.returnType) : "";
722
+ return `${newKw}${typeParams}(${params}) => ${returnType.replace(/^: /, "")}`;
723
+ }
724
+
725
+ case "TSCallSignatureDeclaration":
726
+ case "TSConstructSignatureDeclaration": {
727
+ const newKw = node.type === "TSConstructSignatureDeclaration" ? "new " : "";
728
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
729
+ const params = (node.params ?? []).map(print).join(", ");
730
+ const returnType = node.returnType ? print(node.returnType) : "";
731
+ return `${newKw}${typeParams}(${params})${returnType};`;
732
+ }
733
+
734
+ // ── TypeScript: object types & signatures ──────────────────────
735
+ case "TSTypeLiteral": {
736
+ const members = (node.members ?? []).map(print).join("\n");
737
+ return `{\n${members}\n}`;
738
+ }
739
+
740
+ case "TSPropertySignature": {
741
+ const readonly = node.readonly ? "readonly " : "";
742
+ const computed = node.computed ? `[${print(node.key)}]` : print(node.key);
743
+ const optional = node.optional ? "?" : "";
744
+ const typeAnnotation = node.typeAnnotation ? print(node.typeAnnotation) : "";
745
+ return `${readonly}${computed}${optional}${typeAnnotation};`;
746
+ }
747
+
748
+ case "TSMethodSignature": {
749
+ const computed = node.computed ? `[${print(node.key)}]` : print(node.key);
750
+ const optional = node.optional ? "?" : "";
751
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
752
+ const params = (node.params ?? []).map(print).join(", ");
753
+ const returnType = node.returnType ? print(node.returnType) : "";
754
+ return `${computed}${optional}${typeParams}(${params})${returnType};`;
755
+ }
756
+
757
+ case "TSIndexSignature": {
758
+ const params = (node.parameters ?? []).map(print).join(", ");
759
+ const typeAnnotation = node.typeAnnotation ? print(node.typeAnnotation) : "";
760
+ const readonly = node.readonly ? "readonly " : "";
761
+ return `${readonly}[${params}]${typeAnnotation};`;
762
+ }
763
+
764
+ case "TSMappedType": {
765
+ const readonly = printMappedModifier(node.readonly, "readonly ");
766
+ const param = print(node.typeParameter);
767
+ const nameType = node.nameType ? ` as ${print(node.nameType)}` : "";
768
+ const optional = printMappedModifier(node.optional, "?");
769
+ const typeAnnotation = node.typeAnnotation ? `: ${print(node.typeAnnotation)}` : "";
770
+ return `{ ${readonly}[${param}${nameType}]${optional}${typeAnnotation} }`;
771
+ }
772
+
773
+ // ── TypeScript: declarations ───────────────────────────────────
774
+ case "TSInterfaceDeclaration": {
775
+ const declare = node.declare ? "declare " : "";
776
+ const id = print(node.id);
777
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
778
+ const ext = (node.extends ?? []).map(print);
779
+ const extStr = ext.length ? ` extends ${ext.join(", ")}` : "";
780
+ const body = print(node.body);
781
+ return `${declare}interface ${id}${typeParams}${extStr} ${body}`;
782
+ }
783
+
784
+ case "TSInterfaceBody": {
785
+ const body = (node.body ?? []).map(print).join("\n");
786
+ return braceBlock(body);
787
+ }
788
+
789
+ case "TSInterfaceHeritage":
790
+ case "TSClassImplements": {
791
+ const expr = print(node.expression);
792
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
793
+ return `${expr}${typeParams}`;
794
+ }
795
+
796
+ case "TSTypeAliasDeclaration": {
797
+ const declare = node.declare ? "declare " : "";
798
+ const id = print(node.id);
799
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
800
+ return `${declare}type ${id}${typeParams} = ${print(node.typeAnnotation)};`;
801
+ }
802
+
803
+ case "TSEnumDeclaration": {
804
+ const declare = node.declare ? "declare " : "";
805
+ const constKw = node.const ? "const " : "";
806
+ const id = print(node.id);
807
+ // Newer oxc nests members in a TSEnumBody child; older versions put
808
+ // `members` directly on the declaration.
809
+ const members = (node.body?.members ?? node.members ?? []).map(print).join(",\n");
810
+ return `${declare}${constKw}enum ${id} ${braceBlock(members)}`;
811
+ }
812
+
813
+ case "TSEnumBody": {
814
+ const members = (node.members ?? []).map(print).join(",\n");
815
+ return braceBlock(members);
816
+ }
817
+
818
+ case "TSEnumMember": {
819
+ const id = print(node.id);
820
+ return node.initializer ? `${id} = ${print(node.initializer)}` : id;
821
+ }
822
+
823
+ case "TSModuleDeclaration": {
824
+ const declare = node.declare ? "declare " : "";
825
+ const kind = node.kind === "global" ? "global" : `${node.kind ?? "module"} ${print(node.id)}`;
826
+ const body = node.body ? ` ${print(node.body)}` : "";
827
+ return `${declare}${kind}${body}`;
828
+ }
829
+
830
+ case "TSModuleBlock": {
831
+ const body = (node.body ?? []).map(print).join("\n");
832
+ return braceBlock(body);
833
+ }
834
+
835
+ case "TSNamespaceExportDeclaration":
836
+ return `export as namespace ${print(node.id)};`;
837
+
838
+ // ── TypeScript: expressions & assertions ───────────────────────
839
+ case "TSAsExpression":
840
+ return `${print(node.expression)} as ${print(node.typeAnnotation)}`;
841
+
842
+ case "TSSatisfiesExpression":
843
+ return `${print(node.expression)} satisfies ${print(node.typeAnnotation)}`;
844
+
845
+ case "TSTypeAssertion":
846
+ return `<${print(node.typeAnnotation)}>${print(node.expression)}`;
847
+
848
+ case "TSNonNullExpression":
849
+ return `${print(node.expression)}!`;
850
+
851
+ case "TSInstantiationExpression": {
852
+ const expr = print(node.expression);
853
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
854
+ return `${expr}${typeParams}`;
855
+ }
856
+
857
+ // ── TypeScript: imports & exports ──────────────────────────────
858
+ case "TSImportEqualsDeclaration": {
859
+ const id = print(node.id);
860
+ const ref = print(node.moduleReference);
861
+ return `import ${id} = ${ref};`;
862
+ }
863
+
864
+ case "TSExternalModuleReference":
865
+ return `require(${print(node.expression)})`;
866
+
867
+ case "TSExportAssignment":
868
+ return `export = ${print(node.expression)};`;
869
+
870
+ case "TSImportType": {
871
+ const arg = print(node.parameter);
872
+ const qualifier = node.qualifier ? `.${print(node.qualifier)}` : "";
873
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
874
+ return `import(${arg})${qualifier}${typeParams}`;
875
+ }
876
+
877
+ // ── TypeScript: parameter & type modifiers ─────────────────────
878
+ case "TSParameterProperty": {
879
+ const accessibility = node.accessibility ? node.accessibility + " " : "";
880
+ const readonly = node.readonly ? "readonly " : "";
881
+ const override = node.override ? "override " : "";
882
+ return `${accessibility}${override}${readonly}${print(node.parameter)}`;
883
+ }
884
+
885
+ case "TSTypePredicate": {
886
+ const asserts = node.asserts ? "asserts " : "";
887
+ const name = print(node.parameterName);
888
+ const type = node.typeAnnotation
889
+ ? ` is ${print(node.typeAnnotation).replace(/^: /, "")}`
890
+ : "";
891
+ return `${asserts}${name}${type}`;
892
+ }
893
+
894
+ case "TSTypeQuery": {
895
+ const name = print(node.exprName);
896
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
897
+ return `typeof ${name}${typeParams}`;
898
+ }
899
+
900
+ case "TSEmptyBodyFunctionExpression": {
901
+ const typeParams = node.typeParameters ? print(node.typeParameters) : "";
902
+ const params = (node.params ?? []).map(print).join(", ");
903
+ const returnType = node.returnType ? print(node.returnType) : "";
904
+ return `${typeParams}(${params})${returnType}`;
905
+ }
906
+
907
+ // ── Glimmer nodes (Ember templates) ────────────────────────────
908
+ case "GlimmerTemplate": {
909
+ const children = (node.body ?? node.children ?? []).map(print).join("");
910
+ return `<template>${children}</template>`;
911
+ }
912
+
913
+ case "GlimmerElementNode": {
914
+ const tag = node.tag ?? "";
915
+ const attrs = (node.attributes ?? []).map(print).join(" ");
916
+ const modifiers = (node.modifiers ?? []).map(print).join(" ");
917
+ const children = (node.children ?? []).map(print).join("");
918
+ const blockParams = node.blockParams ?? [];
919
+ const asParams = blockParams.length ? ` as |${blockParams.join(" ")}|` : "";
920
+ const parts = [tag];
921
+ if (attrs) parts.push(attrs);
922
+ if (modifiers) parts.push(modifiers);
923
+ if (node.selfClosing) return `<${parts.join(" ")} />`;
924
+ return `<${parts.join(" ")}${asParams}>${children}</${tag}>`;
925
+ }
926
+
927
+ case "GlimmerElementNodePart":
928
+ return node.original ?? node.name ?? "";
929
+
930
+ case "GlimmerTextNode":
931
+ return node.chars ?? "";
932
+
933
+ case "GlimmerMustacheStatement": {
934
+ const path = print(node.path);
935
+ const params = (node.params ?? []).map(print).join(" ");
936
+ const hash = node.hash ? print(node.hash) : "";
937
+ const parts = [path];
938
+ if (params) parts.push(params);
939
+ if (hash) parts.push(hash);
940
+ return `{{${parts.join(" ")}}}`;
941
+ }
942
+
943
+ case "GlimmerBlockStatement": {
944
+ const path = print(node.path);
945
+ const params = (node.params ?? []).map(print).join(" ");
946
+ const hash = node.hash ? print(node.hash) : "";
947
+ const blockParams = node.program?.blockParams ?? [];
948
+ const asParams = blockParams.length ? ` as |${blockParams.join(" ")}|` : "";
949
+ const body = (node.body ?? node.program?.body ?? []).map(print).join("");
950
+ const inverse = node.inverse
951
+ ? `{{else}}${(node.inverse.body ?? []).map(print).join("")}`
952
+ : "";
953
+ const parts = [path];
954
+ if (params) parts.push(params);
955
+ if (hash) parts.push(hash);
956
+ return `{{#${parts.join(" ")}${asParams}}}${body}${inverse}{{/${print(node.path)}}}`;
957
+ }
958
+
959
+ case "GlimmerPathExpression":
960
+ return node.original ?? (node.parts ?? []).join(".");
961
+
962
+ case "GlimmerSubExpression": {
963
+ const path = print(node.path);
964
+ const params = (node.params ?? []).map(print).join(" ");
965
+ const hash = node.hash ? print(node.hash) : "";
966
+ const parts = [path];
967
+ if (params) parts.push(params);
968
+ if (hash) parts.push(hash);
969
+ return `(${parts.join(" ")})`;
970
+ }
971
+
972
+ case "GlimmerAttrNode": {
973
+ const name = node.name ?? "";
974
+ const value = node.value;
975
+ // A plain text value carries no quote style in the AST, so quote it
976
+ // (always valid) — printing it raw drops the quotes and corrupts any
977
+ // value with whitespace, e.g. `data-x="a b"` -> `data-x=a b`. An empty
978
+ // text value is a valueless attribute (`<input disabled>`).
979
+ if (value?.type === "GlimmerTextNode") {
980
+ const chars = value.chars ?? "";
981
+ if (chars === "") return name;
982
+ const quote = chars.includes('"') ? "'" : '"';
983
+ return `${name}=${quote}${chars}${quote}`;
984
+ }
985
+ // Mustache (`{{x}}`) and concat (`"a {{b}}"`) values print themselves.
986
+ return `${name}=${print(value)}`;
987
+ }
988
+
989
+ case "GlimmerConcatStatement": {
990
+ const parts = (node.parts ?? []).map(print).join("");
991
+ return `"${parts}"`;
992
+ }
993
+
994
+ case "GlimmerHash": {
995
+ const pairs = (node.pairs ?? []).map(print).join(" ");
996
+ return pairs;
997
+ }
998
+
999
+ case "GlimmerHashPair":
1000
+ return `${node.key}=${print(node.value)}`;
1001
+
1002
+ case "GlimmerStringLiteral":
1003
+ return `"${node.value ?? ""}"`;
1004
+
1005
+ case "GlimmerBooleanLiteral":
1006
+ return String(node.value);
1007
+
1008
+ case "GlimmerNumberLiteral":
1009
+ return String(node.value);
1010
+
1011
+ case "GlimmerNullLiteral":
1012
+ return "null";
1013
+
1014
+ case "GlimmerUndefinedLiteral":
1015
+ return "undefined";
1016
+
1017
+ case "GlimmerCommentStatement":
1018
+ return `<!--${node.value ?? ""}-->`;
1019
+
1020
+ case "GlimmerMustacheCommentStatement":
1021
+ return node.longForm ? `{{!-- ${node.value ?? ""} --}}` : `{{! ${node.value ?? ""} }}`;
1022
+
1023
+ case "GlimmerElementModifierStatement": {
1024
+ const path = print(node.path);
1025
+ const params = (node.params ?? []).map(print).join(" ");
1026
+ const hash = node.hash ? print(node.hash) : "";
1027
+ const parts = [path];
1028
+ if (params) parts.push(params);
1029
+ if (hash) parts.push(hash);
1030
+ return `{{${parts.join(" ")}}}`;
1031
+ }
1032
+
1033
+ case "GlimmerBlock":
1034
+ case "GlimmerProgram": {
1035
+ return (node.body ?? []).map(print).join("");
1036
+ }
1037
+
1038
+ // ── Program (root) ─────────────────────────────────────────────
1039
+ case "Program":
1040
+ return (node.body ?? []).map(print).join("\n");
1041
+
1042
+ default:
1043
+ throw new Error(`ember-estree print: unsupported node type '${node.type}'`);
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Prints an identifier with an optional TS type annotation.
1049
+ * @param {string} name
1050
+ * @param {object} node
1051
+ * @return {string}
1052
+ */
1053
+ /**
1054
+ * Indents every non-empty line of a block's inner content by one level.
1055
+ * Nesting compounds naturally: each enclosing block re-indents the
1056
+ * already-formatted child string.
1057
+ * @param {string} text
1058
+ * @return {string}
1059
+ */
1060
+ function indent(text) {
1061
+ return text
1062
+ .split("\n")
1063
+ .map((line) => (line ? ` ${line}` : line))
1064
+ .join("\n");
1065
+ }
1066
+
1067
+ /**
1068
+ * Wraps already-joined statement text in a brace block, indented one level.
1069
+ * Empty content collapses to `{}`.
1070
+ * @param {string} inner
1071
+ * @return {string}
1072
+ */
1073
+ function braceBlock(inner) {
1074
+ return inner ? `{\n${indent(inner)}\n}` : "{}";
1075
+ }
1076
+
1077
+ function printTypeAnnotated(name, node) {
1078
+ const optional = node.optional ? "?" : "";
1079
+ const typeAnnotation = node.typeAnnotation ? print(node.typeAnnotation) : "";
1080
+ return `${name}${optional}${typeAnnotation}`;
1081
+ }
1082
+
1083
+ /**
1084
+ * Prints a TSMappedType modifier (readonly or optional) which can be
1085
+ * `true`, `'+'`, `'-'`, or falsy.
1086
+ * @param {boolean|string|undefined} modifier
1087
+ * @param {string} token - e.g. 'readonly ' or '?'
1088
+ * @return {string}
1089
+ */
1090
+ function printMappedModifier(modifier, token) {
1091
+ if (modifier === true) return token;
1092
+ if (modifier === "+") return `+${token}`;
1093
+ if (modifier === "-") return `-${token}`;
1094
+ return "";
1095
+ }