octane 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/context-identity.cjs +29 -0
  2. package/dist/cjs/dom-stage.cjs +24 -18
  3. package/dist/cjs/error-codes.server.generated.cjs +5 -0
  4. package/dist/cjs/hydration-markers.cjs +12 -0
  5. package/dist/cjs/index.cjs +2 -0
  6. package/dist/cjs/runtime.cjs +253 -393
  7. package/dist/cjs/runtime.server.cjs +137 -35
  8. package/dist/cjs/server/index.cjs +2 -0
  9. package/dist/cjs/server/streamed-signals.cjs +41 -9
  10. package/dist/cjs/signals/client.cjs +1 -6
  11. package/dist/cjs/signals/engine.cjs +1 -12
  12. package/dist/cjs/version.cjs +1 -1
  13. package/dist/compiler/client-only-server.js +1 -28
  14. package/dist/compiler/compile-renderer-boundaries.js +1 -22
  15. package/dist/compiler/compile-universal.js +1 -22
  16. package/dist/compiler/compile.js +782 -359
  17. package/dist/compiler/dom-binding-fixed-props.js +11 -1
  18. package/dist/compiler/generated-origin.js +46 -0
  19. package/dist/compiler/hydrate-boundaries.js +88 -52
  20. package/dist/compiler/plain-hook-memo.js +5 -1
  21. package/dist/compiler/pure-factories.js +66 -0
  22. package/dist/compiler/register.js +4 -1
  23. package/dist/compiler/slot-hooks.js +113 -186
  24. package/dist/compiler/typescript-lowering.js +763 -0
  25. package/dist/compiler/void-roots.js +239 -0
  26. package/dist/compiler/volar.js +91 -38
  27. package/dist/context-identity.d.ts +14 -0
  28. package/dist/context-identity.js +28 -0
  29. package/dist/dom-stage.js +24 -18
  30. package/dist/error-codes.server.generated.d.ts +1 -0
  31. package/dist/error-codes.server.generated.js +5 -0
  32. package/dist/hydration/idle.js +1 -0
  33. package/dist/hydration/independent-island.js +53 -2
  34. package/dist/hydration/media.js +1 -0
  35. package/dist/hydration/types.d.ts +5 -0
  36. package/dist/hydration/visible.js +1 -0
  37. package/dist/hydration-markers.d.ts +8 -0
  38. package/dist/hydration-markers.js +8 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +2 -0
  41. package/dist/runtime.d.ts +25 -40
  42. package/dist/runtime.js +254 -398
  43. package/dist/runtime.server.d.ts +23 -2
  44. package/dist/runtime.server.js +142 -36
  45. package/dist/server/index.d.ts +1 -1
  46. package/dist/server/index.js +2 -0
  47. package/dist/server/streamed-signals.js +41 -9
  48. package/dist/signals/client.js +3 -8
  49. package/dist/signals/engine.d.ts +0 -2
  50. package/dist/signals/engine.js +1 -13
  51. package/dist/universal-core.js +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +1 -1
  54. package/dist/cjs/signals/diagnostic-local-hook.cjs +0 -37
  55. package/dist/compiler/local-void-roots.js +0 -154
  56. package/dist/signals/diagnostic-local-hook.d.ts +0 -8
  57. package/dist/signals/diagnostic-local-hook.js +0 -12
