jslike 1.5.0 → 1.6.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.
@@ -636,6 +636,26 @@ export class Interpreter {
636
636
  return this.evaluateClassExpression(node, env);
637
637
  }
638
638
 
639
+ // JSX Support (async)
640
+ if (node.type === 'JSXElement') {
641
+ return await this.evaluateJSXElementAsync(node, env);
642
+ }
643
+
644
+ if (node.type === 'JSXFragment') {
645
+ return await this.evaluateJSXFragmentAsync(node, env);
646
+ }
647
+
648
+ if (node.type === 'JSXExpressionContainer') {
649
+ if (node.expression.type === 'JSXEmptyExpression') {
650
+ return undefined;
651
+ }
652
+ return await this.evaluateAsync(node.expression, env);
653
+ }
654
+
655
+ if (node.type === 'JSXText') {
656
+ return this.normalizeJSXText(node.value);
657
+ }
658
+
639
659
  // Only leaf nodes should fall through to sync evaluate
640
660
  // These have no sub-expressions that could contain await
641
661
  if (['Literal', 'Identifier', 'BreakStatement', 'ContinueStatement',
@@ -810,6 +830,22 @@ export class Interpreter {
810
830
  case 'Property':
811
831
  return this.evaluateProperty(node, env);
812
832
 
833
+ // JSX Support
834
+ case 'JSXElement':
835
+ return this.evaluateJSXElement(node, env);
836
+
837
+ case 'JSXFragment':
838
+ return this.evaluateJSXFragment(node, env);
839
+
840
+ case 'JSXExpressionContainer':
841
+ if (node.expression.type === 'JSXEmptyExpression') {
842
+ return undefined;
843
+ }
844
+ return this.evaluate(node.expression, env);
845
+
846
+ case 'JSXText':
847
+ return this.normalizeJSXText(node.value);
848
+
813
849
  default:
814
850
  throw new Error(`Unknown node type: ${node.type}`);
815
851
  }
@@ -1635,29 +1671,36 @@ export class Interpreter {
1635
1671
  throw new Error(`Cannot find module '${modulePath}'`);
1636
1672
  }
1637
1673
 
1638
- // Handle both old (string) and new (ModuleResolution) formats
1639
- const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1640
-
1641
- // Parse and execute module in its own environment
1642
- const moduleAst = acornParse(moduleCode, {
1643
- ecmaVersion: 2020,
1644
- sourceType: 'module',
1645
- locations: false
1646
- });
1647
- const moduleEnv = new Environment(this.globalEnv);
1674
+ // Handle native module exports (for libraries like React)
1675
+ // If resolution has 'exports' property, use it directly without parsing
1676
+ if (resolution.exports) {
1677
+ moduleExports = resolution.exports;
1678
+ this.moduleCache.set(modulePath, moduleExports);
1679
+ } else {
1680
+ // Handle both old (string) and new (ModuleResolution) formats
1681
+ const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1682
+
1683
+ // Parse and execute module in its own environment
1684
+ const moduleAst = acornParse(moduleCode, {
1685
+ ecmaVersion: 2020,
1686
+ sourceType: 'module',
1687
+ locations: false
1688
+ });
1689
+ const moduleEnv = new Environment(this.globalEnv);
1648
1690
 
1649
- // Create a new interpreter for the module with shared module cache
1650
- const moduleInterpreter = new Interpreter(this.globalEnv, {
1651
- moduleResolver: this.moduleResolver
1652
- });
1653
- moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1691
+ // Create a new interpreter for the module with shared module cache
1692
+ const moduleInterpreter = new Interpreter(this.globalEnv, {
1693
+ moduleResolver: this.moduleResolver
1694
+ });
1695
+ moduleInterpreter.moduleCache = this.moduleCache; // Share cache
1654
1696
 
1655
- // Execute module and collect exports
1656
- await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
1697
+ // Execute module and collect exports
1698
+ await moduleInterpreter.evaluateAsync(moduleAst, moduleEnv);
1657
1699
 
1658
- // Cache the module exports
1659
- moduleExports = moduleInterpreter.moduleExports;
1660
- this.moduleCache.set(modulePath, moduleExports);
1700
+ // Cache the module exports
1701
+ moduleExports = moduleInterpreter.moduleExports;
1702
+ this.moduleCache.set(modulePath, moduleExports);
1703
+ }
1661
1704
  }
1662
1705
 
1663
1706
  // Import specified bindings into current environment
@@ -2234,4 +2277,253 @@ export class Interpreter {
2234
2277
  // Already handled in evaluateObjectExpression
2235
2278
  return undefined;
2236
2279
  }
2280
+
2281
+ // ===== JSX Support =====
2282
+
2283
+ evaluateJSXElement(node, env) {
2284
+ const createElement = this.getCreateElement(env);
2285
+ const { type, props } = this.evaluateJSXOpeningElement(node.openingElement, env);
2286
+ const children = this.evaluateJSXChildren(node.children, env);
2287
+
2288
+ if (children.length === 0) {
2289
+ return createElement(type, props);
2290
+ } else if (children.length === 1) {
2291
+ return createElement(type, props, children[0]);
2292
+ }
2293
+ return createElement(type, props, ...children);
2294
+ }
2295
+
2296
+ evaluateJSXFragment(node, env) {
2297
+ const createElement = this.getCreateElement(env);
2298
+ const Fragment = this.getFragment(env);
2299
+ const children = this.evaluateJSXChildren(node.children, env);
2300
+
2301
+ if (children.length === 0) {
2302
+ return createElement(Fragment, null);
2303
+ } else if (children.length === 1) {
2304
+ return createElement(Fragment, null, children[0]);
2305
+ }
2306
+ return createElement(Fragment, null, ...children);
2307
+ }
2308
+
2309
+ evaluateJSXOpeningElement(node, env) {
2310
+ const type = this.evaluateJSXElementName(node.name, env);
2311
+ const props = {};
2312
+
2313
+ for (const attr of node.attributes) {
2314
+ if (attr.type === 'JSXAttribute') {
2315
+ const name = attr.name.type === 'JSXIdentifier'
2316
+ ? attr.name.name
2317
+ : `${attr.name.namespace.name}:${attr.name.name.name}`;
2318
+ const value = attr.value
2319
+ ? this.evaluateJSXAttributeValue(attr.value, env)
2320
+ : true;
2321
+ props[name] = value;
2322
+ } else if (attr.type === 'JSXSpreadAttribute') {
2323
+ Object.assign(props, this.evaluate(attr.argument, env));
2324
+ }
2325
+ }
2326
+
2327
+ return { type, props: Object.keys(props).length > 0 ? props : null };
2328
+ }
2329
+
2330
+ evaluateJSXElementName(node, env) {
2331
+ if (node.type === 'JSXIdentifier') {
2332
+ const name = node.name;
2333
+ // Lowercase = intrinsic ('div'), Uppercase = component
2334
+ if (name[0] === name[0].toLowerCase()) {
2335
+ return name;
2336
+ }
2337
+ return env.get(name);
2338
+ } else if (node.type === 'JSXMemberExpression') {
2339
+ const object = this.evaluateJSXElementName(node.object, env);
2340
+ return object[node.property.name];
2341
+ } else if (node.type === 'JSXNamespacedName') {
2342
+ return `${node.namespace.name}:${node.name.name}`;
2343
+ }
2344
+ throw new Error(`Unknown JSX element name type: ${node.type}`);
2345
+ }
2346
+
2347
+ evaluateJSXAttributeValue(node, env) {
2348
+ if (node.type === 'Literal') return node.value;
2349
+ if (node.type === 'JSXExpressionContainer') {
2350
+ return this.evaluate(node.expression, env);
2351
+ }
2352
+ if (node.type === 'JSXElement') return this.evaluateJSXElement(node, env);
2353
+ if (node.type === 'JSXFragment') return this.evaluateJSXFragment(node, env);
2354
+ throw new Error(`Unknown JSX attribute value type: ${node.type}`);
2355
+ }
2356
+
2357
+ evaluateJSXChildren(children, env) {
2358
+ const result = [];
2359
+ for (const child of children) {
2360
+ if (child.type === 'JSXText') {
2361
+ const text = this.normalizeJSXText(child.value);
2362
+ if (text) result.push(text);
2363
+ } else if (child.type === 'JSXExpressionContainer') {
2364
+ if (child.expression.type !== 'JSXEmptyExpression') {
2365
+ const value = this.evaluate(child.expression, env);
2366
+ if (Array.isArray(value)) {
2367
+ result.push(...value);
2368
+ } else if (value !== null && value !== undefined && value !== false) {
2369
+ result.push(value);
2370
+ }
2371
+ }
2372
+ } else if (child.type === 'JSXElement') {
2373
+ result.push(this.evaluateJSXElement(child, env));
2374
+ } else if (child.type === 'JSXFragment') {
2375
+ result.push(this.evaluateJSXFragment(child, env));
2376
+ }
2377
+ }
2378
+ return result;
2379
+ }
2380
+
2381
+ normalizeJSXText(text) {
2382
+ // React's JSX whitespace normalization
2383
+ const lines = text.split('\n');
2384
+ const normalized = lines
2385
+ .map((line, i) => {
2386
+ let result = line;
2387
+ if (i === 0) result = result.trimStart();
2388
+ if (i === lines.length - 1) result = result.trimEnd();
2389
+ return result;
2390
+ })
2391
+ .filter(line => line.length > 0)
2392
+ .join(' ');
2393
+ return normalized || null;
2394
+ }
2395
+
2396
+ getCreateElement(env) {
2397
+ // Try React.createElement first
2398
+ try {
2399
+ const React = env.get('React');
2400
+ if (React && React.createElement) {
2401
+ return React.createElement.bind(React);
2402
+ }
2403
+ } catch (e) { /* not defined */ }
2404
+
2405
+ // Try standalone createElement
2406
+ try {
2407
+ return env.get('createElement');
2408
+ } catch (e) { /* not defined */ }
2409
+
2410
+ // Fallback: simple element factory for non-React usage
2411
+ return (type, props, ...children) => ({
2412
+ $$typeof: Symbol.for('react.element'),
2413
+ type,
2414
+ props: {
2415
+ ...props,
2416
+ children: children.length === 0 ? undefined : children.length === 1 ? children[0] : children
2417
+ },
2418
+ key: props?.key ?? null,
2419
+ ref: props?.ref ?? null
2420
+ });
2421
+ }
2422
+
2423
+ getFragment(env) {
2424
+ // Try React.Fragment
2425
+ try {
2426
+ const React = env.get('React');
2427
+ if (React && React.Fragment) {
2428
+ return React.Fragment;
2429
+ }
2430
+ } catch (e) { /* not defined */ }
2431
+
2432
+ // Try standalone Fragment
2433
+ try {
2434
+ return env.get('Fragment');
2435
+ } catch (e) { /* not defined */ }
2436
+
2437
+ // Fallback: Symbol for fragments
2438
+ return Symbol.for('react.fragment');
2439
+ }
2440
+
2441
+ // ===== Async JSX Support =====
2442
+
2443
+ async evaluateJSXElementAsync(node, env) {
2444
+ const checkpointPromise = this._getCheckpointPromise(node, env);
2445
+ if (checkpointPromise) await checkpointPromise;
2446
+
2447
+ const createElement = this.getCreateElement(env);
2448
+ const { type, props } = await this.evaluateJSXOpeningElementAsync(node.openingElement, env);
2449
+ const children = await this.evaluateJSXChildrenAsync(node.children, env);
2450
+
2451
+ if (children.length === 0) {
2452
+ return createElement(type, props);
2453
+ } else if (children.length === 1) {
2454
+ return createElement(type, props, children[0]);
2455
+ }
2456
+ return createElement(type, props, ...children);
2457
+ }
2458
+
2459
+ async evaluateJSXFragmentAsync(node, env) {
2460
+ const checkpointPromise = this._getCheckpointPromise(node, env);
2461
+ if (checkpointPromise) await checkpointPromise;
2462
+
2463
+ const createElement = this.getCreateElement(env);
2464
+ const Fragment = this.getFragment(env);
2465
+ const children = await this.evaluateJSXChildrenAsync(node.children, env);
2466
+
2467
+ if (children.length === 0) {
2468
+ return createElement(Fragment, null);
2469
+ } else if (children.length === 1) {
2470
+ return createElement(Fragment, null, children[0]);
2471
+ }
2472
+ return createElement(Fragment, null, ...children);
2473
+ }
2474
+
2475
+ async evaluateJSXOpeningElementAsync(node, env) {
2476
+ const type = this.evaluateJSXElementName(node.name, env);
2477
+ const props = {};
2478
+
2479
+ for (const attr of node.attributes) {
2480
+ if (attr.type === 'JSXAttribute') {
2481
+ const name = attr.name.type === 'JSXIdentifier'
2482
+ ? attr.name.name
2483
+ : `${attr.name.namespace.name}:${attr.name.name.name}`;
2484
+ const value = attr.value
2485
+ ? await this.evaluateJSXAttributeValueAsync(attr.value, env)
2486
+ : true;
2487
+ props[name] = value;
2488
+ } else if (attr.type === 'JSXSpreadAttribute') {
2489
+ Object.assign(props, await this.evaluateAsync(attr.argument, env));
2490
+ }
2491
+ }
2492
+
2493
+ return { type, props: Object.keys(props).length > 0 ? props : null };
2494
+ }
2495
+
2496
+ async evaluateJSXAttributeValueAsync(node, env) {
2497
+ if (node.type === 'Literal') return node.value;
2498
+ if (node.type === 'JSXExpressionContainer') {
2499
+ return await this.evaluateAsync(node.expression, env);
2500
+ }
2501
+ if (node.type === 'JSXElement') return await this.evaluateJSXElementAsync(node, env);
2502
+ if (node.type === 'JSXFragment') return await this.evaluateJSXFragmentAsync(node, env);
2503
+ throw new Error(`Unknown JSX attribute value type: ${node.type}`);
2504
+ }
2505
+
2506
+ async evaluateJSXChildrenAsync(children, env) {
2507
+ const result = [];
2508
+ for (const child of children) {
2509
+ if (child.type === 'JSXText') {
2510
+ const text = this.normalizeJSXText(child.value);
2511
+ if (text) result.push(text);
2512
+ } else if (child.type === 'JSXExpressionContainer') {
2513
+ if (child.expression.type !== 'JSXEmptyExpression') {
2514
+ const value = await this.evaluateAsync(child.expression, env);
2515
+ if (Array.isArray(value)) {
2516
+ result.push(...value);
2517
+ } else if (value !== null && value !== undefined && value !== false) {
2518
+ result.push(value);
2519
+ }
2520
+ }
2521
+ } else if (child.type === 'JSXElement') {
2522
+ result.push(await this.evaluateJSXElementAsync(child, env));
2523
+ } else if (child.type === 'JSXFragment') {
2524
+ result.push(await this.evaluateJSXFragmentAsync(child, env));
2525
+ }
2526
+ }
2527
+ return result;
2528
+ }
2237
2529
  }