jz 0.4.0 → 0.5.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/jzify.js CHANGED
@@ -27,11 +27,14 @@ export default function jzify(ast) {
27
27
  argsIdx = 0
28
28
  doIdx = 0
29
29
  classIdx = 0
30
+ objThisIdx = 0
31
+ staticClassIdx = 0
32
+ classBaseIdx = 0
30
33
  // Hoist module-level vars: any `var x` inside nested blocks bubbles up.
31
34
  const names = new Set()
32
35
  ast = hoistVars(ast, names)
33
36
  if (names.size) ast = prependDecls(ast, names)
34
- return foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast)))
37
+ return foldStaticBundlerHelpers(foldStaticExportHelpers(canonicalizeObjectIdioms(transformScope(ast))))
35
38
  }
36
39
 
37
40
  /**
@@ -71,16 +74,24 @@ function hoistVars(node, names) {
71
74
  }
72
75
  return [op, lhs, hoistVars(node[2], names)]
73
76
  }
77
+ // Labeled statement: recurse into the body so its `var`s hoist to the
78
+ // enclosing function, not stop at the label.
79
+ if (op === ':' && typeof node[1] === 'string') {
80
+ return [':', node[1], hoistVars(node[2], names)]
81
+ }
74
82
  if (op === '=' && Array.isArray(node[1]) && node[1][0] === 'var' && typeof node[1][1] === 'string' && node[1].length === 2) {
75
83
  names.add(node[1][1])
76
84
  return ['=', node[1][1], hoistVars(node[2], names)]
77
85
  }
86
+ if (op === '=' && isDestructurePat(node[1])) {
87
+ return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
88
+ }
78
89
  // For-head `;` is positional (init; cond; update), not a statement sequence.
79
90
  // Recurse into each slot but never filter nulls — empty slots are valid.
80
91
  if (op === 'for') {
81
92
  const head = node[1]
82
93
  let h2
83
- const normalizedHead = normalizeForDeclHead(head, names)
94
+ const normalizedHead = normalizeForDeclHead(head, names) || normalizeForCommaHead(head, names)
84
95
  if (normalizedHead) {
85
96
  h2 = normalizedHead
86
97
  } else if (Array.isArray(head) && head[0] === 'var' && Array.isArray(head[1]) &&
@@ -109,25 +120,84 @@ function hoistVars(node, names) {
109
120
  if (decls.length === 1) return decls[0]
110
121
  return [',', ...decls]
111
122
  }
123
+ if (op === 'let' || op === 'const') {
124
+ const decls = [op]
125
+ for (let i = 1; i < node.length; i++) {
126
+ const d = node[i]
127
+ if (Array.isArray(d) && d[0] === '=' && isDestructurePat(d[1])) {
128
+ decls.push(['=', hoistPattern(d[1], names), hoistVars(d[2], names)])
129
+ } else {
130
+ decls.push(hoistVars(d, names))
131
+ }
132
+ }
133
+ return decls
134
+ }
112
135
  // Filter null returns from `;` sequences (bare-var no-ops). `{}` is left
113
136
  // to recurse normally — it may be either a block or an object literal,
114
137
  // and we don't want to clobber `['{}', null]` (empty object literal).
115
138
  if (op === ';') {
116
139
  const out = [op]
117
140
  for (let i = 1; i < node.length; i++) {
118
- const c = hoistVars(node[i], names)
141
+ const child = node[i]
142
+ // A direct scope-child `var f = <arrow>` keeps its decl shape (→ a single
143
+ // `let f = arrow`) instead of splitting into a hoisted `let f` + later
144
+ // assignment. `var` and `let` are equivalent at a direct statement
145
+ // position, and the single-decl form is what top-level-function
146
+ // recognition keys on — bundlers (esbuild &c.) emit `var` for every
147
+ // module binding, so without this every bundled function would degrade
148
+ // to a closure value (and hit MAX_CLOSURE_ARITY). Redeclarations are
149
+ // collapsed downstream by the binding-dedup pass.
150
+ if (Array.isArray(child) && child[0] === 'var' && child.length === 2 &&
151
+ Array.isArray(child[1]) && child[1][0] === '=' && typeof child[1][1] === 'string' &&
152
+ Array.isArray(child[1][2]) && child[1][2][0] === '=>') {
153
+ out.push(['let', ['=', child[1][1], hoistVars(child[1][2], names)]])
154
+ continue
155
+ }
156
+ const c = hoistVars(child, names)
119
157
  if (c != null) out.push(c)
120
158
  }
121
159
  if (out.length === 1) return null
122
160
  if (out.length === 2) return out[1]
123
161
  return out
124
162
  }
163
+ // Block body: recursing the inner `;` may collapse it to a bare expression
164
+ // (e.g. `{ v + 1; }` → `['{}', ['+','v',1]]`), which prepare.js would then
165
+ // mistake for an object literal. Re-wrap as a 1-stmt `;` if the inner was
166
+ // block-shaped going in but collapsed to a non-statement-op expression.
167
+ if (op === '{}' && node.length === 2) {
168
+ const inner = node[1]
169
+ const wasBlock = inner != null && Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
170
+ const t = hoistVars(inner, names)
171
+ if (!wasBlock || t == null) return ['{}', t]
172
+ const stayed = Array.isArray(t) && JZ_BLOCK_OPS.has(t[0])
173
+ return ['{}', stayed ? t : [';', t]]
174
+ }
125
175
  const out = new Array(node.length)
126
176
  out[0] = op
127
177
  for (let i = 1; i < node.length; i++) out[i] = hoistVars(node[i], names)
128
178
  return out
129
179
  }
130
180
 
181
+ function hoistPattern(node, names) {
182
+ if (node == null || !Array.isArray(node)) return node
183
+ const op = node[0]
184
+ if (op === '=') return ['=', hoistPattern(node[1], names), hoistVars(node[2], names)]
185
+ if (op === ':') return [':', hoistVars(node[1], names), hoistPattern(node[2], names)]
186
+ if (op === '...') return ['...', hoistPattern(node[1], names)]
187
+ if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(n => hoistPattern(n, names))]
188
+ return hoistVars(node, names)
189
+ }
190
+
191
+ function transformPattern(node) {
192
+ if (node == null || !Array.isArray(node)) return node
193
+ const op = node[0]
194
+ if (op === '=') return ['=', transformPattern(node[1]), transform(node[2])]
195
+ if (op === ':') return [':', transform(node[1]), transformPattern(node[2])]
196
+ if (op === '...') return ['...', transformPattern(node[1])]
197
+ if (op === '[]' || op === '{}' || op === ',') return [op, ...node.slice(1).map(transformPattern)]
198
+ return transform(node)
199
+ }
200
+
131
201
  function prependDecls(body, names) {
132
202
  const decl = ['let', ...names]
133
203
  if (Array.isArray(body) && body[0] === ';') return [';', decl, ...body.slice(1)]
@@ -141,18 +211,39 @@ function prependDecls(body, names) {
141
211
  }
142
212
 
143
213
  function normalizeForDeclHead(head, names) {
144
- if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const') || head.length !== 2) return null
214
+ if (!Array.isArray(head) || (head[0] !== 'var' && head[0] !== 'let' && head[0] !== 'const')) return null
145
215
  const kind = head[0]
146
- const expr = head[1]
147
- if (!Array.isArray(expr)) return null
148
- if (expr.length >= 3 && Array.isArray(expr[1]) &&
149
- (expr[1][0] === 'in' || expr[1][0] === 'of') && typeof expr[1][1] === 'string') {
150
- const iter = expr[1]
151
- return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([expr[0], iter[2], ...expr.slice(2)], names)]
216
+ if (head.length === 2) {
217
+ const expr = head[1]
218
+ if (!Array.isArray(expr)) return null
219
+ if (expr.length >= 3 && Array.isArray(expr[1]) &&
220
+ (expr[1][0] === 'in' || expr[1][0] === 'of') && typeof expr[1][1] === 'string') {
221
+ const iter = expr[1]
222
+ return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([expr[0], iter[2], ...expr.slice(2)], names)]
223
+ }
224
+ return null
225
+ }
226
+ // Comma Expression in a for-in/of head: subscript parses with no for-head
227
+ // context, so `for (let x in A, B)` becomes a multi-declarator `let` whose
228
+ // tail declarators are really the rest of a comma Expression. Fold them back
229
+ // into the iterated source so the comma operator evaluates them in order.
230
+ if (head.length > 2 && Array.isArray(head[1]) &&
231
+ (head[1][0] === 'in' || head[1][0] === 'of') && typeof head[1][1] === 'string') {
232
+ const iter = head[1]
233
+ return [iter[0], normalizeForDecl(kind, iter[1], names), hoistVars([',', iter[2], ...head.slice(2)], names)]
152
234
  }
153
235
  return null
154
236
  }
155
237
 
238
+ // Bare-LHS counterpart of the above: `for (x in A, B)` (no declaration) parses
239
+ // as a comma expression whose first operand is the for-in/of head.
240
+ function normalizeForCommaHead(head, names) {
241
+ if (!Array.isArray(head) || head[0] !== ',' || head.length < 3) return null
242
+ const iter = head[1]
243
+ if (!Array.isArray(iter) || (iter[0] !== 'in' && iter[0] !== 'of') || typeof iter[1] !== 'string') return null
244
+ return [iter[0], iter[1], hoistVars([',', iter[2], ...head.slice(2)], names)]
245
+ }
246
+
156
247
  function normalizeForDecl(kind, name, names) {
157
248
  if (kind === 'var') {
158
249
  names.add(name)
@@ -163,7 +254,7 @@ function normalizeForDecl(kind, name, names) {
163
254
 
164
255
  /** Convert a named function declaration to a hoisted const arrow */
