tova 0.2.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,6 +27,7 @@ export class ClientCodegen extends BaseCodegen {
27
27
  }
28
28
  if (node.type === 'IfExpression') {
29
29
  return this._containsRPC(node.condition) || this._containsRPC(node.consequent) ||
30
+ (node.alternates && node.alternates.some(a => this._containsRPC(a.condition) || this._containsRPC(a.body))) ||
30
31
  this._containsRPC(node.elseBody);
31
32
  }
32
33
  if (node.type === 'ForStatement') return this._containsRPC(node.iterable) || this._containsRPC(node.body);
@@ -45,14 +46,15 @@ export class ClientCodegen extends BaseCodegen {
45
46
  return this._containsRPC(node.subject) || node.arms.some(a => this._containsRPC(a.body));
46
47
  }
47
48
  if (node.type === 'TryCatchStatement') {
48
- return this._containsRPC(node.tryBlock) || this._containsRPC(node.catchBlock) ||
49
- this._containsRPC(node.finallyBlock);
49
+ return (node.tryBody && node.tryBody.some(s => this._containsRPC(s))) ||
50
+ (node.catchBody && node.catchBody.some(s => this._containsRPC(s))) ||
51
+ (node.finallyBody && node.finallyBody.some(s => this._containsRPC(s)));
50
52
  }
51
53
  if (node.type === 'PipeExpression') {
52
54
  return this._containsRPC(node.left) || this._containsRPC(node.right);
53
55
  }
54
56
  if (node.type === 'GuardStatement') {
55
- return this._containsRPC(node.condition) || this._containsRPC(node.elseBlock);
57
+ return this._containsRPC(node.condition) || this._containsRPC(node.elseBody);
56
58
  }
57
59
  if (node.type === 'LetDestructure') return this._containsRPC(node.value);
58
60
  if (node.type === 'ArrayLiteral') return node.elements.some(e => this._containsRPC(e));
