jslike 1.5.0 → 1.6.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.
@@ -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
  }
@@ -2234,4 +2270,253 @@ export class Interpreter {
2234
2270
  // Already handled in evaluateObjectExpression
2235
2271
  return undefined;
2236
2272
  }
2273
+
2274
+ // ===== JSX Support =====
2275
+
2276
+ evaluateJSXElement(node, env) {
2277
+ const createElement = this.getCreateElement(env);
2278
+ const { type, props } = this.evaluateJSXOpeningElement(node.openingElement, env);
2279
+ const children = this.evaluateJSXChildren(node.children, env);
2280
+
2281
+ if (children.length === 0) {
2282
+ return createElement(type, props);
2283
+ } else if (children.length === 1) {
2284
+ return createElement(type, props, children[0]);
2285
+ }
2286
+ return createElement(type, props, ...children);
2287
+ }
2288
+
2289
+ evaluateJSXFragment(node, env) {
2290
+ const createElement = this.getCreateElement(env);
2291
+ const Fragment = this.getFragment(env);
2292
+ const children = this.evaluateJSXChildren(node.children, env);
2293
+
2294
+ if (children.length === 0) {
2295
+ return createElement(Fragment, null);
2296
+ } else if (children.length === 1) {
2297
+ return createElement(Fragment, null, children[0]);
2298
+ }
2299
+ return createElement(Fragment, null, ...children);
2300
+ }
2301
+
2302
+ evaluateJSXOpeningElement(node, env) {
2303
+ const type = this.evaluateJSXElementName(node.name, env);
2304
+ const props = {};
2305
+
2306
+ for (const attr of node.attributes) {
2307
+ if (attr.type === 'JSXAttribute') {
2308
+ const name = attr.name.type === 'JSXIdentifier'
2309
+ ? attr.name.name
2310
+ : `${attr.name.namespace.name}:${attr.name.name.name}`;
2311
+ const value = attr.value
2312
+ ? this.evaluateJSXAttributeValue(attr.value, env)
2313
+ : true;
2314
+ props[name] = value;
2315
+ } else if (attr.type === 'JSXSpreadAttribute') {
2316
+ Object.assign(props, this.evaluate(attr.argument, env));
2317
+ }
2318
+ }
2319
+
2320
+ return { type, props: Object.keys(props).length > 0 ? props : null };
2321
+ }
2322
+
2323
+ evaluateJSXElementName(node, env) {
2324
+ if (node.type === 'JSXIdentifier') {
2325
+ const name = node.name;
2326
+ // Lowercase = intrinsic ('div'), Uppercase = component
2327
+ if (name[0] === name[0].toLowerCase()) {
2328
+ return name;
2329
+ }
2330
+ return env.get(name);
2331
+ } else if (node.type === 'JSXMemberExpression') {
2332
+ const object = this.evaluateJSXElementName(node.object, env);
2333
+ return object[node.property.name];
2334
+ } else if (node.type === 'JSXNamespacedName') {
2335
+ return `${node.namespace.name}:${node.name.name}`;
2336
+ }
2337
+ throw new Error(`Unknown JSX element name type: ${node.type}`);
2338
+ }
2339
+
2340
+ evaluateJSXAttributeValue(node, env) {
2341
+ if (node.type === 'Literal') return node.value;
2342
+ if (node.type === 'JSXExpressionContainer') {
2343
+ return this.evaluate(node.expression, env);
2344
+ }
2345
+ if (node.type === 'JSXElement') return this.evaluateJSXElement(node, env);
2346
+ if (node.type === 'JSXFragment') return this.evaluateJSXFragment(node, env);
2347
+ throw new Error(`Unknown JSX attribute value type: ${node.type}`);
2348
+ }
2349
+
2350
+ evaluateJSXChildren(children, env) {
2351
+ const result = [];
2352
+ for (const child of children) {
2353
+ if (child.type === 'JSXText') {
2354
+ const text = this.normalizeJSXText(child.value);
2355
+ if (text) result.push(text);
2356
+ } else if (child.type === 'JSXExpressionContainer') {
2357
+ if (child.expression.type !== 'JSXEmptyExpression') {
2358
+ const value = this.evaluate(child.expression, env);
2359
+ if (Array.isArray(value)) {
2360
+ result.push(...value);
2361
+ } else if (value !== null && value !== undefined && value !== false) {
2362
+ result.push(value);
2363
+ }
2364
+ }
2365
+ } else if (child.type === 'JSXElement') {
2366
+ result.push(this.evaluateJSXElement(child, env));
2367
+ } else if (child.type === 'JSXFragment') {
2368
+ result.push(this.evaluateJSXFragment(child, env));
2369
+ }
2370
+ }
2371
+ return result;
2372
+ }
2373
+
2374
+ normalizeJSXText(text) {
2375
+ // React's JSX whitespace normalization
2376
+ const lines = text.split('\n');
2377
+ const normalized = lines
2378
+ .map((line, i) => {
2379
+ let result = line;
2380
+ if (i === 0) result = result.trimStart();
2381
+ if (i === lines.length - 1) result = result.trimEnd();
2382
+ return result;
2383
+ })
2384
+ .filter(line => line.length > 0)
2385
+ .join(' ');
2386
+ return normalized || null;
2387
+ }
2388
+
2389
+ getCreateElement(env) {
2390
+ // Try React.createElement first
2391
+ try {
2392
+ const React = env.get('React');
2393
+ if (React && React.createElement) {
2394
+ return React.createElement.bind(React);
2395
+ }
2396
+ } catch (e) { /* not defined */ }
2397
+
2398
+ // Try standalone createElement
2399
+ try {
2400
+ return env.get('createElement');
2401
+ } catch (e) { /* not defined */ }
2402
+
2403
+ // Fallback: simple element factory for non-React usage
2404
+ return (type, props, ...children) => ({
2405
+ $$typeof: Symbol.for('react.element'),
2406
+ type,
2407
+ props: {
2408
+ ...props,
2409
+ children: children.length === 0 ? undefined : children.length === 1 ? children[0] : children
2410
+ },
2411
+ key: props?.key ?? null,
2412
+ ref: props?.ref ?? null
2413
+ });
2414
+ }
2415
+
2416
+ getFragment(env) {
2417
+ // Try React.Fragment
2418
+ try {
2419
+ const React = env.get('React');
2420
+ if (React && React.Fragment) {
2421
+ return React.Fragment;
2422
+ }
2423
+ } catch (e) { /* not defined */ }
2424
+
2425
+ // Try standalone Fragment
2426
+ try {
2427
+ return env.get('Fragment');
2428
+ } catch (e) { /* not defined */ }
2429
+
2430
+ // Fallback: Symbol for fragments
2431
+ return Symbol.for('react.fragment');
2432
+ }
2433
+
2434
+ // ===== Async JSX Support =====
2435
+
2436
+ async evaluateJSXElementAsync(node, env) {
2437
+ const checkpointPromise = this._getCheckpointPromise(node, env);
2438
+ if (checkpointPromise) await checkpointPromise;
2439
+
2440
+ const createElement = this.getCreateElement(env);
2441
+ const { type, props } = await this.evaluateJSXOpeningElementAsync(node.openingElement, env);
2442
+ const children = await this.evaluateJSXChildrenAsync(node.children, env);
2443
+
2444
+ if (children.length === 0) {
2445
+ return createElement(type, props);
2446
+ } else if (children.length === 1) {
2447
+ return createElement(type, props, children[0]);
2448
+ }
2449
+ return createElement(type, props, ...children);
2450
+ }
2451
+
2452
+ async evaluateJSXFragmentAsync(node, env) {
2453
+ const checkpointPromise = this._getCheckpointPromise(node, env);
2454
+ if (checkpointPromise) await checkpointPromise;
2455
+
2456
+ const createElement = this.getCreateElement(env);
2457
+ const Fragment = this.getFragment(env);
2458
+ const children = await this.evaluateJSXChildrenAsync(node.children, env);
2459
+
2460
+ if (children.length === 0) {
2461
+ return createElement(Fragment, null);
2462
+ } else if (children.length === 1) {
2463
+ return createElement(Fragment, null, children[0]);
2464
+ }
2465
+ return createElement(Fragment, null, ...children);
2466
+ }
2467
+
2468
+ async evaluateJSXOpeningElementAsync(node, env) {
2469
+ const type = this.evaluateJSXElementName(node.name, env);
2470
+ const props = {};
2471
+
2472
+ for (const attr of node.attributes) {
2473
+ if (attr.type === 'JSXAttribute') {
2474
+ const name = attr.name.type === 'JSXIdentifier'
2475
+ ? attr.name.name
2476
+ : `${attr.name.namespace.name}:${attr.name.name.name}`;
2477
+ const value = attr.value
2478
+ ? await this.evaluateJSXAttributeValueAsync(attr.value, env)
2479
+ : true;
2480
+ props[name] = value;
2481
+ } else if (attr.type === 'JSXSpreadAttribute') {
2482
+ Object.assign(props, await this.evaluateAsync(attr.argument, env));
2483
+ }
2484
+ }
2485
+
2486
+ return { type, props: Object.keys(props).length > 0 ? props : null };
2487
+ }
2488
+
2489
+ async evaluateJSXAttributeValueAsync(node, env) {
2490
+ if (node.type === 'Literal') return node.value;
2491
+ if (node.type === 'JSXExpressionContainer') {
2492
+ return await this.evaluateAsync(node.expression, env);
2493
+ }
2494
+ if (node.type === 'JSXElement') return await this.evaluateJSXElementAsync(node, env);
2495
+ if (node.type === 'JSXFragment') return await this.evaluateJSXFragmentAsync(node, env);
2496
+ throw new Error(`Unknown JSX attribute value type: ${node.type}`);
2497
+ }
2498
+
2499
+ async evaluateJSXChildrenAsync(children, env) {
2500
+ const result = [];
2501
+ for (const child of children) {
2502
+ if (child.type === 'JSXText') {
2503
+ const text = this.normalizeJSXText(child.value);
2504
+ if (text) result.push(text);
2505
+ } else if (child.type === 'JSXExpressionContainer') {
2506
+ if (child.expression.type !== 'JSXEmptyExpression') {
2507
+ const value = await this.evaluateAsync(child.expression, env);
2508
+ if (Array.isArray(value)) {
2509
+ result.push(...value);
2510
+ } else if (value !== null && value !== undefined && value !== false) {
2511
+ result.push(value);
2512
+ }
2513
+ }
2514
+ } else if (child.type === 'JSXElement') {
2515
+ result.push(await this.evaluateJSXElementAsync(child, env));
2516
+ } else if (child.type === 'JSXFragment') {
2517
+ result.push(await this.evaluateJSXFragmentAsync(child, env));
2518
+ }
2519
+ }
2520
+ return result;
2521
+ }
2237
2522
  }