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.
- package/dist/esm/interpreter/interpreter.js +285 -0
- package/dist/esm/parser.js +781 -3
- package/dist/esm/runtime/builtins.js +14 -0
- package/dist/index.cjs +913 -7
- package/dist/index.d.cts +1044 -11
- package/dist/index.d.ts +1044 -11
- package/dist/index.js +913 -7
- package/dist/validator/index.cjs +669 -5
- package/dist/validator/index.js +669 -5
- package/package.json +5 -2
|
@@ -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
|
}
|