165
256
  function hoistFnDecl(name, params, body) {
166
- const [p2, b2] = lowerArguments(params, body)
257
+ const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
167
258
  const decl = ['const', ['=', name, ['=>', p2, wrapArrowBody(b2)]]]
168
259
  decl._hoisted = true
169
260
  return decl
@@ -185,25 +276,6 @@ function transformScope(node) {
185
276
  const hoisted = [], rest = []
186
277
  for (let i = 0; i < args.length; i++) {
187
278
  const stmt = args[i]
188
- // Workaround for subscript parser ASI bug: multiline named IIFE
189
- // `(function name(){...})();` is parsed as two statements when there are
190
- // newlines inside the function body. Reconstruct the single-statement IIFE
191
- // so the () handler can desugar it correctly.
192
- if (Array.isArray(stmt) && stmt[0] === '()' &&
193
- Array.isArray(stmt[1]) && stmt[1][0] === 'function' && stmt[1][1] &&
194
- i + 1 < args.length && Array.isArray(args[i + 1]) && args[i + 1][0] === '()') {
195
- const merged = ['()', ['()', stmt[1]], args[i + 1][1] ?? null]
196
- const t = transform(merged)
197
- if (t != null) {
198
- if (Array.isArray(t) && t[0] === ';') {
199
- for (const s of t.slice(1)) { if (s != null) rest.push(s) }
200
- } else {
201
- rest.push(t)
202
- }
203
- }
204
- i++
205
- continue
206
- }
207
279
  // Statement-form named function declaration: hoist directly (skip expression handler)
208
280
  if (Array.isArray(stmt) && stmt[0] === 'function' && stmt[1]) {
209
281
  hoisted.push(hoistFnDecl(stmt[1], stmt[2], stmt[3]))
@@ -249,24 +321,48 @@ function transformScope(node) {
249
321
  * wins; a later redeclaration keeps only its initializer, as a plain assignment.
250
322
  */
251
323
  function dedupeRedecls(stmts) {
252
- const nameOf = s => Array.isArray(s) && (s[0] === 'let' || s[0] === 'const' || s[0] === 'var')
253
- ? (typeof s[1] === 'string' ? s[1]
254
- : Array.isArray(s[1]) && s[1][0] === '=' && typeof s[1][1] === 'string' ? s[1][1] : null)
255
- : null
324
+ // Per-declarator name bare `x` or `['=', x, init]`; null for patterns.
325
+ const declName = d => typeof d === 'string' ? d
326
+ : Array.isArray(d) && d[0] === '=' && typeof d[1] === 'string' ? d[1] : null
256
327
  const seen = new Set(), out = []
257
328
  for (const s of stmts) {
258
- const n = nameOf(s)
259
- if (n == null) { out.push(s); continue }
260
- if (seen.has(n)) { if (Array.isArray(s[1]) && s[1][0] === '=') out.push(['=', s[1][1], s[1][2]]); continue }
261
- seen.add(n); out.push(s)
329
+ if (!Array.isArray(s) || (s[0] !== 'let' && s[0] !== 'const' && s[0] !== 'var')) { out.push(s); continue }
330
+ // Walk every declarator: a multi-name bare `let` (the var-hoist
331
+ // `prependDecls` output) can carry a name already bound by a hoisted
332
+ // function `const`. Keep only fresh declarators; a redeclaration with an
333
+ // initializer survives as a plain assignment.
334
+ const keep = [s[0]], reassign = []
335
+ for (let i = 1; i < s.length; i++) {
336
+ const d = s[i], n = declName(d)
337
+ if (n == null) { keep.push(d); continue }
338
+ if (seen.has(n)) { if (Array.isArray(d) && d[0] === '=') reassign.push(['=', d[1], d[2]]) }
339
+ else { seen.add(n); keep.push(d) }
340
+ }
341
+ if (keep.length > 1) out.push(keep)
342
+ for (const r of reassign) out.push(r)
262
343
  }
263
344
  return out
264
345
  }
265
346
 
266
- /** Wrap function body for arrow conversion */
347
+ /** Wrap function body for arrow conversion.
348
+ * Produces the canonical block form `['{}', [';', ...stmts]]`: a `{}` whose
349
+ * sole child is a `;`-list. A bare single statement (`['{}', stmt]`, from the
350
+ * parser eliding the `;` wrapper) would otherwise be mistaken for an object
351
+ * literal, so it is `;`-wrapped here too — `function`→arrow conversions bypass
352
+ * the `=>` transform handler and must normalize their own bodies. */
267
353
  function wrapArrowBody(body) {
268
354
  const t = transformScope(body)
269
- return Array.isArray(t) && (t[0] === '{}' || t[0] === ';') ? (t[0] === '{}' ? t : ['{}', t]) : ['{}', t]
355
+ if (!Array.isArray(t)) return ['{}', [';', t]]
356
+ if (t[0] === ';') return ['{}', t]
357
+ if (t[0] !== '{}') return ['{}', [';', t]]
358
+ if (t.length === 2 && !(Array.isArray(t[1]) && t[1][0] === ';')) return ['{}', [';', t[1]]]
359
+ return t
360
+ }
361
+
362
+ function functionBodyBlock(body) {
363
+ if (Array.isArray(body) && body[0] === '{}') return body
364
+ if (Array.isArray(body) && body[0] === ';') return ['{}', body]
365
+ return ['{}', [';', body]]
270
366
  }
271
367
 
272
368
  /** Prototype identity check: X.prototype.Y */
@@ -276,6 +372,46 @@ const TYPED_ARRAYS = new Set(['Float64Array','Float32Array','Int32Array','Uint32
276
372
  'Int16Array','Uint16Array','Int8Array','Uint8Array',
277
373
  'ArrayBuffer','BigInt64Array','BigUint64Array','DataView'])
278
374
 
375
+ // Block-shape ops used to detect "this `{}` is a block body, not an object literal".
376
+ // Mirrors analyze.STMT_OPS — kept inline so jzify stays self-contained.
377
+ const JZ_BLOCK_OPS = new Set([';', 'let', 'const', 'var', 'return', 'if', 'for', 'for-in', 'for-of',
378
+ 'while', 'do', 'break', 'continue', 'switch', 'throw', 'try', 'catch', 'finally',
379
+ '=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '>>=', '<<=', '>>>=', '||=', '&&=', '??=',
380
+ '++', '--', '()', 'function', 'class', 'import', 'export', 'label'])
381
+ const LABEL_BODY_OPS = new Set([';', 'if', 'for', 'for-in', 'for-of', 'while', 'do', 'switch', 'try', 'throw'])
382
+
383
+ /** Statically discriminate `x instanceof Ctor` when the LHS's syntactic shape
384
+ * already pins down its runtime type. Returns true/false or null (unknown).
385
+ * Matches the lhs forms a user might plausibly write: literal arrays, object
386
+ * literals, string/number literals, and `new C(...)` whose ctor name is known.
387
+ * Stays conservative — anything else returns null and falls back to the runtime
388
+ * predicate so behavior is never silently changed. */
389
+ function staticInstanceofFold(val, ctor) {
390
+ if (typeof ctor !== 'string' || !Array.isArray(val)) return null
391
+ // Unwrap grouping parens: `(expr)` parses as `['()', expr]`
392
+ if (val[0] === '()' && val.length === 2) return staticInstanceofFold(val[1], ctor)
393
+ // Array literal: `[1,2,3]` → `['[]', [',', …]]` (or `['[]']` for empty array)
394
+ if (val[0] === '[]' && val.length <= 2) return ctor === 'Array' || ctor === 'Object'
395
+ // Object literal: `{a:1}` → `['{}', [':', …], …]`
396
+ if (val[0] === '{}') return ctor === 'Object'
397
+ // Regex literal: `/x/` → `['//', …]`
398
+ if (val[0] === '//') return ctor === 'RegExp' || ctor === 'Object'
399
+ // `new C(...)` — `['new', ['()', 'C', args]]` or `['new', 'C']`
400
+ if (val[0] === 'new') {
401
+ const inner = val[1]
402
+ const cname = typeof inner === 'string' ? inner
403
+ : (Array.isArray(inner) && inner[0] === '()' && typeof inner[1] === 'string') ? inner[1]
404
+ : null
405
+ if (cname) return cname === ctor || (cname !== 'Object' && ctor === 'Object')
406
+ }
407
+ // Bare primitive: `[null, v]` where v is a primitive — never an instance per JS spec.
408
+ if (val[0] == null && val.length === 2) {
409
+ const v = val[1]
410
+ if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v == null) return false
411
+ }
412
+ return null
413
+ }
414
+
279
415
  // `arguments` lowering: regular `function` has implicit `arguments`; arrow doesn't.
280
416
  // jzify converts function → arrow, so any `arguments` use must be rewritten to a rest param.
281
417
  // Arrow functions inherit `arguments` from enclosing function — don't stop at '=>'.
@@ -373,6 +509,10 @@ function prependParamDecls(decl, body) {
373
509
  }
374
510
 
375
511
  const arrowParams = params => Array.isArray(params) && params[0] === '()' ? params : ['()', params]
512
+ // A method body from jessie is a bare statement / `;`-sequence — wrap it in a
513
+ // `{}` block so the `=>` handler treats it as a function body, not an expression
514
+ // (an unwrapped `;`-seq arrow body produces malformed IR).
515
+ const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
376
516
 
377
517
  // === class lowering ===
378
518
  //
@@ -393,10 +533,19 @@ const arrowParams = params => Array.isArray(params) && params[0] === '()' ? para
393
533
  // return selfN
394
534
  // }
395
535
  //
396
- // Out of scope for now (rejected with a clear message): `extends`/`super`,
397
- // `static` members, getters/setters, computed/private-via-`#` member names are
398
- // kept as the literal key string `#name` (jz allows it).
536
+ // Simple inheritance is lowered too: `class D extends B` builds the instance
537
+ // from `B`'s factory — forwarding `super(...)` args, or the derived ctor params
538
+ // when the derived constructor is implicit then applies D's own fields and
539
+ // methods over it.
540
+ //
541
+ // Out of scope (rejected with a clear message): full `super.foo` property
542
+ // semantics, getters/setters, non-constant computed member names. Private
543
+ // `#name` members are kept as the literal key string `#name` (jz allows it).
399
544
  let classIdx = 0
545
+ let objThisIdx = 0
546
+ let staticClassIdx = 0
547
+ let classBaseIdx = 0
548
+ const DEFAULT_DERIVED_CTOR_ARITY = 8
400
549
 
401
550
  const classBodyItems = (body) =>
402
551
  body == null ? [] : Array.isArray(body) && body[0] === ';' ? body.slice(1) : [body]
@@ -413,39 +562,200 @@ function renameThis(node, to) {
413
562
  return node.map(n => renameThis(n, to))
414
563
  }
415
564
 
565
+ function usesThis(node) {
566
+ if (node === 'this') return true
567
+ if (!Array.isArray(node)) return false
568
+ if (node[0] === 'function' || node[0] === 'class') return false
569
+ if (node[0] === '.' || node[0] === '?.') return usesThis(node[1])
570
+ if (node[0] === ':') return usesThis(node[2])
571
+ return node.some(usesThis)
572
+ }
573
+
574
+ function hasSuperProp(node) {
575
+ if (!Array.isArray(node)) return false
576
+ if ((node[0] === '.' || node[0] === '?.') && node[1] === 'super') return true
577
+ if (node[0] === '[]' && node[1] === 'super') return true
578
+ return node.some(hasSuperProp)
579
+ }
580
+
581
+ function isSuperCall(node) {
582
+ return Array.isArray(node) && node[0] === '()' && node[1] === 'super'
583
+ }
584
+
585
+ function literalStringKey(node) {
586
+ return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
587
+ }
588
+
589
+ function constStringKey(node) {
590
+ if (typeof node === 'string') return node
591
+ const lit = literalStringKey(node)
592
+ if (lit != null) return lit
593
+ if (Array.isArray(node) && node[0] === '[]') return literalStringKey(node[1])
594
+ return null
595
+ }
596
+
597
+ function superMethodName(callee) {
598
+ if (!Array.isArray(callee)) return null
599
+ if ((callee[0] === '.' || callee[0] === '?.') && callee[1] === 'super') return callee[2]
600
+ if (callee[0] === '[]' && callee[1] === 'super') return literalStringKey(callee[2])
601
+ return null
602
+ }
603
+
604
+ function collectSuperMethodCalls(node, out = new Set()) {
605
+ if (!Array.isArray(node)) return out
606
+ if (node[0] === 'function' || node[0] === 'class') return out
607
+ if (node[0] === '()') {
608
+ const name = superMethodName(node[1])
609
+ if (name) out.add(name)
610
+ }
611
+ for (const n of node) collectSuperMethodCalls(n, out)
612
+ return out
613
+ }
614
+
615
+ function rewriteSuperMethodCalls(node, baseMethodVars) {
616
+ if (!Array.isArray(node)) return node
617
+ if (node[0] === 'function' || node[0] === 'class') return node
618
+ if (node[0] === '()') {
619
+ const name = superMethodName(node[1])
620
+ if (name) {
621
+ const fn = baseMethodVars.get(name)
622
+ if (!fn) jzifyError(`super.${name} is not available on the base class`)
623
+ return ['()', fn, ...node.slice(2).map(n => rewriteSuperMethodCalls(n, baseMethodVars))]
624
+ }
625
+ }
626
+ return node.map(n => rewriteSuperMethodCalls(n, baseMethodVars))
627
+ }
628
+
629
+ function splitCtorSuper(body) {
630
+ if (body == null) return { args: null, body }
631
+ if (isSuperCall(body)) return { args: body.slice(2), body: null }
632
+ if (Array.isArray(body) && body[0] === '{}') {
633
+ const inner = splitCtorSuper(body[1])
634
+ return { args: inner.args, body: ['{}', inner.body] }
635
+ }
636
+ if (Array.isArray(body) && body[0] === ';') {
637
+ const out = [';']
638
+ let args = null
639
+ for (const stmt of body.slice(1)) {
640
+ if (args == null && isSuperCall(stmt)) { args = stmt.slice(2); continue }
641
+ out.push(stmt)
642
+ }
643
+ return { args, body: out.length === 1 ? null : out.length === 2 ? out[1] : out }
644
+ }
645
+ return { args: null, body }
646
+ }
647
+
648
+ // Object shorthand methods and arrow-valued properties both parse as `=>`.
649
+ // Stay conservative: only statement-shaped bodies are receiver methods here;
650
+ // expression-bodied arrows keep their lexical `this` and remain unsupported.
651
+ const OBJ_METHOD_BODY_OPS = new Set([';', 'return', 'if', 'for', 'for-in', 'for-of',
652
+ 'while', 'do', 'switch', 'throw', 'try', 'break', 'continue'])
653
+
654
+ function objectLiteralProps(args) {
655
+ const raw = args.length === 1 && Array.isArray(args[0]) && args[0][0] === ',' ? args[0].slice(1) : args
656
+ return raw.filter(p => p != null)
657
+ }
658
+
659
+ function isStatementBody(body) {
660
+ return Array.isArray(body) && OBJ_METHOD_BODY_OPS.has(body[0])
661
+ }
662
+
663
+ function objectMethodUsesThis(prop) {
664
+ if (!Array.isArray(prop) || prop[0] !== ':' || typeof prop[1] !== 'string') return false
665
+ const value = prop[2]
666
+ if (!Array.isArray(value)) return false
667
+ if (value[0] === '=>' && isStatementBody(value[2])) return usesThis(value[2])
668
+ return false
669
+ }
670
+
671
+ function lowerObjectLiteralThis(args) {
672
+ const props = objectLiteralProps(args)
673
+ if (props.length === 0 || !props.some(objectMethodUsesThis)) return null
674
+ if (!props.every(p => Array.isArray(p) && p[0] === ':' && typeof p[1] === 'string')) return null
675
+
676
+ const self = `obj${objThisIdx++}`
677
+ const litProps = props.map(p => {
678
+ const value = p[2]
679
+ if (objectMethodUsesThis(p)) {
680
+ return [':', p[1], transform(['=>', value[1], block(renameThis(value[2], self))])]
681
+ }
682
+ return [':', p[1], transform(value)]
683
+ })
684
+ const lit = ['{}', litProps.length === 1 ? litProps[0] : [',', ...litProps]]
685
+ return ['()', ['()', ['=>', null, ['{}', [';',
686
+ ['let', ['=', self, lit]],
687
+ ['return', self]
688
+ ]]]], null]
689
+ }
690
+
416
691
  function jzifyError(msg) { throw new Error(`jzify: ${msg}`) }
417
692
 
418
693
  function lowerClass(name, heritage, body) {
419
- if (heritage != null) jzifyError('`class … extends …` is not supported yet — flatten the hierarchy or compose explicitly')
420
694
  let ctorParams = null, ctorBody = null
421
- const methods = [], fields = []
695
+ const methods = [], fields = [], statics = []
422
696
  for (const it of classBodyItems(body)) {
423
697
  if (typeof it === 'string') { fields.push([it, null]); continue } // bare `x;`
424
698
  if (!Array.isArray(it)) continue
699
+ const bareFieldName = constStringKey(it)
700
+ if (bareFieldName != null) { fields.push([bareFieldName, null]); continue }
425
701
  if (it[0] === ':' && Array.isArray(it[2]) && it[2][0] === '=>') {
426
- if (typeof it[1] !== 'string') jzifyError('computed class member names are not supported')
427
- if (it[1] === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
428
- else methods.push([it[1], it[2][1], it[2][2]])
702
+ const key = constStringKey(it[1])
703
+ if (key == null) jzifyError('non-constant computed class member names are not supported')
704
+ if (key === 'constructor') { ctorParams = it[2][1]; ctorBody = it[2][2] }
705
+ else methods.push([key, it[2][1], it[2][2]])
429
706
  continue
430
707
  }
431
708
  if (it[0] === '=') {
432
709
  const lhs = it[1]
433
- if (Array.isArray(lhs) && lhs[0] === 'static') jzifyError('`static` class members are not supported yet')
434
- if (typeof lhs !== 'string') jzifyError('computed/destructured class fields are not supported')
435
- fields.push([lhs, it[2]])
710
+ if (Array.isArray(lhs) && lhs[0] === 'static') {
711
+ const key = constStringKey(lhs[1])
712
+ if (key == null) jzifyError('non-constant computed static class fields are not supported')
713
+ statics.push([key, it[2]])
714
+ continue
715
+ }
716
+ const key = constStringKey(lhs)
717
+ if (key == null) jzifyError('non-constant computed/destructured class fields are not supported')
718
+ fields.push([key, it[2]])
719
+ continue
720
+ }
721
+ if (it[0] === 'static') {
722
+ const key = constStringKey(it[1])
723
+ if (key != null) {
724
+ statics.push([key, null])
725
+ continue
726
+ }
727
+ }
728
+ if (it[0] === 'static' && typeof it[1] === 'string') {
729
+ statics.push([it[1], null])
730
+ continue
731
+ }
732
+ if (it[0] === 'static' && Array.isArray(it[1]) && it[1][0] === ':' && Array.isArray(it[1][2]) && it[1][2][0] === '=>') {
733
+ const key = constStringKey(it[1][1])
734
+ if (key == null) jzifyError('non-constant computed static class member names are not supported')
735
+ statics.push([key, it[1][2], true])
436
736
  continue
437
737
  }
438
738
  if (it[0] === 'get' || it[0] === 'set') jzifyError('class getters/setters are not supported — jz objects have no accessors')
439
739
  if (it[0] === 'static') jzifyError('`static` class members are not supported yet')
440
740
  jzifyError(`unsupported class member ${JSON.stringify(it).slice(0, 60)}`)
441
741
  }
742
+ const superMethods = heritage == null ? new Set() : new Set([
743
+ ...collectSuperMethodCalls(ctorBody),
744
+ ...fields.flatMap(([, init]) => init == null ? [] : [...collectSuperMethodCalls(init)]),
745
+ ...methods.flatMap(([, , mbody]) => [...collectSuperMethodCalls(mbody)])
746
+ ])
747
+ if (heritage != null) {
748
+ const dummySuperVars = new Map([...superMethods].map((k, i) => [k, `super_${i}`]))
749
+ const unsupportedSuperProp = node => node != null && hasSuperProp(rewriteSuperMethodCalls(node, dummySuperVars))
750
+ if (
751
+ unsupportedSuperProp(ctorBody) ||
752
+ fields.some(([, init]) => unsupportedSuperProp(init)) ||
753
+ methods.some(([, , mbody]) => unsupportedSuperProp(mbody))
754
+ )
755
+ jzifyError('`super` property access is not supported yet')
756
+ }
442
757
  const self = `self${classIdx++}`
443
758
  const UNDEF = [] // jessie's node for `undefined`
444
- // A class member body from jessie is a bare statement / `;`-sequence — wrap it
445
- // in a `{}` block so the `=>` handler treats it as a function body, not an
446
- // expression (an unwrapped `;`-seq arrow body produces malformed IR).
447
- const block = b => Array.isArray(b) && b[0] === '{}' ? b : ['{}', b]
448
- const usesThis = n => n === 'this' || (Array.isArray(n) && n[0] !== 'function' && n[0] !== 'class' && n.some(usesThis))
449
759
  // Object literal: every declared field (its initializer inline when it doesn't
450
760
  // touch `this`, else `undefined` and assigned below), every method as its
451
761
  // self-capturing arrow. Declaring all fields up front fixes the object shape.
@@ -457,10 +767,39 @@ function lowerClass(name, heritage, body) {
457
767
  for (const [mname, mparams, mbody] of methods)
458
768
  litProps.push([':', mname, transform(['=>', mparams ?? ['()', null], block(renameThis(mbody, self))])])
459
769
  const lit = ['{}', litProps.length === 0 ? null : litProps.length === 1 ? litProps[0] : [',', ...litProps]]
460
- const stmts = [['let', ['=', self, lit]]]
770
+ let params = ctorParams ?? ['()', null]
771
+ const dynamicBase = heritage != null && typeof heritage !== 'string'
772
+ const baseRef = heritage == null ? null : dynamicBase ? `base${classBaseIdx++}` : heritage
773
+ const stmts = []
774
+ if (heritage != null) {
775
+ const split = splitCtorSuper(ctorBody)
776
+ ctorBody = split.body
777
+ const defaultArgs = ctorParams == null
778
+ ? Array.from({ length: DEFAULT_DERIVED_CTOR_ARITY }, (_, i) => `superArg${classIdx}_${i}`)
779
+ : null
780
+ const baseArgs = split.args ?? (defaultArgs ? [defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]] : paramList(ctorParams))
781
+ stmts.push(['let', ['=', self, ['()', baseRef, ...baseArgs.map(transform)]]])
782
+ const superMethodVars = new Map()
783
+ let superIdx = 0
784
+ for (const mname of superMethods) {
785
+ const v = `super${classIdx}_${superIdx++}`
786
+ superMethodVars.set(mname, v)
787
+ stmts.push(['let', ['=', v, ['.', self, mname]]])
788
+ }
789
+ for (const [fname, init] of fields)
790
+ stmts.push(['=', ['.', self, fname], init != null ? transform(renameThis(rewriteSuperMethodCalls(init, superMethodVars), self)) : UNDEF])
791
+ for (const [mname, mparams, mbody] of methods)
792
+ stmts.push(['=', ['.', self, mname], transform(['=>', mparams ?? ['()', null], block(renameThis(rewriteSuperMethodCalls(mbody, superMethodVars), self))])])
793
+ ctorBody = rewriteSuperMethodCalls(ctorBody, superMethodVars)
794
+ if (defaultArgs) params = ['()', defaultArgs.length === 1 ? defaultArgs[0] : [',', ...defaultArgs]]
795
+ } else {
796
+ stmts.push(['let', ['=', self, lit]])
797
+ }
461
798
  // `this`-dependent field initializers run, in declaration order, before the ctor.
462
- for (const [fname, init] of deferred)
463
- stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
799
+ if (heritage == null) {
800
+ for (const [fname, init] of deferred)
801
+ stmts.push(['=', ['.', self, fname], transform(renameThis(init, self))])
802
+ }
464
803
  if (ctorBody != null) {
465
804
  let cb = transform(renameThis(ctorBody, self))
466
805
  if (Array.isArray(cb) && cb[0] === '{}') cb = cb[1]
@@ -468,15 +807,43 @@ function lowerClass(name, heritage, body) {
468
807
  else if (cb != null) stmts.push(cb)
469
808
  }
470
809
  stmts.push(['return', self])
471
- return ['=>', arrowParams(ctorParams ?? ['()', null]), ['{}', [';', ...stmts]]]
810
+ const factory = ['=>', arrowParams(params), ['{}', [';', ...stmts]]]
811
+ if (!dynamicBase && statics.length === 0) return factory
812
+
813
+ const cls = name || `class${staticClassIdx++}`
814
+ const staticStmts = []
815
+ if (dynamicBase) staticStmts.push(['let', ['=', baseRef, transform(heritage)]])
816
+ staticStmts.push(['let', ['=', cls, factory]])
817
+ for (const [sname, value, isMethod] of statics) {
818
+ const rhs = isMethod
819
+ ? transform(['=>', value[1], block(renameThis(value[2], cls))])
820
+ : value == null ? UNDEF : transform(renameThis(value, cls))
821
+ staticStmts.push(['=', ['.', cls, sname], rhs])
822
+ }
823
+ staticStmts.push(['return', cls])
824
+ return ['()', ['()', ['=>', null, ['{}', [';', ...staticStmts]]]], null]
825
+ }
826
+
827
+ // Array(a, b, …) / new Array(a, b, …) → array literal [a, b, …]; Array() → [].
828
+ // The single-argument Array(n) is a length constructor (n holes), not a
829
+ // literal — return null there so the caller keeps it as a constructor call.
830
+ function lowerArrayConstructor(arg) {
831
+ if (arg == null) return ['[]', null]
832
+ if (Array.isArray(arg) && arg[0] === ',' && arg.length > 2)
833
+ return ['[]', [',', ...arg.slice(1).map(transform)]]
834
+ return null
472
835
  }
473
836
 
474
837
  const handlers = {
475
838
  // Named IIFE: (function name(p){b})(a) → let name = arrow; name(a)
476
839
  '()'(callee, ...rest) {
840
+ if (callee === 'Array') {
841
+ const lit = lowerArrayConstructor(rest[0])
842
+ if (lit) return lit
843
+ }
477
844
  if (Array.isArray(callee) && callee[0] === '()' && Array.isArray(callee[1]) && callee[1][0] === 'function' && callee[1][1]) {
478
845
  const [, name, params, body] = callee[1]
479
- const [p2, b2] = lowerArguments(params, body)
846
+ const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
480
847
  return [';', ['let', ['=', name, ['=>', arrowParams(p2), wrapArrowBody(b2)]]], ['()', name, ...rest.map(transform)]]
481
848
  }
482
849
  },
@@ -485,7 +852,7 @@ const handlers = {
485
852
  // bound inside body per ES spec: `function f(){...f...}` → `(()=>{let f;f=arrow;return f})()`.
486
853
  // Statement-form named functions are hoisted by transformScope before reaching here.
487
854
  'function'(name, params, body) {
488
- const [p2, b2] = lowerArguments(params, body)
855
+ const [p2, b2] = lowerArguments(params, functionBodyBlock(body))
489
856
  const arrow = ['=>', p2, wrapArrowBody(b2)]
490
857
  if (name) {
491
858
  return ['()', ['()', ['=>', null, ['{}', [';',
@@ -498,7 +865,21 @@ const handlers = {
498
865
  },
499
866
 
500
867
  '=>'(params, body) {
501
- const [p2, b2] = lowerArguments(params, body)
868
+ // The subscript parser elides the `[';', stmt, null]` wrapper inside an
869
+ // arrow's `{` block when the block holds a single statement — the result
870
+ // `['{}', stmt]` is syntactically identical to an object-literal shape, and
871
+ // downstream `{}` handlers can no longer tell them apart. JS grammar makes
872
+ // it unambiguous: `=>` followed by `{` is always a block (use `=> ({...})`
873
+ // for an object return), so coerce every single-statement body to the
874
+ // canonical `['{}', [';', stmt]]` block form — `;`-wrapped, never bare.
875
+ let b = body
876
+ if (Array.isArray(b) && b[0] === '{}' && b.length === 2) {
877
+ const inner = b[1]
878
+ if (inner != null && !(Array.isArray(inner) && inner[0] === ';')) {
879
+ b = ['{}', [';', inner]]
880
+ }
881
+ }
882
+ const [p2, b2] = lowerArguments(params, b)
502
883
  return ['=>', p2, transform(b2)]
503
884
  },
504
885
 
@@ -514,17 +895,13 @@ const handlers = {
514
895
  return ['let', ...args.map(transform)]
515
896
  },
516
897
 
898
+ ':'(label, body) {
899
+ if (typeof label === 'string' && Array.isArray(body) && LABEL_BODY_OPS.has(body[0]))
900
+ return ['label', label, transform(body)]
901
+ },
902
+
517
903
  '='(lhs, rhs) {
518
- // Chained property assignment: a.x = a.y = v → a.y = v; a.x = v
519
- if (Array.isArray(lhs) && lhs[0] === '.' && Array.isArray(rhs) && rhs[0] === '=') {
520
- const targets = []
521
- let cur = ['=', lhs, rhs]
522
- while (Array.isArray(cur) && cur[0] === '=') { targets.push(cur[1]); cur = cur[2] }
523
- const val = transform(cur)
524
- const stmts = []
525
- for (let i = targets.length - 1; i >= 0; i--) stmts.push(['=', transform(targets[i]), val])
526
- return stmts.length === 1 ? stmts[0] : [';', ...stmts]
527
- }
904
+ if (isDestructurePat(lhs)) return ['=', transformPattern(lhs), transform(rhs)]
528
905
  },
529
906
 
530
907
  'switch'(disc, ...cases) {
@@ -546,32 +923,49 @@ const handlers = {
546
923
  return transformSwitch(disc, clean)
547
924
  },
548
925
 
549
- // == ===, != !== (with prototype identity folding)
550
- '=='(a, b) { return isProto(a) || isProto(b) ? 1 : ['===', transform(a), transform(b)] },
551
- '!='(a, b) { return isProto(a) || isProto(b) ? 0 : ['!==', transform(a), transform(b)] },
926
+ // Equality keeps the JS loose/strict distinction (jz core now lowers both):
927
+ // `==`/`!=` stay loose, `===`/`!==` stay strict. A comparison against a prototype
928
+ // object (e.g. `x.constructor === Object`) folds to a boolean jz has no
929
+ // prototype objects, so identity against one is decided statically.
930
+ '=='(a, b) { return isProto(a) || isProto(b) ? 1 : ['==', transform(a), transform(b)] },
931
+ '!='(a, b) { return isProto(a) || isProto(b) ? 0 : ['!=', transform(a), transform(b)] },
552
932
  '==='(a, b) { if (isProto(a) || isProto(b)) return 1 },
553
933
  '!=='(a, b) { if (isProto(a) || isProto(b)) return 0 },
554
934
 
555
935
  // new → call (keep TypedArrays)
556
936
  'new'(ctor, ...cargs) {
557
- if (Array.isArray(ctor) && ctor[0] === '()' && Array.isArray(ctor[1]) && ctor[1][0] === '.') {
558
- return ['()', ['.', transform(['new', ctor[1][1]]), ctor[1][2]], ...ctor.slice(2).map(transform)]
937
+ if (Array.isArray(ctor) && ctor[0] === '()' && ctor[1] === 'Array') {
938
+ const lit = lowerArrayConstructor(ctor[2])
939
+ if (lit) return lit
559
940
  }
560
941
  const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
561
- if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array')) return ['new', transform(ctor), ...cargs.map(transform)]
942
+ if (typeof name === 'string' && (TYPED_ARRAYS.has(name) || name === 'Array' || name === 'RegExp')) return ['new', transform(ctor), ...cargs.map(transform)]
562
943
  if (Array.isArray(ctor) && ctor[0] === '()') return transform(ctor)
563
944
  // `new C(a)` → `C(a)`; `new C` (no parens) → `C()` — a 2-element `['()', X]`
564
945
  // is grouping parens, so a no-arg call needs the explicit `null` arg slot.
565
946
  return ['()', transform(ctor), ...(cargs.length ? cargs.map(transform) : [null])]
566
947
  },
567
948
 
568
- // instanceof → typeof / Array.isArray (jzify allows what strict mode prohibits)
949
+ // instanceof → typeof / Array.isArray / __is_* helpers. jzify lets us preserve
950
+ // constructor identity that strict-mode `instanceof` discards — Map, Set, and
951
+ // TypedArrays get dedicated typed predicates (__is_map / __is_set / __is_typed)
952
+ // that both compile to a runtime __ptr_type check and feed extractRefinements
953
+ // for downstream method-dispatch elision (e.g. `if (x instanceof Map) x.has(k)`
954
+ // resolves to __map_has, not the default __set_has fallback). Date / RegExp /
955
+ // Object stay on the weak typeof-object lowering — they share PTR.OBJECT and
956
+ // the JS runtime offers no cheaper discrimination.
569
957
  'instanceof'(val, ctor) {
570
958
  const t = transform(val)
571
959
  const name = typeof ctor === 'string' ? ctor : (Array.isArray(ctor) && ctor[0] === '()' ? ctor[1] : null)
960
+ // Static fold: literal shape of LHS already discriminates against the constructor.
961
+ const fold = staticInstanceofFold(val, name)
962
+ if (fold != null) return [null, fold]
572
963
  if (name === 'Array') return ['()', ['.', 'Array', 'isArray'], t]
573
- if (name === 'Object') return ['===', ['typeof', t], [null, 'object']]
574
- if (typeof name === 'string' && TYPED_ARRAYS.has(name)) return ['===', ['typeof', t], [null, 'object']]
964
+ if (name === 'Map') return ['()', '__is_map', t]
965
+ if (name === 'Set') return ['()', '__is_set', t]
966
+ if (typeof name === 'string' && TYPED_ARRAYS.has(name) && name !== 'ArrayBuffer' && name !== 'DataView')
967
+ return ['()', '__is_typed', t]
968
+ // Object / ArrayBuffer / DataView / RegExp / Date / unknown: weak typeof-object check.
575
969
  return ['===', ['typeof', t], [null, 'object']]
576
970
  },
577
971
 
@@ -585,8 +979,27 @@ const handlers = {
585
979
  ['while', ['||', flag, transform(cond)], ['{}', [';', ['=', flag, [null, false]], transform(body)]]]]
586
980
  },
587
981
 
588
- // Block body: recurse as scope for hoisting
589
- '{}'(...args) { return ['{}', ...args.map(a => transformScope(a) ?? a)] },
982
+ // Block body: recurse as scope for hoisting. transformScope reduces a single-statement
983
+ // sequence to its bare element; if the input WAS block-shaped (`;` list or a single
984
+ // statement op), we re-wrap the collapsed result in `[';', ...]` so prepare.js keeps
985
+ // routing the `{}` through the block branch instead of mistaking it for `{ expr }`.
986
+ '{}'(...args) {
987
+ const loweredObject = lowerObjectLiteralThis(args)
988
+ if (loweredObject) return loweredObject
989
+
990
+ return ['{}', ...args.map((a, i) => {
991
+ const t = transformScope(a) ?? a
992
+ if (i !== 0 || a == null) return t
993
+ const blockIn = Array.isArray(a) && JZ_BLOCK_OPS.has(a[0])
994
+ if (!blockIn || t == null) return t
995
+ // transformScope collapses a single-statement `;`-list to its bare element;
996
+ // re-wrap so the block always stays `['{}', [';', ...]]`. The `;` is the
997
+ // only reliable block marker — a bare statement op can desugar to an
998
+ // expression op downstream (postfix `c++` → `['-', ...]`) and lose its
999
+ // block identity, leaving `['{}', expr]` mistakable for an object literal.
1000
+ return Array.isArray(t) && t[0] === ';' ? t : [';', t]
1001
+ })]
1002
+ },
590
1003
 
591
1004
  // Export: recurse into exported declaration. Statement-form `export function name`
592
1005
  // and `export default function name` must be hoisted as const-arrows — otherwise
@@ -642,16 +1055,16 @@ function foldStaticExportHelpers(ast) {
642
1055
 
643
1056
  const defPropAliases = new Set()
644
1057
  for (const stmt of body) {
645
- if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' && isObjectDefineProperty(stmt[2]))
646
- defPropAliases.add(stmt[1])
1058
+ const b = bindingOf(stmt)
1059
+ if (b && isObjectDefineProperty(b[1])) defPropAliases.add(b[0])
647
1060
  }
648
1061
  if (!defPropAliases.size) return ast
649
1062
 
650
1063
  const helperNames = new Set()
651
1064
  for (const stmt of body) {
652
- if (Array.isArray(stmt) && stmt[0] === '=' && typeof stmt[1] === 'string' &&
653
- Array.isArray(stmt[2]) && stmt[2][0] === '=>' && containsDefinePropertyCall(stmt[2], defPropAliases))
654
- helperNames.add(stmt[1])
1065
+ const b = bindingOf(stmt)
1066
+ if (b && Array.isArray(b[1]) && b[1][0] === '=>' && containsDefinePropertyCall(b[1], defPropAliases))
1067
+ helperNames.add(b[0])
655
1068
  }
656
1069
  if (!helperNames.size) return ast
657
1070
 
@@ -671,6 +1084,210 @@ function foldStaticExportHelpers(ast) {
671
1084
  return rewritten.length === 0 ? null : rewritten.length === 1 ? rewritten[0] : [';', ...rewritten]
672
1085
  }
673
1086
 
1087
+ // Esbuild's CommonJS/ESM interop helpers alias Object reflection built-ins into
1088
+ // locals (`var __create = Object.create`, `var __getOwnPropNames =
1089
+ // Object.getOwnPropertyNames`, ...). jz deliberately does not expose those
1090
+ // built-ins as first-class function values, but the helpers are static enough to
1091
+ // lower back to the supported direct calls and module reads.
1092
+ function foldStaticBundlerHelpers(ast) {
1093
+ const body = astSeq(ast)
1094
+ if (!body) return ast
1095
+ const binds = body.map(bindingOf) // [name, init] | null, index-aligned with body
1096
+
1097
+ // Local aliases of Object reflection built-ins: name -> canonical built-in.
1098
+ // esbuild's interop preamble always emits these (`var __defProp =
1099
+ // Object.defineProperty`, ...); their absence proves the input is not a
1100
+ // bundle, so the fold stays a strict no-op rather than guessing.
1101
+ const aliases = new Map()
1102
+ for (const b of binds) {
1103
+ const key = b && objectBuiltinKey(b[1])
1104
+ if (key) aliases.set(b[0], key)
1105
+ }
1106
+ if (!aliases.size) return ast
1107
+
1108
+ // __copyProps: an arrow driving both aliased getOwnPropertyNames + defineProperty.
1109
+ const copyHelpers = new Set()
1110
+ for (const b of binds)
1111
+ if (b && isArrow(b[1]) &&
1112
+ containsCall(b[1], c => aliases.get(c) === 'Object.getOwnPropertyNames') &&
1113
+ containsCall(b[1], c => aliases.get(c) === 'Object.defineProperty'))
1114
+ copyHelpers.add(b[0])
1115
+
1116
+ // __toESM: an arrow cloning a module behind a prototype, tagging default/__esModule.
1117
+ const interopHelpers = new Set()
1118
+ for (const b of binds)
1119
+ if (b && isArrow(b[1]) &&
1120
+ containsCall(b[1], c => aliases.get(c) === 'Object.create') &&
1121
+ containsCall(b[1], c => aliases.get(c) === 'Object.getPrototypeOf') &&
1122
+ containsCall(b[1], c => copyHelpers.has(c)) &&
1123
+ astSome(b[1], n => n === 'default') && astSome(b[1], n => n === '__esModule'))
1124
+ interopHelpers.add(b[0])
1125
+
1126
+ // Bindings produced by an interop-helper call: name -> wrapped module expression.
1127
+ const interopBindings = new Map()
1128
+ for (const b of binds)
1129
+ if (b && Array.isArray(b[1]) && b[1][0] === '()' && interopHelpers.has(b[1][1])) {
1130
+ const args = callArgs(b[1].slice(2))
1131
+ if (args.length) interopBindings.set(b[0], args[0])
1132
+ }
1133
+
1134
+ let out = body.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings))
1135
+ if (interopBindings.size) out = out.map(stmt => replaceInteropReads(stmt, interopBindings))
1136
+ out = out.map(stmt => rewriteBundlerAliases(stmt, aliases, interopBindings)).filter(s => s != null)
1137
+
1138
+ // Drop synthetic alias/helper bindings nothing references after rewriting.
1139
+ const synthetic = n => aliases.has(n) || copyHelpers.has(n) || interopHelpers.has(n) || interopBindings.has(n)
1140
+ const live = new Set()
1141
+ for (const stmt of out) {
1142
+ const b = bindingOf(stmt)
1143
+ if (!(b && synthetic(b[0]))) collectRefs(stmt, live)
1144
+ }
1145
+ out = out.filter(stmt => {
1146
+ const b = bindingOf(stmt)
1147
+ return !(b && synthetic(b[0]) && !live.has(b[0]))
1148
+ })
1149
+
1150
+ return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
1151
+ }
1152
+
1153
+ const isArrow = node => Array.isArray(node) && node[0] === '=>'
1154
+
1155
+ const OBJECT_BUILTINS = new Set(['create', 'getPrototypeOf', 'getOwnPropertyNames', 'getOwnPropertyDescriptor', 'defineProperty'])
1156
+
1157
+ // Canonical name of the Object reflection built-in `node` references, or null.
1158
+ function objectBuiltinKey(node) {
1159
+ if (!Array.isArray(node) || node[0] !== '.') return null
1160
+ if (node[1] === 'Object' && OBJECT_BUILTINS.has(node[2])) return 'Object.' + node[2]
1161
+ return isObjectHasOwnPropertyRef(node) ? 'Object.prototype.hasOwnProperty' : null
1162
+ }
1163
+
1164
+ // Deep `some` over AST children (skips the op slot, so op names never match).
1165
+ function astSome(node, pred) {
1166
+ if (pred(node)) return true
1167
+ if (!Array.isArray(node)) return false
1168
+ for (let i = 1; i < node.length; i++) if (astSome(node[i], pred)) return true
1169
+ return false
1170
+ }
1171
+
1172
+ // Does `node` contain a `()` call whose string callee satisfies `ok`?
1173
+ const containsCall = (node, ok) =>
1174
+ astSome(node, n => Array.isArray(n) && n[0] === '()' && typeof n[1] === 'string' && ok(n[1]))
1175
+
1176
+ function rewriteBundlerAliases(node, aliases, interopBindings) {
1177
+ if (!Array.isArray(node)) return node
1178
+ const rec = n => rewriteBundlerAliases(n, aliases, interopBindings)
1179
+
1180
+ if (node[0] === ';') {
1181
+ const out = [';']
1182
+ for (let i = 1; i < node.length; i++) {
1183
+ const child = rec(node[i])
1184
+ if (child != null) out.push(child)
1185
+ }
1186
+ return out.length === 1 ? null : out.length === 2 ? out[1] : out
1187
+ }
1188
+ if (node[0] === '{}' && node.length === 2) {
1189
+ const wasBlock = Array.isArray(node[1]) && JZ_BLOCK_OPS.has(node[1][0])
1190
+ const inner = rec(node[1])
1191
+ if (!wasBlock || inner == null) return ['{}', inner]
1192
+ const stayed = Array.isArray(inner) && JZ_BLOCK_OPS.has(inner[0])
1193
+ return ['{}', stayed ? inner : [';', inner]]
1194
+ }
1195
+
1196
+ if (node[0] === '()') {
1197
+ const callee = node[1]
1198
+ const args = callArgs(node.slice(2))
1199
+
1200
+ if (typeof callee === 'string') {
1201
+ const key = aliases.get(callee)
1202
+ if (key === 'Object.defineProperty') {
1203
+ const define = staticDefineProperty(args)
1204
+ if (define !== undefined) return define
1205
+ }
1206
+ if (key === 'Object.getOwnPropertyNames' || key === 'Object.create') {
1207
+ if (key === 'Object.create' && isGetPrototypeOfCall(args[0], aliases)) return ['{}', null]
1208
+ return ['()', key, ...args.map(rec)]
1209
+ }
1210
+ }
1211
+ // `__hasOwnProp.call(o, k)` -> `o.hasOwnProperty(k)`.
1212
+ if (Array.isArray(callee) && callee[0] === '.' && callee[2] === 'call' && args.length >= 2 &&
1213
+ typeof callee[1] === 'string' && aliases.get(callee[1]) === 'Object.prototype.hasOwnProperty')
1214
+ return ['()', ['.', rec(args[0]), 'hasOwnProperty'], rec(args[1])]
1215
+ // `(0, fn)(...)` comma-call resolving to an interop module read.
1216
+ const seqCall = commaZeroCall(callee, interopBindings)
1217
+ if (seqCall) return ['()', seqCall, ...args.map(rec)]
1218
+ }
1219
+
1220
+ if (node[0] === '.' || node[0] === '?.') return [node[0], rec(node[1]), node[2]]
1221
+ if (node[0] === ':') return [node[0], node[1], rec(node[2])]
1222
+ return node.map((part, i) => i === 0 ? part : rec(part))
1223
+ }
1224
+
1225
+ function replaceInteropReads(node, bindings) {
1226
+ if (typeof node === 'string' && bindings.has(node)) return cloneAst(bindings.get(node))
1227
+ if (!Array.isArray(node)) return node
1228
+ if (node[0] === '=' && typeof node[1] === 'string') return ['=', node[1], replaceInteropReads(node[2], bindings)]
1229
+ if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var')
1230
+ return [node[0], ...node.slice(1).map(decl =>
1231
+ Array.isArray(decl) && decl[0] === '=' ? ['=', decl[1], replaceInteropReads(decl[2], bindings)] : decl)]
1232
+ if ((node[0] === '.' || node[0] === '?.') && typeof node[1] === 'string' && typeof node[2] === 'string' && bindings.has(node[1])) {
1233
+ const mod = cloneAst(bindings.get(node[1]))
1234
+ return node[2] === 'default' ? mod : [node[0], mod, node[2]]
1235
+ }
1236
+ if (node[0] === ':') return [node[0], node[1], replaceInteropReads(node[2], bindings)]
1237
+ return node.map((part, i) => i === 0 ? part : replaceInteropReads(part, bindings))
1238
+ }
1239
+
1240
+ function isGetPrototypeOfCall(node, aliases) {
1241
+ if (!Array.isArray(node) || node[0] !== '()') return false
1242
+ const callee = node[1]
1243
+ return (typeof callee === 'string' && aliases.get(callee) === 'Object.getPrototypeOf') ||
1244
+ objectBuiltinKey(callee) === 'Object.getPrototypeOf'
1245
+ }
1246
+
1247
+ function commaZeroCall(callee, bindings) {
1248
+ if (!Array.isArray(callee) || callee[0] !== '()' || !Array.isArray(callee[1]) || callee[1][0] !== ',') return null
1249
+ const parts = callee[1].slice(1)
1250
+ if (parts.length !== 2 || !isZeroLiteral(parts[0])) return null
1251
+ const fn = replaceInteropReads(parts[1], bindings)
1252
+ return fn === parts[1] ? null : fn
1253
+ }
1254
+
1255
+ // `defProp(obj, "key", descriptor)` -> `obj.key = value`; null drops `__esModule`.
1256
+ function staticDefineProperty(args) {
1257
+ if (args.length < 3) return undefined
1258
+ const [obj, keyExpr, desc] = args
1259
+ const key = stringLiteral(keyExpr)
1260
+ const props = objectProps(desc)
1261
+ if (typeof key !== 'string' || !props) return undefined
1262
+ if (key === '__esModule') return null
1263
+ const prop = name => props.find(p => Array.isArray(p) && p[0] === ':' && p[1] === name)?.[2]
1264
+ const value = prop('value')
1265
+ if (value !== undefined) return ['=', ['.', obj, key], value]
1266
+ const got = getterReturnExpr(prop('get'))
1267
+ return got !== null ? ['=', ['.', obj, key], got] : undefined
1268
+ }
1269
+
1270
+ function stringLiteral(node) {
1271
+ return Array.isArray(node) && node[0] == null && typeof node[1] === 'string' ? node[1] : null
1272
+ }
1273
+
1274
+ // Identifier references in `node`, excluding declaration names, member property
1275
+ // names and object keys — enough to decide whether a synthetic binding is live.
1276
+ function collectRefs(node, out) {
1277
+ if (typeof node === 'string') return void out.add(node)
1278
+ if (!Array.isArray(node)) return
1279
+ if (node[0] === 'let' || node[0] === 'const' || node[0] === 'var') {
1280
+ for (let i = 1; i < node.length; i++)
1281
+ if (Array.isArray(node[i]) && node[i][0] === '=') collectRefs(node[i][2], out)
1282
+ } else if ((node[0] === '.' || node[0] === '?.') && typeof node[2] === 'string') {
1283
+ collectRefs(node[1], out)
1284
+ } else if (node[0] === ':') {
1285
+ collectRefs(node[2], out)
1286
+ } else {
1287
+ for (let i = 1; i < node.length; i++) collectRefs(node[i], out)
1288
+ }
1289
+ }
1290
+
674
1291
  function astSeq(ast) {
675
1292
  if (!Array.isArray(ast)) return null
676
1293
  return ast[0] === ';' ? ast.slice(1).filter(Boolean) : [ast]
@@ -680,12 +1297,28 @@ function isObjectDefineProperty(node) {
680
1297
  return Array.isArray(node) && node[0] === '.' && node[1] === 'Object' && node[2] === 'defineProperty'
681
1298
  }
682
1299
 
1300
+ /** Unwrap an esbuild module binding to `[name, init]`. After hoistVars, a binding
1301
+ * reaches this pass either split into a bare `['=', name, init]` (RHS hoisted out
1302
+ * as a separate `let name;`) or kept as a single `['let', ['=', name, init]]` decl
1303
+ * (arrow RHS — see the `;` handler in hoistVars). The fold keys on name/init,
1304
+ * so it must see through both shapes. */
1305
+ function bindingOf(stmt) {
1306
+ if (!Array.isArray(stmt)) return null
1307
+ if (stmt[0] === '=' && typeof stmt[1] === 'string') return [stmt[1], stmt[2]]
1308
+ if ((stmt[0] === 'let' || stmt[0] === 'const' || stmt[0] === 'var') && stmt.length === 2 &&
1309
+ Array.isArray(stmt[1]) && stmt[1][0] === '=' && typeof stmt[1][1] === 'string')
1310
+ return [stmt[1][1], stmt[1][2]]
1311
+ return null
1312
+ }
1313
+
683
1314
  function isDefPropAliasAssign(stmt, aliases) {
684
- return Array.isArray(stmt) && stmt[0] === '=' && aliases.has(stmt[1]) && isObjectDefineProperty(stmt[2])
1315
+ const b = bindingOf(stmt)
1316
+ return b != null && aliases.has(b[0]) && isObjectDefineProperty(b[1])
685
1317
  }
686
1318
 
687
1319
  function isExportHelperAssign(stmt, helpers) {
688
- return Array.isArray(stmt) && stmt[0] === '=' && helpers.has(stmt[1])
1320
+ const b = bindingOf(stmt)
1321
+ return b != null && helpers.has(b[0])
689
1322
  }
690
1323
 
691
1324
  function containsDefinePropertyCall(node, aliases) {
@@ -730,6 +1363,8 @@ function getterReturnExpr(node) {
730
1363
  if (params.length !== 0) return null
731
1364
  const body = node[2]
732
1365
  if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === 'return') return body[1][1]
1366
+ if (Array.isArray(body) && body[0] === '{}' && Array.isArray(body[1]) && body[1][0] === ';' &&
1367
+ Array.isArray(body[1][1]) && body[1][1][0] === 'return') return body[1][1][1]
733
1368
  if (Array.isArray(body) && body[0] === 'return') return body[1]
734
1369
  return body
735
1370
  }
@@ -752,6 +1387,9 @@ function canonicalizeObjectIdioms(node) {
752
1387
  const hasOwnCall = objectHasOwnPropertyCall(out)
753
1388
  if (hasOwnCall) return ['()', ['.', hasOwnCall.obj, 'hasOwnProperty'], hasOwnCall.key]
754
1389
 
1390
+ const mapString = arrayMapStringCallback(out)
1391
+ if (mapString) return mapString
1392
+
755
1393
  if (out[0] === '&&') {
756
1394
  const leftCtor = constructorIsObject(out[1])
757
1395
  const rightKeys = objectKeysLengthZero(out[2])
@@ -765,16 +1403,31 @@ function canonicalizeObjectIdioms(node) {
765
1403
  return out
766
1404
  }
767
1405
 
1406
+ function arrayMapStringCallback(node) {
1407
+ if (!Array.isArray(node) || node[0] !== '()') return null
1408
+ const callee = node[1]
1409
+ if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'map') return null
1410
+ const args = callArgs(node.slice(2))
1411
+ if (args.length !== 1 || args[0] !== 'String') return null
1412
+ return ['()', callee, ['=>', 'value', ['()', 'String', 'value']]]
1413
+ }
1414
+
768
1415
  function objectHasOwnPropertyCall(node) {
769
1416
  if (!Array.isArray(node) || node[0] !== '()') return null
770
1417
  const callee = node[1]
771
1418
  if (!Array.isArray(callee) || callee[0] !== '.' || callee[2] !== 'call') return null
772
- if (!Array.isArray(callee[1]) || callee[1][0] !== '.' || callee[1][1] !== 'Object' || callee[1][2] !== 'hasOwnProperty') return null
1419
+ if (!isObjectHasOwnPropertyRef(callee[1])) return null
773
1420
  const args = callArgs(node.slice(2))
774
1421
  if (args.length < 2) return null
775
1422
  return { obj: args[0], key: args[1] }
776
1423
  }
777
1424
 
1425
+ function isObjectHasOwnPropertyRef(node) {
1426
+ if (!Array.isArray(node) || node[0] !== '.' || node[2] !== 'hasOwnProperty') return false
1427
+ if (node[1] === 'Object') return true
1428
+ return Array.isArray(node[1]) && node[1][0] === '.' && node[1][1] === 'Object' && node[1][2] === 'prototype'
1429
+ }
1430
+
778
1431
  function constructorIsObject(node) {
779
1432
  if (!Array.isArray(node) || (node[0] !== '===' && node[0] !== '==')) return null
780
1433
  const left = constructorReceiver(node[1])
@@ -836,24 +1489,63 @@ function stripTerminalSwitchBreak(body) {
836
1489
  return stmts.length === 0 ? null : stmts.length === 1 ? stmts[0] : [';', ...stmts]
837
1490
  }
838
1491
 
1492
+ const SWITCH_BREAK_BOUNDARIES = new Set(['for', 'for-in', 'for-of', 'while', 'do', 'switch', '=>', 'function', 'class'])
1493
+
1494
+ function hasOwnSwitchBreak(node) {
1495
+ if (!Array.isArray(node)) return false
1496
+ if (node[0] === 'break') return true
1497
+ if (SWITCH_BREAK_BOUNDARIES.has(node[0])) return false
1498
+ for (let i = 1; i < node.length; i++) if (hasOwnSwitchBreak(node[i])) return true
1499
+ return false
1500
+ }
1501
+
1502
+ function rewriteSwitchBreaks(node, flag) {
1503
+ if (!Array.isArray(node)) return node
1504
+ const op = node[0]
1505
+ if (op === 'break') return ['=', flag, [null, true]]
1506
+ if (SWITCH_BREAK_BOUNDARIES.has(op)) return node
1507
+
1508
+ if (op === ';') {
1509
+ const out = []
1510
+ const stmts = node.slice(1)
1511
+ for (let i = 0; i < stmts.length; i++) {
1512
+ const stmt = stmts[i]
1513
+ out.push(rewriteSwitchBreaks(stmt, flag))
1514
+ if (hasOwnSwitchBreak(stmt) && i < stmts.length - 1) {
1515
+ const tail = rewriteSwitchBreaks([';', ...stmts.slice(i + 1)], flag)
1516
+ out.push(['if', ['!', flag], tail])
1517
+ break
1518
+ }
1519
+ }
1520
+ return out.length === 0 ? null : out.length === 1 ? out[0] : [';', ...out]
1521
+ }
1522
+
1523
+ return node.map((part, i) => i === 0 ? part : rewriteSwitchBreaks(part, flag))
1524
+ }
1525
+
839
1526
  /** Transform switch statement to if/else chain. */
840
1527
  let swIdx = 0
841
1528
  function transformSwitch(discriminant, cases) {
842
1529
  const disc = transform(discriminant)
843
1530
  const tmp = `\uE000sw${swIdx++}`
1531
+ const needsBreakFlag = cases.some(c => hasOwnSwitchBreak(c[0] === 'case' ? c[2] : c[1]))
1532
+ const brk = needsBreakFlag ? `\uE000swbrk${swIdx++}` : null
844
1533
 
845
1534
  // Collect case/default
846
1535
  const stmts = [['let', ['=', tmp, disc]]]
1536
+ if (brk) stmts.push(['let', ['=', brk, [null, false]]])
847
1537
  let chain = null
848
1538
 
849
1539
  for (let i = cases.length - 1; i >= 0; i--) {
850
1540
  const c = cases[i]
851
1541
  if (c[0] === 'default') {
852
- chain = transform(c[1])
1542
+ const body = transform(c[1])
1543
+ chain = brk ? rewriteSwitchBreaks(body, brk) : body
853
1544
  } else if (c[0] === 'case') {
854
1545
  const cond = ['===', tmp, transform(c[1])]
855
1546
  const body = transform(c[2])
856
- chain = chain != null ? ['if', cond, body, chain] : ['if', cond, body]
1547
+ const lowered = brk ? rewriteSwitchBreaks(body, brk) : body
1548
+ chain = chain != null ? ['if', cond, lowered, chain] : ['if', cond, lowered]
857
1549
  }
858
1550
  }
859
1551
  if (chain) stmts.push(chain)