@@ -61,7 +63,7 @@ export class ClientCodegen extends BaseCodegen {
61
63
  if (node.type === 'AwaitExpression') return this._containsRPC(node.argument);
62
64
  if (node.type === 'PropagateExpression') return this._containsRPC(node.expression);
63
65
  if (node.type === 'UnaryExpression') return this._containsRPC(node.operand);
64
- if (node.type === 'TemplateLiteral') return node.parts.some(p => p.type === 'expr' && this._containsRPC(p.expression));
66
+ if (node.type === 'TemplateLiteral') return node.parts.some(p => p.type === 'expr' && this._containsRPC(p.value));
65
67
  if (node.type === 'ChainedComparison') return node.operands.some(o => this._containsRPC(o));
66
68
  if (node.type === 'RangeExpression') return this._containsRPC(node.start) || this._containsRPC(node.end);
67
69
  if (node.type === 'SliceExpression') return this._containsRPC(node.object) || this._containsRPC(node.start) || this._containsRPC(node.end) || this._containsRPC(node.step);
@@ -177,7 +179,7 @@ export class ClientCodegen extends BaseCodegen {
177
179
  const lines = [];
178
180
 
179
181
  // Runtime imports
180
- lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_inject_css, batch, onMount, onUnmount, onCleanup, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, createRoot, watch, untrack, Dynamic, Portal, lazy } from './runtime/reactivity.js';`);
182
+ lines.push(`import { createSignal, createEffect, createComputed, mount, hydrate, tova_el, tova_fragment, tova_keyed, tova_transition, tova_inject_css, batch, onMount, onUnmount, onCleanup, createRef, createContext, provide, inject, createErrorBoundary, ErrorBoundary, ErrorInfo, createRoot, watch, untrack, Dynamic, Portal, lazy } from './runtime/reactivity.js';`);
181
183
  lines.push(`import { rpc } from './runtime/rpc.js';`);
182
184
 
183
185
  // Hoist import lines from shared code to the top of the module
@@ -360,26 +362,26 @@ export class ClientCodegen extends BaseCodegen {
360
362
 
361
363
  _generateEffect(body) {
362
364
  const hasRPC = this._containsRPC(body);
363
- let code;
365
+ const p = [];
364
366
  if (hasRPC) {
365
- code = `createEffect(() => {\n`;
366
- code += `${this.i()} (async () => {\n`;
367
+ p.push(`createEffect(() => {\n`);
368
+ p.push(`${this.i()} (async () => {\n`);
367
369
  this.indent += 2;
368
370
  const prevAsync = this._asyncContext;
369
371
  this._asyncContext = true;
370
- code += this.genBlockStatements(body);
372
+ p.push(this.genBlockStatements(body));
371
373
  this._asyncContext = prevAsync;
372
374
  this.indent -= 2;
373
- code += `\n${this.i()} })();\n`;
374
- code += `${this.i()}});`;
375
+ p.push(`\n${this.i()} })();\n`);
376
+ p.push(`${this.i()}});`);
375
377
  } else {
376
- code = `createEffect(() => {\n`;
378
+ p.push(`createEffect(() => {\n`);
377
379
  this.indent++;
378
- code += this.genBlockStatements(body);
380
+ p.push(this.genBlockStatements(body));
379
381
  this.indent--;
380
- code += `\n${this.i()}});`;
382
+ p.push(`\n${this.i()}});`);
381
383
  }
382
- return code;
384
+ return p.join('');
383
385
  }
384
386
 
385
387
  // Generate a short hash from component name + CSS content (for CSS scoping)
@@ -422,20 +424,21 @@ export class ClientCodegen extends BaseCodegen {
422
424
  const savedState = new Set(this.stateNames);
423
425
  const savedComputed = new Set(this.computedNames);
424
426
 
425
- let code = `function ${comp.name}(${paramStr}) {\n`;
427
+ const p = [];
428
+ p.push(`function ${comp.name}(${paramStr}) {\n`);
426
429
  this.indent++;
427
430
 
428
431
  // Generate reactive prop accessors — each prop is accessed through __props getter
429
432
  // This ensures parent signal changes propagate reactively to the child
430
433
  if (hasParams) {
431
- for (const p of comp.params) {
432
- this.computedNames.add(p.name);
433
- const def = p.default || p.defaultValue;
434
+ for (const param of comp.params) {
435
+ this.computedNames.add(param.name);
436
+ const def = param.default || param.defaultValue;
434
437
  if (def) {
435
438
  const defaultExpr = this.genExpression(def);
436
- code += `${this.i()}const ${p.name} = () => __props.${p.name} !== undefined ? __props.${p.name} : ${defaultExpr};\n`;
439
+ p.push(`${this.i()}const ${param.name} = () => __props.${param.name} !== undefined ? __props.${param.name} : ${defaultExpr};\n`);
437
440
  } else {
438
- code += `${this.i()}const ${p.name} = () => __props.${p.name};\n`;
441
+ p.push(`${this.i()}const ${param.name} = () => __props.${param.name};\n`);
439
442
  }
440
443
  }
441
444
  }
@@ -446,7 +449,7 @@ export class ClientCodegen extends BaseCodegen {
446
449
  const bodyItems = [];
447
450
 
448
451
  for (const node of comp.body) {
449
- if (node.type === 'JSXElement' || node.type === 'JSXFor' || node.type === 'JSXIf') {
452
+ if (node.type === 'JSXElement' || node.type === 'JSXFragment' || node.type === 'JSXFor' || node.type === 'JSXIf') {
450
453
  jsxElements.push(node);
451
454
  } else if (node.type === 'ComponentStyleBlock') {
452
455
  styleBlocks.push(node);
@@ -462,7 +465,7 @@ export class ClientCodegen extends BaseCodegen {
462
465
  const scopeId = this._genScopeId(comp.name, rawCSS);
463
466
  this._currentScopeId = scopeId;
464
467
  const scopedCSS = this._scopeCSS(rawCSS, `[data-tova-${scopeId}]`);
465
- code += `${this.i()}tova_inject_css(${JSON.stringify(scopeId)}, ${JSON.stringify(scopedCSS)});\n`;
468
+ p.push(`${this.i()}tova_inject_css(${JSON.stringify(scopeId)}, ${JSON.stringify(scopedCSS)});\n`);
466
469
  }
467
470
 
468
471
  // Generate body items in order (state, computed, effect, other statements)
@@ -470,38 +473,38 @@ export class ClientCodegen extends BaseCodegen {
470
473
  if (node.type === 'StateDeclaration') {
471
474
  this.stateNames.add(node.name);
472
475
  const init = this.genExpression(node.initialValue);
473
- code += `${this.i()}const [${node.name}, set${capitalize(node.name)}] = createSignal(${init});\n`;
476
+ p.push(`${this.i()}const [${node.name}, set${capitalize(node.name)}] = createSignal(${init});\n`);
474
477
  } else if (node.type === 'ComputedDeclaration') {
475
478
  this.computedNames.add(node.name);
476
479
  const expr = this.genExpression(node.expression);
477
- code += `${this.i()}const ${node.name} = createComputed(() => ${expr});\n`;
480
+ p.push(`${this.i()}const ${node.name} = createComputed(() => ${expr});\n`);
478
481
  } else if (node.type === 'EffectDeclaration') {
479
482
  this.indent++;
480
483
  const effectCode = this._generateEffect(node.body);
481
484
  this.indent--;
482
- code += `${this.i()}${effectCode}\n`;
485
+ p.push(`${this.i()}${effectCode}\n`);
483
486
  } else {
484
- code += this.generateStatement(node) + '\n';
487
+ p.push(this.generateStatement(node) + '\n');
485
488
  }
486
489
  }
487
490
 
488
491
  // Generate JSX return
489
492
  if (jsxElements.length === 1) {
490
- code += `${this.i()}return ${this.genJSX(jsxElements[0])};\n`;
493
+ p.push(`${this.i()}return ${this.genJSX(jsxElements[0])};\n`);
491
494
  } else if (jsxElements.length > 1) {
492
495
  const children = jsxElements.map(el => this.genJSX(el)).join(', ');
493
- code += `${this.i()}return tova_fragment([${children}]);\n`;
496
+ p.push(`${this.i()}return tova_fragment([${children}]);\n`);
494
497
  }
495
498
 
496
499
  this.indent--;
497
- code += `}`;
500
+ p.push(`}`);
498
501
 
499
502
  // Restore scoped names and scope id
500
503
  this.stateNames = savedState;
501
504
  this.computedNames = savedComputed;
502
505
  this._currentScopeId = savedScopeId;
503
506
 
504
- return code;
507
+ return p.join('');
505
508
  }
506
509
 
507
510
  generateStore(store) {
@@ -526,54 +529,55 @@ export class ClientCodegen extends BaseCodegen {
526
529
  }
527
530
  }
528
531
 
529
- let code = `const ${store.name} = (() => {\n`;
532
+ const p = [];
533
+ p.push(`const ${store.name} = (() => {\n`);
530
534
  this.indent++;
531
535
 
532
536
  // Generate state signals
533
537
  for (const s of storeStates) {
534
538
  const init = this.genExpression(s.initialValue);
535
- code += `${this.i()}const [${s.name}, set${capitalize(s.name)}] = createSignal(${init});\n`;
539
+ p.push(`${this.i()}const [${s.name}, set${capitalize(s.name)}] = createSignal(${init});\n`);
536
540
  }
537
541
 
538
542
  // Generate computed values
539
543
  for (const c of storeComputeds) {
540
544
  const expr = this.genExpression(c.expression);
541
- code += `${this.i()}const ${c.name} = createComputed(() => ${expr});\n`;
545
+ p.push(`${this.i()}const ${c.name} = createComputed(() => ${expr});\n`);
542
546
  }
543
547
 
544
548
  // Generate functions
545
549
  for (const fn of storeFunctions) {
546
- code += this.genFunctionDeclaration(fn) + '\n';
550
+ p.push(this.genFunctionDeclaration(fn) + '\n');
547
551
  }
548
552
 
549
553
  // Build return object with getters/setters
550
- code += `${this.i()}return {\n`;
554
+ p.push(`${this.i()}return {\n`);
551
555
  this.indent++;
552
556
 
553
557
  for (const s of storeStates) {
554
- code += `${this.i()}get ${s.name}() { return ${s.name}(); },\n`;
555
- code += `${this.i()}set ${s.name}(v) { set${capitalize(s.name)}(v); },\n`;
558
+ p.push(`${this.i()}get ${s.name}() { return ${s.name}(); },\n`);
559
+ p.push(`${this.i()}set ${s.name}(v) { set${capitalize(s.name)}(v); },\n`);
556
560
  }
557
561
 
558
562
  for (const c of storeComputeds) {
559
- code += `${this.i()}get ${c.name}() { return ${c.name}(); },\n`;
563
+ p.push(`${this.i()}get ${c.name}() { return ${c.name}(); },\n`);
560
564
  }
561
565
 
562
566
  for (const fn of storeFunctions) {
563
- code += `${this.i()}${fn.name},\n`;
567
+ p.push(`${this.i()}${fn.name},\n`);
564
568
  }
565
569
 
566
570
  this.indent--;
567
- code += `${this.i()}};\n`;
571
+ p.push(`${this.i()}};\n`);
568
572
 
569
573
  this.indent--;
570
- code += `${this.i()}})();`;
574
+ p.push(`${this.i()}})();`);
571
575
 
572
576
  // Restore state/computed names
573
577
  this.stateNames = savedState;
574
578
  this.computedNames = savedComputed;
575
579
 
576
- return code;
580
+ return p.join('');
577
581
  }
578
582
 
579
583
  // Check if an AST expression references any signal/computed name
@@ -602,6 +606,7 @@ export class ClientCodegen extends BaseCodegen {
602
606
  if (node.type === 'ObjectLiteral') return node.properties.some(p => this._exprReadsSignal(p.value));
603
607
  if (node.type === 'IfExpression') {
604
608
  return this._exprReadsSignal(node.condition) || this._exprReadsSignal(node.consequent) ||
609
+ (node.alternates && node.alternates.some(a => this._exprReadsSignal(a.condition) || this._exprReadsSignal(a.body))) ||
605
610
  this._exprReadsSignal(node.elseBody);
606
611
  }
607
612
  if (node.type === 'MatchExpression') {
@@ -623,6 +628,7 @@ export class ClientCodegen extends BaseCodegen {
623
628
 
624
629
  switch (node.type) {
625
630
  case 'JSXElement': return this.genJSXElement(node);
631
+ case 'JSXFragment': return this.genJSXFragment(node);
626
632
  case 'JSXText': return this.genJSXText(node);
627
633
  case 'JSXExpression': {
628
634
  // If expression reads a signal, wrap as () => expr for fine-grained reactivity
@@ -702,10 +708,24 @@ export class ClientCodegen extends BaseCodegen {
702
708
  events.change = `(e) => { set${capitalize(exprName)}(${valueExpr}); }`;
703
709
  }
704
710
  }
711
+ } else if (attr.name === 'show') {
712
+ // show={condition} → toggles display:none instead of removing from DOM
713
+ const expr = this.genExpression(attr.value);
714
+ const reactive = this._exprReadsSignal(attr.value);
715
+ const displayExpr = `(${expr}) ? "" : "none"`;
716
+ // Store show directive to merge with style later
717
+ node._showDirective = { expr: displayExpr, reactive };
705
718
  } else if (attr.name.startsWith('class:')) {
706
719
  // Conditional class: class:active={cond}
707
720
  const className = attr.name.slice(6);
708
721
  classDirectives.push({ className, condition: this.genExpression(attr.value), node: attr.value });
722
+ } else if (attr.name.startsWith('transition:')) {
723
+ // transition:fade, transition:slide={duration: 300}, etc.
724
+ const transName = attr.name.slice(11); // 'fade', 'slide', 'scale', 'fly'
725
+ const config = attr.value.type === 'BooleanLiteral' ? '{}' : this.genExpression(attr.value);
726
+ // Store transition info for element wrapping
727
+ if (!node._transitions) node._transitions = [];
728
+ node._transitions.push({ name: transName, config });
709
729
  } else if (attr.name.startsWith('on:')) {
710
730
  const eventName = attr.name.slice(3);
711
731
  events[eventName] = this.genExpression(attr.value);
@@ -731,6 +751,24 @@ export class ClientCodegen extends BaseCodegen {
731
751
  attrs.className = isReactive ? `() => ${classExpr}` : classExpr;
732
752
  }
733
753
 
754
+ // Merge show directive with style (show toggles display:none)
755
+ if (node._showDirective) {
756
+ const { expr: displayExpr, reactive } = node._showDirective;
757
+ if (attrs.style) {
758
+ // Merge with existing style object
759
+ const existing = attrs.style;
760
+ if (reactive) {
761
+ attrs.style = `() => Object.assign({}, ${existing}, { display: ${displayExpr} })`;
762
+ } else {
763
+ attrs.style = `Object.assign({}, ${existing}, { display: ${displayExpr} })`;
764
+ }
765
+ } else {
766
+ attrs.style = reactive
767
+ ? `() => ({ display: ${displayExpr} })`
768
+ : `{ display: ${displayExpr} }`;
769
+ }
770
+ }
771
+
734
772
  // Add scoped CSS attribute to HTML elements (not components)
735
773
  if (this._currentScopeId && !isComponent) {
736
774
  attrs[`"data-tova-${this._currentScopeId}"`] = '""';
@@ -802,12 +840,22 @@ export class ClientCodegen extends BaseCodegen {
802
840
 
803
841
  const tag = JSON.stringify(node.tag);
804
842
 
843
+ let result;
805
844
  if (node.selfClosing || node.children.length === 0) {
806
- return `tova_el(${tag}, ${propsStr})`;
845
+ result = `tova_el(${tag}, ${propsStr})`;
846
+ } else {
847
+ const children = node.children.map(c => this.genJSX(c)).join(', ');
848
+ result = `tova_el(${tag}, ${propsStr}, [${children}])`;
807
849
  }
808
850
 
809
- const children = node.children.map(c => this.genJSX(c)).join(', ');
810
- return `tova_el(${tag}, ${propsStr}, [${children}])`;
851
+ // Wrap with transition directives if present
852
+ if (node._transitions && node._transitions.length > 0) {
853
+ for (const t of node._transitions) {
854
+ result = `tova_transition(${result}, "${t.name}", ${t.config})`;
855
+ }
856
+ }
857
+
858
+ return result;
811
859
  }
812
860
 
813
861
  genJSXText(node) {
@@ -876,6 +924,21 @@ export class ClientCodegen extends BaseCodegen {
876
924
  return `() => ${result}`;
877
925
  }
878
926
 
927
+ genJSXFragment(node) {
928
+ const children = node.children.map(c => this.genJSX(c)).join(', ');
929
+ return `tova_fragment([${children}])`;
930
+ }
931
+
932
+ // Override to add await for piped RPC calls
933
+ genPipeExpression(node) {
934
+ const result = super.genPipeExpression(node);
935
+ // If the pipe target is an RPC call and we're in async context, wrap with await
936
+ if (this._asyncContext && this._containsRPC(node.right)) {
937
+ return `await ${result}`;
938
+ }
939
+ return result;
940
+ }
941
+
879
942
  // Override function declaration to make async if it contains server.* calls
880
943
  genFunctionDeclaration(node) {
881
944
  const hasRPC = this._containsRPC(node.body);
@@ -3,10 +3,28 @@
3
3
  // Blocks with the same name are merged; different names produce separate output files.
4
4
 
5
5
  import { SharedCodegen } from './shared-codegen.js';
6
- import { ServerCodegen } from './server-codegen.js';
7
- import { ClientCodegen } from './client-codegen.js';
8
6
  import { BUILTIN_NAMES } from '../stdlib/inline.js';
9
7
 
8
+ // Lazy-loaded codegen modules — only imported when server/client blocks exist
9
+ let _ServerCodegen = null;
10
+ let _ClientCodegen = null;
11
+
12
+ function getServerCodegen() {
13
+ if (!_ServerCodegen) {
14
+ // Dynamic require avoids loading server-codegen.js for client-only builds
15
+ _ServerCodegen = import.meta.require('./server-codegen.js').ServerCodegen;
16
+ }
17
+ return _ServerCodegen;
18
+ }
19
+
20
+ function getClientCodegen() {
21
+ if (!_ClientCodegen) {
22
+ // Dynamic require avoids loading client-codegen.js for server-only builds
23
+ _ClientCodegen = import.meta.require('./client-codegen.js').ClientCodegen;
24
+ }
25
+ return _ClientCodegen;
26
+ }
27
+
10
28
  export class CodeGenerator {
11
29
  constructor(ast, filename = '<stdin>') {
12
30
  this.ast = ast;
@@ -31,6 +49,7 @@ export class CodeGenerator {
31
49
  const topLevel = [];
32
50
 
33
51
  const testBlocks = [];
52
+ const benchBlocks = [];
34
53
  const dataBlocks = [];
35
54
 
36
55
  for (const node of this.ast.body) {
@@ -39,12 +58,36 @@ export class CodeGenerator {
39
58
  case 'ServerBlock': serverBlocks.push(node); break;
40
59
  case 'ClientBlock': clientBlocks.push(node); break;
41
60
  case 'TestBlock': testBlocks.push(node); break;
61
+ case 'BenchBlock': benchBlocks.push(node); break;
42
62
  case 'DataBlock': dataBlocks.push(node); break;
43
63
  default: topLevel.push(node); break;
44
64
  }
45
65
  }
46
66
 
67
+ // Detect module mode: no blocks, only top-level statements
68
+ const isModule = sharedBlocks.length === 0 && serverBlocks.length === 0
69
+ && clientBlocks.length === 0 && testBlocks.length === 0
70
+ && benchBlocks.length === 0 && dataBlocks.length === 0
71
+ && topLevel.length > 0;
72
+
73
+ if (isModule) {
74
+ const moduleGen = new SharedCodegen();
75
+ moduleGen.setSourceFile(this.filename);
76
+ const moduleCode = topLevel.map(s => moduleGen.generateStatement(s)).join('\n');
77
+ const helpers = moduleGen.generateHelpers();
78
+ const combined = [helpers, moduleCode].filter(s => s.trim()).join('\n').trim();
79
+ return {
80
+ shared: combined,
81
+ server: '',
82
+ client: '',
83
+ isModule: true,
84
+ sourceMappings: moduleGen.getSourceMappings(),
85
+ _sourceFile: this.filename,
86
+ };
87
+ }
88
+
47
89
  const sharedGen = new SharedCodegen();
90
+ sharedGen.setSourceFile(this.filename);
48
91
 
49
92
  // All shared blocks (regardless of name) are merged into one shared output
50
93
  const sharedCode = sharedBlocks.map(b => sharedGen.generate(b)).join('\n');
@@ -90,7 +133,7 @@ export class CodeGenerator {
90
133
  // Generate server outputs (one per named group)
91
134
  const servers = {};
92
135
  for (const [name, blocks] of serverGroups) {
93
- const gen = new ServerCodegen();
136
+ const gen = new (getServerCodegen())();
94
137
  const key = name || 'default';
95
138
  // Build peer blocks map (all named blocks except self)
96
139
  let peerBlocks = null;
@@ -108,7 +151,7 @@ export class CodeGenerator {
108
151
  // Generate client outputs (one per named group)
109
152
  const clients = {};
110
153
  for (const [name, blocks] of clientGroups) {
111
- const gen = new ClientCodegen();
154
+ const gen = new (getClientCodegen())();
112
155
  const key = name || 'default';
113
156
  clients[key] = gen.generate(blocks, combinedShared, sharedGen._usedBuiltins);
114
157
  }
@@ -116,7 +159,7 @@ export class CodeGenerator {
116
159
  // Generate tests if test blocks exist
117
160
  let testCode = '';
118
161
  if (testBlocks.length > 0) {
119
- const testGen = new ServerCodegen();
162
+ const testGen = new (getServerCodegen())();
120
163
  testCode = testGen.generateTests(testBlocks);
121
164
 
122
165
  // Add __handleRequest export to server code
@@ -126,6 +169,13 @@ export class CodeGenerator {
126
169
  }
127
170
  }
128
171
 
172
+ // Generate benchmarks if bench blocks exist
173
+ let benchCode = '';
174
+ if (benchBlocks.length > 0) {
175
+ const benchGen = new (getServerCodegen())();
176
+ benchCode = benchGen.generateBench(benchBlocks);
177
+ }
178
+
129
179
  // Backward-compatible: if only unnamed blocks, return flat structure
130
180
  const hasNamedBlocks = [...serverGroups.keys(), ...clientGroups.keys()].some(k => k !== null);
131
181
 
@@ -138,8 +188,10 @@ export class CodeGenerator {
138
188
  server: servers['default'] || '',
139
189
  client: clients['default'] || '',
140
190
  sourceMappings,
191
+ _sourceFile: this.filename,
141
192
  };
142
193
  if (testCode) result.test = testCode;
194
+ if (benchCode) result.bench = benchCode;
143
195
  return result;
144
196
  }
145
197
 
@@ -152,8 +204,10 @@ export class CodeGenerator {
152
204
  clients, // { "admin": code, "dashboard": code, ... }
153
205
  multiBlock: true,
154
206
  sourceMappings,
207
+ _sourceFile: this.filename,
155
208
  };
156
209
  if (testCode) result.test = testCode;
210
+ if (benchCode) result.bench = benchCode;
157
211
  return result;
158
212
  }
159
213
 
@@ -167,6 +221,12 @@ export class CodeGenerator {
167
221
  if (node.type === 'CallExpression' && node.callee && node.callee.type === 'Identifier' && BUILTIN_NAMES.has(node.callee.name)) {
168
222
  targetSet.add(node.callee.name);
169
223
  }
224
+ // Track namespace builtin usage: math.sin() or math.PI
225
+ if (node.type === 'MemberExpression' &&
226
+ node.object.type === 'Identifier' &&
227
+ BUILTIN_NAMES.has(node.object.name)) {
228
+ targetSet.add(node.object.name);
229
+ }
170
230
  for (const key of Object.keys(node)) {
171
231
  if (key === 'loc' || key === 'type') continue;
172
232
  const val = node[key];