@@ -0,0 +1,763 @@
1
+ // Lowering for the TypeScript declarations that carry RUNTIME semantics:
2
+ // `enum`, value `namespace`, entity-name `import X = A.B` aliases, and class
3
+ // parameter properties (`constructor(private x)`).
4
+ //
5
+ // A `.tsrx` module is never TS-transformed after Octane (Vite type-strips only
6
+ // `.ts`/`.tsx`), so the compiler's single print must already be plain
7
+ // JavaScript. Type-only surface is simply dropped (see isTypeOnlyStatement in
8
+ // compile.js); these constructs instead expand to the same JavaScript tsc
9
+ // emits for an ES2022 module target:
10
+ //
11
+ // enum E { A, B = 'b' } var E;
12
+ // (function (E) {
13
+ // E[E["A"] = 0] = "A";
14
+ // E["B"] = "b";
15
+ // })(E || (E = {}));
16
+ //
17
+ // namespace N { var N;
18
+ // export const x = 1; (function (N) {
19
+ // export function f() {} N.x = 1;
20
+ // const y = x; function f() {}
21
+ // } N.f = f;
22
+ // const y = N.x;
23
+ // })(N || (N = {}));
24
+ //
25
+ // Declaration merging keeps one binding (`var` only on the first declaration in
26
+ // a statement list, none when a function/class already owns the name), and an
27
+ // exported enum/namespace inside a namespace binds through `E = N.E || (N.E = {})`.
28
+ // `const enum` is emitted as a regular enum (tsc's `preserveConstEnums` /
29
+ // isolatedModules behavior): the values are identical and references stay
30
+ // valid across modules. Enum member initializers are constant-folded like tsc,
31
+ // so string members get no reverse mapping and bare member references resolve
32
+ // to members.
33
+ //
34
+ // Constructs that have no ESM lowering (`export =`, `import x = require()`) are
35
+ // rejected with a diagnostic rather than printed as a parse error for the
36
+ // bundler to report.
37
+ //
38
+ // Everything here is copy-on-write: authored nodes are only ever shared or
39
+ // shallow-copied (tests deep-freeze adopted parser ASTs). Freshly built nodes
40
+ // carry their origin's loc so the print's source map stays complete.
41
+
42
+ import { builders as b } from '@tsrx/core';
43
+ import { createLexicalAnalysis, isIdentifierReference } from './compile-universal.js';
44
+
45
+ const NOT_CONSTANT = Symbol('not constant');
46
+
47
+ export const TS_ENUM_INITIALIZER_REQUIRED = 'OCTANE_TS_ENUM_INITIALIZER_REQUIRED';
48
+ export const TS_EXPORT_ASSIGNMENT_UNSUPPORTED = 'OCTANE_TS_EXPORT_ASSIGNMENT_UNSUPPORTED';
49
+ export const TS_IMPORT_REQUIRE_UNSUPPORTED = 'OCTANE_TS_IMPORT_REQUIRE_UNSUPPORTED';
50
+ export const TS_NAMESPACE_EXPORT_UNSUPPORTED = 'OCTANE_TS_NAMESPACE_EXPORT_UNSUPPORTED';
51
+ export const TS_PARAMETER_PROPERTY_UNSUPPORTED = 'OCTANE_TS_PARAMETER_PROPERTY_UNSUPPORTED';
52
+
53
+ function diagnostic(code, filename, node, message) {
54
+ const start = node?.loc?.start;
55
+ const at = start ? `${filename}:${start.line}:${start.column}` : filename;
56
+ const error = new Error(`${message} (${at})`);
57
+ error.code = code;
58
+ error.filename = filename;
59
+ error.loc = start ? Object.freeze({ line: start.line, column: start.column }) : null;
60
+ return error;
61
+ }
62
+
63
+ // Builder output stamped with its origin's position (see assertNodeLocs in
64
+ // compile.js). Only ever applied to freshly built nodes.
65
+ function at(origin, node) {
66
+ return b.set_location(node, origin);
67
+ }
68
+
69
+ const id = (origin, name) => b.id(name, origin);
70
+ const str = (origin, value) => b.literal(value, JSON.stringify(value), origin);
71
+ const member = (origin, object, name) => at(origin, b.member(object, id(origin, name)));
72
+ const index = (origin, object, property) => at(origin, b.member(object, property, true));
73
+ const assign = (origin, left, right) => at(origin, b.assignment('=', left, right));
74
+ const stmt = (origin, expression) => at(origin, b.stmt(expression));
75
+ const declare = (origin, kind, name, init = null, exported = false) => {
76
+ const declaration = at(
77
+ origin,
78
+ b.declaration(kind, [at(origin, b.declarator(id(origin, name), init))]),
79
+ );
80
+ return exported ? at(origin, b.export(declaration)) : declaration;
81
+ };
82
+
83
+ function literalFor(origin, value) {
84
+ if (typeof value === 'string') return str(origin, value);
85
+ if (Number.isNaN(value)) return id(origin, 'NaN');
86
+ if (value < 0 || Object.is(value, -0)) {
87
+ return at(origin, b.unary('-', literalFor(origin, -value)));
88
+ }
89
+ if (value === Infinity) return id(origin, 'Infinity');
90
+ return b.literal(value, String(value), origin);
91
+ }
92
+
93
+ // `function (Name) { ...body }` — the scope an enum/namespace body runs in.
94
+ function scopeFunction(origin, name, body) {
95
+ return b.function(null, [id(origin, name)], b.block(body, origin), false, undefined, origin);
96
+ }
97
+
98
+ // `(fn)(binding);`
99
+ function invoke(origin, fn, binding) {
100
+ return stmt(origin, at(origin, b.call(fn, binding)));
101
+ }
102
+
103
+ // `Name || (Name = {})`, or `Name = Parent.Name || (Parent.Name = {})` for a
104
+ // declaration exported from an enclosing namespace.
105
+ function bindingArgument(origin, name, parent) {
106
+ const target = () =>
107
+ parent === null ? id(origin, name) : member(origin, id(origin, parent), name);
108
+ const binding = at(
109
+ origin,
110
+ b.logical('||', target(), assign(origin, target(), b.object([], origin))),
111
+ );
112
+ return parent === null ? binding : assign(origin, id(origin, name), binding);
113
+ }
114
+
115
+ function unwrapExpression(node) {
116
+ while (
117
+ node != null &&
118
+ (node.type === 'ParenthesizedExpression' ||
119
+ node.type === 'TSAsExpression' ||
120
+ node.type === 'TSSatisfiesExpression' ||
121
+ node.type === 'TSNonNullExpression' ||
122
+ node.type === 'TSTypeAssertion')
123
+ ) {
124
+ node = node.expression;
125
+ }
126
+ return node;
127
+ }
128
+
129
+ function enumMemberName(memberNode) {
130
+ const key = memberNode.id;
131
+ if (key?.type === 'Identifier') return key.name;
132
+ if (key?.type === 'Literal') return String(key.value);
133
+ if (key?.type === 'TemplateLiteral' && key.expressions.length === 0) {
134
+ return key.quasis[0].value.cooked;
135
+ }
136
+ return null;
137
+ }
138
+
139
+ // tsc's constant-enum-expression evaluation. `own` holds this enum's members
140
+ // (all merged declarations seen so far); `known` resolves `Other.Member` for
141
+ // enums lowered earlier in the same or an enclosing statement list.
142
+ function evaluate(node, enumName, own, known) {
143
+ node = unwrapExpression(node);
144
+ if (node == null) return NOT_CONSTANT;
145
+ switch (node.type) {
146
+ case 'Literal':
147
+ return typeof node.value === 'number' || typeof node.value === 'string'
148
+ ? node.value
149
+ : NOT_CONSTANT;
150
+ case 'TemplateLiteral': {
151
+ let text = node.quasis[0].value.cooked;
152
+ for (let i = 0; i < node.expressions.length; i++) {
153
+ const value = evaluate(node.expressions[i], enumName, own, known);
154
+ if (value === NOT_CONSTANT) return NOT_CONSTANT;
155
+ text += String(value) + node.quasis[i + 1].value.cooked;
156
+ }
157
+ return text;
158
+ }
159
+ case 'Identifier':
160
+ return own.has(node.name) ? own.get(node.name) : NOT_CONSTANT;
161
+ case 'MemberExpression': {
162
+ const object = unwrapExpression(node.object);
163
+ if (object?.type !== 'Identifier') return NOT_CONSTANT;
164
+ const key = node.computed
165
+ ? unwrapExpression(node.property)?.type === 'Literal'
166
+ ? String(unwrapExpression(node.property).value)
167
+ : null
168
+ : node.property.name;
169
+ if (key == null) return NOT_CONSTANT;
170
+ const values = object.name === enumName ? own : known.get(object.name);
171
+ return values?.has(key) ? values.get(key) : NOT_CONSTANT;
172
+ }
173
+ case 'UnaryExpression': {
174
+ const value = evaluate(node.argument, enumName, own, known);
175
+ if (typeof value !== 'number') return NOT_CONSTANT;
176
+ if (node.operator === '+') return value;
177
+ if (node.operator === '-') return -value;
178
+ if (node.operator === '~') return ~value;
179
+ return NOT_CONSTANT;
180
+ }
181
+ case 'BinaryExpression': {
182
+ const left = evaluate(node.left, enumName, own, known);
183
+ if (left === NOT_CONSTANT) return NOT_CONSTANT;
184
+ const right = evaluate(node.right, enumName, own, known);
185
+ if (right === NOT_CONSTANT) return NOT_CONSTANT;
186
+ if (node.operator === '+') return left + right;
187
+ if (typeof left !== 'number' || typeof right !== 'number') return NOT_CONSTANT;
188
+ switch (node.operator) {
189
+ case '-':
190
+ return left - right;
191
+ case '*':
192
+ return left * right;
193
+ case '/':
194
+ return left / right;
195
+ case '%':
196
+ return left % right;
197
+ case '**':
198
+ return left ** right;
199
+ case '<<':
200
+ return left << right;
201
+ case '>>':
202
+ return left >> right;
203
+ case '>>>':
204
+ return left >>> right;
205
+ case '&':
206
+ return left & right;
207
+ case '|':
208
+ return left | right;
209
+ case '^':
210
+ return left ^ right;
211
+ }
212
+ return NOT_CONSTANT;
213
+ }
214
+ }
215
+ return NOT_CONSTANT;
216
+ }
217
+
218
+ // A non-constant initializer that is nevertheless always a string gets no
219
+ // reverse mapping (tsc's isSyntacticallyString).
220
+ function isSyntacticallyString(node) {
221
+ node = unwrapExpression(node);
222
+ if (node?.type === 'Literal') return typeof node.value === 'string';
223
+ if (node?.type === 'TemplateLiteral') return true;
224
+ if (node?.type === 'BinaryExpression' && node.operator === '+') {
225
+ return isSyntacticallyString(node.left) || isSyntacticallyString(node.right);
226
+ }
227
+ return false;
228
+ }
229
+
230
+ // Rewrite free references to `names` inside `root` as `qualifier.name`. Bindings
231
+ // declared anywhere inside `root` (including the IIFE parameter) shadow them.
232
+ function qualifyReferences(root, names, qualifier) {
233
+ if (names.size === 0) return root;
234
+ const lexical = createLexicalAnalysis(root);
235
+ const visit = (node, parent, key) => {
236
+ if (node === null || typeof node !== 'object') return node;
237
+ if (Array.isArray(node)) {
238
+ let out = null;
239
+ for (let i = 0; i < node.length; i++) {
240
+ const mapped = visit(node[i], parent, key);
241
+ if (out === null && mapped !== node[i]) out = node.slice(0, i);
242
+ if (out !== null) out.push(mapped);
243
+ }
244
+ return out ?? node;
245
+ }
246
+ if (node.type === 'Identifier') {
247
+ if (
248
+ names.has(node.name) &&
249
+ isIdentifierReference(node, parent, key, lexical) &&
250
+ !lexical.isBound(lexical.nodeScopes.get(node) ?? lexical.rootScope, node.name)
251
+ ) {
252
+ return member(node, id(node, qualifier), node.name);
253
+ }
254
+ return node;
255
+ }
256
+ let out = null;
257
+ for (const field of Object.keys(node)) {
258
+ if (field === 'loc' || field === 'start' || field === 'end' || field === 'metadata') continue;
259
+ // A non-computed key names a property, never a binding reference; a
260
+ // shorthand property is handled through its `value`.
261
+ if (field === 'key' && node.computed !== true) continue;
262
+ const child = node[field];
263
+ if (child === null || typeof child !== 'object') continue;
264
+ const mapped = visit(child, node, field);
265
+ if (mapped !== child) {
266
+ if (out === null) out = { ...node };
267
+ out[field] = mapped;
268
+ }
269
+ }
270
+ // `{ x }` → `{ x: N.x }` once the value is qualified.
271
+ if (out !== null && node.type === 'Property' && node.shorthand === true) out.shorthand = false;
272
+ return out ?? node;
273
+ };
274
+ return visit(root, null, null);
275
+ }
276
+
277
+ // The exported declaration of `export <declaration>`, else null. The
278
+ // JavaScript parser marks `export import X = …` with `isExport` on the alias
279
+ // itself rather than wrapping it.
280
+ function exportedDeclaration(statement) {
281
+ if (statement?.type === 'ExportNamedDeclaration') return statement.declaration ?? null;
282
+ if (statement?.type === 'TSImportEqualsDeclaration' && statement.isExport === true) {
283
+ return statement;
284
+ }
285
+ return null;
286
+ }
287
+
288
+ function isValueNamespace(node, isTypeOnlyStatement) {
289
+ return (
290
+ node?.type === 'TSModuleDeclaration' &&
291
+ node.kind === 'namespace' &&
292
+ node.declare !== true &&
293
+ !isTypeOnlyStatement(node)
294
+ );
295
+ }
296
+
297
+ // `namespace A.B.C {…}` → [A, B, C]. The native parser spells the dotted id
298
+ // as a TSQualifiedName; the JavaScript parser nests one TSModuleDeclaration
299
+ // per segment instead.
300
+ function namespacePath(node) {
301
+ const names = [];
302
+ let current = node.id;
303
+ while (current?.type === 'TSQualifiedName') {
304
+ names.unshift(current.right.name);
305
+ current = current.left;
306
+ }
307
+ if (current?.type === 'Identifier') names.unshift(current.name);
308
+ if (node.body?.type === 'TSModuleDeclaration') names.push(...namespacePath(node.body));
309
+ return names;
310
+ }
311
+
312
+ function namespaceBody(node) {
313
+ let body = node.body;
314
+ while (body?.type === 'TSModuleDeclaration') body = body.body;
315
+ return body?.type === 'TSModuleBlock' ? body.body : [];
316
+ }
317
+
318
+ function valueDeclarationNames(declaration, out) {
319
+ switch (declaration?.type) {
320
+ case 'FunctionDeclaration':
321
+ case 'TSDeclareFunction':
322
+ case 'ClassDeclaration':
323
+ case 'TSEnumDeclaration':
324
+ case 'TSImportEqualsDeclaration':
325
+ if (declaration.id?.type === 'Identifier') out.push(declaration.id.name);
326
+ return;
327
+ case 'TSModuleDeclaration':
328
+ if (declaration.kind === 'namespace') out.push(namespacePath(declaration)[0]);
329
+ return;
330
+ case 'VariableDeclaration':
331
+ for (const declarator of declaration.declarations ?? []) {
332
+ if (declarator.id?.type === 'Identifier') out.push(declarator.id.name);
333
+ }
334
+ }
335
+ }
336
+
337
+ // Map "A.B" → every value name any merged `A.B` declaration exports, so a
338
+ // reference in one block to another block's export is qualified. Keyed by the
339
+ // full dotted path because each dotted/merged block lowers independently.
340
+ function collectNamespaceExports(statements, prefix, exportsByPath, isTypeOnlyStatement) {
341
+ for (const statement of statements) {
342
+ const exported = exportedDeclaration(statement);
343
+ const declaration = exported ?? statement;
344
+ if (prefix !== null && exported !== null) {
345
+ // `export declare const x` still names `N.x` for sibling references.
346
+ const names = [];
347
+ if (declaration.type !== 'TSModuleDeclaration' || !isTypeOnlyStatement(declaration)) {
348
+ valueDeclarationNames(declaration, names);
349
+ }
350
+ let set = exportsByPath.get(prefix);
351
+ if (set === undefined) exportsByPath.set(prefix, (set = new Set()));
352
+ for (const name of names) set.add(name);
353
+ }
354
+ if (!isValueNamespace(declaration, isTypeOnlyStatement)) continue;
355
+ const path = namespacePath(declaration);
356
+ let current = prefix;
357
+ for (let i = 0; i < path.length; i++) {
358
+ if (current !== null && i > 0) {
359
+ let set = exportsByPath.get(current);
360
+ if (set === undefined) exportsByPath.set(current, (set = new Set()));
361
+ set.add(path[i]);
362
+ }
363
+ current = current === null ? path[i] : `${current}.${path[i]}`;
364
+ }
365
+ collectNamespaceExports(
366
+ namespaceBody(declaration),
367
+ current,
368
+ exportsByPath,
369
+ isTypeOnlyStatement,
370
+ );
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Whether a statement-list item needs lowering here. Cheap enough to run on
376
+ * every array item stripTsOnlyWrappers visits.
377
+ */
378
+ export function needsTypeScriptStatementLowering(statement) {
379
+ const declaration = exportedDeclaration(statement) ?? statement;
380
+ const type = declaration?.type;
381
+ return (
382
+ (type === 'TSEnumDeclaration' && declaration.declare !== true) ||
383
+ (type === 'TSModuleDeclaration' && declaration.kind === 'namespace') ||
384
+ type === 'TSImportEqualsDeclaration' ||
385
+ type === 'TSExportAssignment'
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Lower one statement list. `options`:
391
+ * topLevel — the Program body (bindings use `var`, like tsc)
392
+ * strip — compile.js stripTsOnlyWrappers, applied to every
393
+ * statement this pass passes through or builds; it
394
+ * carries the enums visible at that point into
395
+ * nested lists so `Outer.Member` still folds
396
+ * enums — enum values visible from enclosing lists
397
+ * isTypeOnlyStatement — compile.js's type-only statement predicate
398
+ * filename — for diagnostics
399
+ */
400
+ export function lowerTypeScriptStatements(statements, options) {
401
+ const exportsByPath = new Map();
402
+ collectNamespaceExports(statements, null, exportsByPath, options.isTypeOnlyStatement);
403
+ return lowerList(statements, options, {
404
+ topLevel: options.topLevel,
405
+ namespace: null,
406
+ path: null,
407
+ exportsByPath,
408
+ enums: options.enums ?? new Map(),
409
+ });
410
+ }
411
+
412
+ function lowerList(statements, options, scope) {
413
+ const { strip, isTypeOnlyStatement, filename } = options;
414
+ // Names already bound in this list: a merged enum/namespace reuses them
415
+ // instead of redeclaring (tsc: `function f(){}` + `namespace f {}`).
416
+ const bound = new Set();
417
+ for (const statement of statements) {
418
+ const declaration =
419
+ statement.type === 'ExportDefaultDeclaration'
420
+ ? statement.declaration
421
+ : (exportedDeclaration(statement) ?? statement);
422
+ if (
423
+ (declaration.type === 'FunctionDeclaration' || declaration.type === 'ClassDeclaration') &&
424
+ declaration.id != null
425
+ ) {
426
+ bound.add(declaration.id.name);
427
+ }
428
+ }
429
+ // Enum values visible here: this list's enums shadow the enclosing lists'.
430
+ const enums = new Map(scope.enums);
431
+ const kind = scope.topLevel ? 'var' : 'let';
432
+ const out = [];
433
+ const aliases = [];
434
+ for (const statement of statements) {
435
+ if (isTypeOnlyStatement(statement)) continue;
436
+ const exported = exportedDeclaration(statement);
437
+ const declaration = exported ?? statement;
438
+ const isExported = exported !== null;
439
+ if (scope.namespace !== null && statement.type === 'ExportNamedDeclaration' && !isExported) {
440
+ throw diagnostic(
441
+ TS_NAMESPACE_EXPORT_UNSUPPORTED,
442
+ filename,
443
+ statement,
444
+ 'Export lists are not allowed inside a namespace; export the declaration itself.',
445
+ );
446
+ }
447
+ if (scope.namespace !== null && statement.type === 'ExportDefaultDeclaration') {
448
+ throw diagnostic(
449
+ TS_NAMESPACE_EXPORT_UNSUPPORTED,
450
+ filename,
451
+ statement,
452
+ 'A namespace cannot have a default export.',
453
+ );
454
+ }
455
+ if (declaration.type === 'TSExportAssignment') {
456
+ throw diagnostic(
457
+ TS_EXPORT_ASSIGNMENT_UNSUPPORTED,
458
+ filename,
459
+ declaration,
460
+ '`export =` cannot be compiled to an ES module. Use `export default` instead.',
461
+ );
462
+ }
463
+ if (declaration.type === 'TSEnumDeclaration' && declaration.declare !== true) {
464
+ const name = declaration.id.name;
465
+ const first = !bound.has(name);
466
+ bound.add(name);
467
+ out.push(...lowerEnum(declaration, isExported, first, kind, scope, enums, options));
468
+ continue;
469
+ }
470
+ if (isValueNamespace(declaration, isTypeOnlyStatement)) {
471
+ const names = namespacePath(declaration);
472
+ const first = !bound.has(names[0]);
473
+ bound.add(names[0]);
474
+ out.push(
475
+ ...lowerNamespace(declaration, names, 0, isExported, first, kind, scope, enums, options),
476
+ );
477
+ continue;
478
+ }
479
+ if (declaration.type === 'TSImportEqualsDeclaration') {
480
+ if (declaration.importKind === 'type') continue;
481
+ if (declaration.moduleReference?.type === 'TSExternalModuleReference') {
482
+ throw diagnostic(
483
+ TS_IMPORT_REQUIRE_UNSUPPORTED,
484
+ filename,
485
+ declaration,
486
+ '`import x = require(…)` cannot be compiled to an ES module. Use an `import` declaration instead.',
487
+ );
488
+ }
489
+ const value = entityExpression(declaration.moduleReference);
490
+ const name = declaration.id.name;
491
+ if (isExported && scope.namespace !== null) {
492
+ out.push(
493
+ stmt(
494
+ declaration,
495
+ assign(declaration, member(declaration, id(declaration, scope.namespace), name), value),
496
+ ),
497
+ );
498
+ } else {
499
+ const alias = declare(declaration, 'var', name, value, isExported);
500
+ if (!isExported) aliases.push(alias);
501
+ out.push(alias);
502
+ }
503
+ continue;
504
+ }
505
+ if (scope.namespace !== null && isExported) {
506
+ out.push(...lowerNamespaceExport(statement, declaration, scope.namespace, options, enums));
507
+ continue;
508
+ }
509
+ out.push(strip(statement, enums));
510
+ }
511
+ // Like tsc, a local alias nothing reads as a value is elided: it may name a
512
+ // type-only namespace, which has no runtime binding at all. Types are
513
+ // already stripped from `out`, so any remaining same-named identifier keeps
514
+ // the alias (conservatively, even when shadowed).
515
+ if (aliases.length === 0) return out;
516
+ return out.filter(
517
+ (statement) =>
518
+ !aliases.includes(statement) ||
519
+ out.some(
520
+ (other) => other !== statement && mentionsName(other, statement.declarations[0].id.name),
521
+ ),
522
+ );
523
+ }
524
+
525
+ function mentionsName(node, name) {
526
+ if (node === null || typeof node !== 'object') return false;
527
+ if (Array.isArray(node)) return node.some((item) => mentionsName(item, name));
528
+ if (node.type === 'Identifier') return node.name === name;
529
+ for (const key of Object.keys(node)) {
530
+ if (key === 'loc' || key === 'start' || key === 'end' || key === 'metadata') continue;
531
+ if (mentionsName(node[key], name)) return true;
532
+ }
533
+ return false;
534
+ }
535
+
536
+ function entityExpression(node) {
537
+ if (node.type === 'TSQualifiedName') {
538
+ return member(node, entityExpression(node.left), node.right.name);
539
+ }
540
+ return id(node, node.name);
541
+ }
542
+
543
+ function lowerNamespaceExport(statement, declaration, namespace, options, enums) {
544
+ const { strip, filename } = options;
545
+ if (declaration.type === 'VariableDeclaration') {
546
+ const out = [];
547
+ for (const declarator of declaration.declarations) {
548
+ if (declarator.id.type !== 'Identifier') {
549
+ throw diagnostic(
550
+ TS_NAMESPACE_EXPORT_UNSUPPORTED,
551
+ filename,
552
+ declarator,
553
+ 'Destructured namespace exports are not supported. Export each name separately.',
554
+ );
555
+ }
556
+ // `export let x;` declares `N.x` without emitting anything.
557
+ if (declarator.init == null) continue;
558
+ out.push(
559
+ stmt(
560
+ declarator,
561
+ assign(
562
+ declarator,
563
+ member(declarator.id, id(declarator, namespace), declarator.id.name),
564
+ strip(declarator.init, enums),
565
+ ),
566
+ ),
567
+ );
568
+ }
569
+ return out;
570
+ }
571
+ if (
572
+ (declaration.type === 'FunctionDeclaration' || declaration.type === 'ClassDeclaration') &&
573
+ declaration.id != null
574
+ ) {
575
+ const name = declaration.id.name;
576
+ return [
577
+ strip(declaration, enums),
578
+ stmt(
579
+ declaration.id,
580
+ assign(
581
+ declaration.id,
582
+ member(declaration.id, id(declaration.id, namespace), name),
583
+ id(declaration.id, name),
584
+ ),
585
+ ),
586
+ ];
587
+ }
588
+ // Ambient exports (`export declare …`) were dropped as type-only already.
589
+ return [strip(statement, enums)];
590
+ }
591
+
592
+ // `names` is the dotted id (`namespace A.B.C`); each segment after the first
593
+ // is an exported namespace of the one before it, exactly as tsc desugars it.
594
+ function lowerNamespace(node, names, depth, isExported, first, kind, scope, enums, options) {
595
+ const name = names[depth];
596
+ const path = scope.path === null ? name : `${scope.path}.${name}`;
597
+ const inner = {
598
+ topLevel: false,
599
+ namespace: name,
600
+ path,
601
+ exportsByPath: scope.exportsByPath,
602
+ enums,
603
+ };
604
+ const body =
605
+ depth === names.length - 1
606
+ ? lowerList(namespaceBody(node), options, inner)
607
+ : lowerNamespace(node, names, depth + 1, true, true, 'let', inner, enums, options);
608
+ const origin = depth === 0 ? node : node.id;
609
+ const parent = isExported ? scope.namespace : null;
610
+ const fn = qualifyReferences(
611
+ scopeFunction(origin, name, body),
612
+ scope.exportsByPath.get(path) ?? new Set(),
613
+ name,
614
+ );
615
+ const call = invoke(origin, fn, bindingArgument(origin, name, parent));
616
+ return withBinding(origin, name, isExported, first, kind, scope, call);
617
+ }
618
+
619
+ function withBinding(node, name, isExported, first, kind, scope, statement) {
620
+ if (!first) return [statement];
621
+ // A top-level export binds through `export var`; an export from inside a
622
+ // namespace binds through the parent object instead (bindingArgument).
623
+ return [declare(node, kind, name, null, isExported && scope.namespace === null), statement];
624
+ }
625
+
626
+ function lowerEnum(node, isExported, first, kind, scope, enums, options) {
627
+ const { strip, filename } = options;
628
+ const name = node.id.name;
629
+ const members = node.body?.members ?? node.members ?? [];
630
+ const own = new Map(enums.get(name));
631
+ const body = [];
632
+ let previous;
633
+ for (let i = 0; i < members.length; i++) {
634
+ const memberNode = members[i];
635
+ const key = enumMemberName(memberNode);
636
+ let valueNode;
637
+ let value;
638
+ let stringValued;
639
+ if (memberNode.initializer != null) {
640
+ value = evaluate(memberNode.initializer, name, own, enums);
641
+ if (value === NOT_CONSTANT) {
642
+ valueNode = strip(memberNode.initializer, enums);
643
+ stringValued = isSyntacticallyString(memberNode.initializer);
644
+ }
645
+ } else if (i === 0) {
646
+ value = 0;
647
+ } else if (typeof previous === 'number') {
648
+ value = previous + 1;
649
+ } else {
650
+ throw diagnostic(
651
+ TS_ENUM_INITIALIZER_REQUIRED,
652
+ filename,
653
+ memberNode,
654
+ `Enum member ${JSON.stringify(key)} must have an initializer because the member before it is not a constant number.`,
655
+ );
656
+ }
657
+ if (value !== NOT_CONSTANT) {
658
+ valueNode = literalFor(memberNode, value);
659
+ stringValued = typeof value === 'string';
660
+ }
661
+ own.set(key, value);
662
+ previous = value;
663
+ const slot = assign(
664
+ memberNode,
665
+ index(memberNode, id(memberNode, name), str(memberNode.id, key)),
666
+ valueNode,
667
+ );
668
+ body.push(
669
+ stmt(
670
+ memberNode,
671
+ stringValued
672
+ ? slot
673
+ : assign(
674
+ memberNode,
675
+ index(memberNode, id(memberNode, name), slot),
676
+ str(memberNode.id, key),
677
+ ),
678
+ ),
679
+ );
680
+ }
681
+ enums.set(name, own);
682
+ const parent = isExported ? scope.namespace : null;
683
+ // A bare member name in a non-constant initializer means that member.
684
+ const fn = qualifyReferences(scopeFunction(node, name, body), new Set(own.keys()), name);
685
+ const call = invoke(node, fn, bindingArgument(node, name, parent));
686
+ return withBinding(node, name, isExported, first, kind, scope, call);
687
+ }
688
+
689
+ /**
690
+ * `constructor(private x, readonly y = 1)` → plain parameters plus
691
+ * `this.x = x; this.y = y;` at the top of the constructor body, after the
692
+ * `super(…)` call in a derived class. Like tsc for an ES2022 target
693
+ * (`useDefineForClassFields`), each property is also declared as a leading
694
+ * class field, so own-key order and define semantics match. Returns the class
695
+ * node unchanged when it has no parameter properties.
696
+ */
697
+ export function lowerParameterProperties(node, filename) {
698
+ const elements = node.body?.body;
699
+ if (!Array.isArray(elements)) return node;
700
+ const ctorIndex = elements.findIndex(
701
+ (element) =>
702
+ element.type === 'MethodDefinition' &&
703
+ element.kind === 'constructor' &&
704
+ element.value?.body != null &&
705
+ element.value.params?.some((param) => param.type === 'TSParameterProperty'),
706
+ );
707
+ if (ctorIndex === -1) return node;
708
+ const ctor = elements[ctorIndex];
709
+ const assignments = [];
710
+ const fields = [];
711
+ const params = ctor.value.params.map((param) => {
712
+ if (param.type !== 'TSParameterProperty') return param;
713
+ const binding =
714
+ param.parameter.type === 'AssignmentPattern' ? param.parameter.left : param.parameter;
715
+ if (binding?.type !== 'Identifier') {
716
+ throw diagnostic(
717
+ TS_PARAMETER_PROPERTY_UNSUPPORTED,
718
+ filename,
719
+ param,
720
+ 'A parameter property must be a plain identifier.',
721
+ );
722
+ }
723
+ fields.push(at(param, b.prop_def(id(binding, binding.name), null)));
724
+ assignments.push(
725
+ stmt(
726
+ param,
727
+ assign(
728
+ param,
729
+ member(binding, at(param, { ...b.this }), binding.name),
730
+ id(binding, binding.name),
731
+ ),
732
+ ),
733
+ );
734
+ return param.parameter;
735
+ });
736
+ const statements = ctor.value.body.body;
737
+ let insertAt = 0;
738
+ while (insertAt < statements.length && statements[insertAt].directive != null) insertAt++;
739
+ if (node.superClass != null) {
740
+ const superIndex = statements.findIndex(
741
+ (statement) =>
742
+ statement.type === 'ExpressionStatement' &&
743
+ statement.expression?.type === 'CallExpression' &&
744
+ statement.expression.callee?.type === 'Super',
745
+ );
746
+ if (superIndex === -1) {
747
+ throw diagnostic(
748
+ TS_PARAMETER_PROPERTY_UNSUPPORTED,
749
+ filename,
750
+ ctor,
751
+ 'A derived class with parameter properties must call `super(…)` as a top-level constructor statement.',
752
+ );
753
+ }
754
+ insertAt = superIndex + 1;
755
+ }
756
+ const body = [...statements.slice(0, insertAt), ...assignments, ...statements.slice(insertAt)];
757
+ const nextElements = [...fields, ...elements];
758
+ nextElements[fields.length + ctorIndex] = {
759
+ ...ctor,
760
+ value: { ...ctor.value, params, body: { ...ctor.value.body, body } },
761
+ };
762
+ return { ...node, body: { ...node.body, body: nextElements } };
763
+ }