jslike 1.4.5 → 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/index.js +51 -25
- package/dist/esm/interpreter/index.js +2 -1
- package/dist/esm/interpreter/interpreter.js +370 -26
- package/dist/esm/parser.js +781 -3
- package/dist/esm/runtime/builtins.js +14 -0
- package/dist/esm/runtime/execution-controller.js +212 -0
- package/dist/index.cjs +1199 -41
- package/dist/index.d.cts +1394 -63
- package/dist/index.d.ts +1394 -63
- package/dist/index.js +1198 -41
- package/dist/validator/index.cjs +669 -5
- package/dist/validator/index.js +669 -5
- package/package.json +5 -2
package/dist/esm/index.js
CHANGED
|
@@ -98,34 +98,59 @@ function containsTopLevelAwait(node) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export async function execute(code, env = null, options = {}) {
|
|
101
|
-
//
|
|
102
|
-
const
|
|
101
|
+
// Get execution controller if provided
|
|
102
|
+
const controller = options.executionController;
|
|
103
103
|
|
|
104
|
-
//
|
|
105
|
-
if (
|
|
106
|
-
|
|
104
|
+
// Mark execution as starting
|
|
105
|
+
if (controller) {
|
|
106
|
+
controller._start();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
109
|
+
try {
|
|
110
|
+
// Parse the code
|
|
111
|
+
const ast = parse(code, options);
|
|
112
|
+
|
|
113
|
+
// Create global environment if not provided
|
|
114
|
+
if (!env) {
|
|
115
|
+
env = createGlobalEnvironment(new Environment());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Create interpreter with module resolver, abort signal, and execution controller
|
|
119
|
+
const interpreter = new Interpreter(env, {
|
|
120
|
+
moduleResolver: options.moduleResolver,
|
|
121
|
+
abortSignal: options.abortSignal,
|
|
122
|
+
executionController: controller
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Use async evaluation if:
|
|
126
|
+
// 1. Explicitly requested module mode
|
|
127
|
+
// 2. AST contains import/export declarations
|
|
128
|
+
// 3. Code contains top-level await
|
|
129
|
+
// 4. Execution controller provided (needs async for pause/resume)
|
|
130
|
+
const needsAsync = options.sourceType === 'module' ||
|
|
131
|
+
containsModuleDeclarations(ast) ||
|
|
132
|
+
containsTopLevelAwait(ast) ||
|
|
133
|
+
controller != null;
|
|
134
|
+
|
|
135
|
+
if (needsAsync) {
|
|
136
|
+
const result = await interpreter.evaluateAsync(ast, env);
|
|
137
|
+
if (controller) {
|
|
138
|
+
controller._complete();
|
|
139
|
+
}
|
|
140
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
141
|
+
} else {
|
|
142
|
+
const result = interpreter.evaluate(ast, env);
|
|
143
|
+
if (controller) {
|
|
144
|
+
controller._complete();
|
|
145
|
+
}
|
|
146
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Mark as aborted if that's the error type
|
|
150
|
+
if (controller && e.name === 'AbortError') {
|
|
151
|
+
controller.state = 'aborted';
|
|
152
|
+
}
|
|
153
|
+
throw e;
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
|
|
@@ -139,6 +164,7 @@ export const isTopLevelAwait = containsModuleSyntax;
|
|
|
139
164
|
export { Interpreter } from './interpreter/interpreter.js';
|
|
140
165
|
export { Environment } from './runtime/environment.js';
|
|
141
166
|
export { WangInterpreter, InMemoryModuleResolver } from './interpreter/index.js';
|
|
167
|
+
export { ExecutionController } from './runtime/execution-controller.js';
|
|
142
168
|
|
|
143
169
|
/**
|
|
144
170
|
* Abstract base class for module resolution
|
|
@@ -101,7 +101,8 @@ export class WangInterpreter {
|
|
|
101
101
|
|
|
102
102
|
// Prepare execution options
|
|
103
103
|
const options = {
|
|
104
|
-
moduleResolver: this.moduleResolver
|
|
104
|
+
moduleResolver: this.moduleResolver,
|
|
105
|
+
executionController: userOptions.executionController
|
|
105
106
|
// sourceType will be auto-detected from code
|
|
106
107
|
};
|
|
107
108
|
|
|
@@ -9,10 +9,17 @@ export class Interpreter {
|
|
|
9
9
|
this.moduleCache = new Map(); // Cache loaded modules
|
|
10
10
|
this.moduleExports = {}; // Track exports in current module
|
|
11
11
|
this.abortSignal = options.abortSignal;
|
|
12
|
+
this.executionController = options.executionController;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
// Check if execution should be aborted
|
|
15
|
+
// Check if execution should be aborted (sync version)
|
|
15
16
|
checkAbortSignal() {
|
|
17
|
+
// Check controller first if available
|
|
18
|
+
if (this.executionController) {
|
|
19
|
+
this.executionController._checkAbortSync();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Fall back to legacy abortSignal
|
|
16
23
|
if (this.abortSignal && this.abortSignal.aborted) {
|
|
17
24
|
const error = new Error('The operation was aborted');
|
|
18
25
|
error.name = 'AbortError';
|
|
@@ -20,12 +27,26 @@ export class Interpreter {
|
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
29
|
|
|
30
|
+
// Checkpoint that returns a promise only when controller is present
|
|
31
|
+
// When no controller, returns null to signal no await needed
|
|
32
|
+
_getCheckpointPromise(node, env) {
|
|
33
|
+
if (this.executionController) {
|
|
34
|
+
this.executionController._setEnv(env);
|
|
35
|
+
return this.executionController._checkpoint(node);
|
|
36
|
+
} else {
|
|
37
|
+
this.checkAbortSignal();
|
|
38
|
+
return null; // Signal that no await is needed
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
23
42
|
// Async evaluation for async functions - handles await expressions
|
|
24
43
|
async evaluateAsync(node, env) {
|
|
25
44
|
if (!node) return undefined;
|
|
26
45
|
|
|
27
|
-
//
|
|
28
|
-
|
|
46
|
+
// Checkpoint - yields if paused, throws if aborted
|
|
47
|
+
// Only await when there's actually a promise (controller present)
|
|
48
|
+
const checkpointPromise = this._getCheckpointPromise(node, env);
|
|
49
|
+
if (checkpointPromise) await checkpointPromise;
|
|
29
50
|
|
|
30
51
|
// Handle await expressions by actually awaiting the promise
|
|
31
52
|
if (node.type === 'AwaitExpression') {
|
|
@@ -262,6 +283,9 @@ export class Interpreter {
|
|
|
262
283
|
await this.evaluateAsync(node.init, forEnv);
|
|
263
284
|
}
|
|
264
285
|
while (!node.test || await this.evaluateAsync(node.test, forEnv)) {
|
|
286
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
287
|
+
const cp1 = this._getCheckpointPromise(node, forEnv);
|
|
288
|
+
if (cp1) await cp1;
|
|
265
289
|
const result = await this.evaluateAsync(node.body, forEnv);
|
|
266
290
|
if (result instanceof BreakSignal) {
|
|
267
291
|
break;
|
|
@@ -290,6 +314,9 @@ export class Interpreter {
|
|
|
290
314
|
const isConst = node.left.kind === 'const';
|
|
291
315
|
|
|
292
316
|
for (const value of iterable) {
|
|
317
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
318
|
+
const cp2 = this._getCheckpointPromise(node, forEnv);
|
|
319
|
+
if (cp2) await cp2;
|
|
293
320
|
const iterEnv = forEnv.extend();
|
|
294
321
|
if (declarator.id.type === 'Identifier') {
|
|
295
322
|
iterEnv.define(declarator.id.name, value, isConst);
|
|
@@ -323,6 +350,9 @@ export class Interpreter {
|
|
|
323
350
|
forEnv.define(varName, undefined);
|
|
324
351
|
|
|
325
352
|
for (const key in obj) {
|
|
353
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
354
|
+
const cp3 = this._getCheckpointPromise(node, forEnv);
|
|
355
|
+
if (cp3) await cp3;
|
|
326
356
|
forEnv.set(varName, key);
|
|
327
357
|
const result = await this.evaluateAsync(node.body, forEnv);
|
|
328
358
|
if (result instanceof BreakSignal) {
|
|
@@ -341,6 +371,9 @@ export class Interpreter {
|
|
|
341
371
|
// For WhileStatement with async body
|
|
342
372
|
if (node.type === 'WhileStatement') {
|
|
343
373
|
while (await this.evaluateAsync(node.test, env)) {
|
|
374
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
375
|
+
const cp4 = this._getCheckpointPromise(node, env);
|
|
376
|
+
if (cp4) await cp4;
|
|
344
377
|
const result = await this.evaluateAsync(node.body, env);
|
|
345
378
|
if (result instanceof BreakSignal) {
|
|
346
379
|
break;
|
|
@@ -358,6 +391,9 @@ export class Interpreter {
|
|
|
358
391
|
// For DoWhileStatement with async body
|
|
359
392
|
if (node.type === 'DoWhileStatement') {
|
|
360
393
|
do {
|
|
394
|
+
// Checkpoint at each loop iteration (only await if controller present)
|
|
395
|
+
const cp5 = this._getCheckpointPromise(node, env);
|
|
396
|
+
if (cp5) await cp5;
|
|
361
397
|
const result = await this.evaluateAsync(node.body, env);
|
|
362
398
|
if (result instanceof BreakSignal) {
|
|
363
399
|
break;
|
|
@@ -600,6 +636,26 @@ export class Interpreter {
|
|
|
600
636
|
return this.evaluateClassExpression(node, env);
|
|
601
637
|
}
|
|
602
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
|
+
|
|
603
659
|
// Only leaf nodes should fall through to sync evaluate
|
|
604
660
|
// These have no sub-expressions that could contain await
|
|
605
661
|
if (['Literal', 'Identifier', 'BreakStatement', 'ContinueStatement',
|
|
@@ -774,6 +830,22 @@ export class Interpreter {
|
|
|
774
830
|
case 'Property':
|
|
775
831
|
return this.evaluateProperty(node, env);
|
|
776
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
|
+
|
|
777
849
|
default:
|
|
778
850
|
throw new Error(`Unknown node type: ${node.type}`);
|
|
779
851
|
}
|
|
@@ -1161,6 +1233,9 @@ export class Interpreter {
|
|
|
1161
1233
|
const metadata = func.__metadata || func;
|
|
1162
1234
|
const funcEnv = new Environment(metadata.closure);
|
|
1163
1235
|
|
|
1236
|
+
// Get function name for call stack tracking
|
|
1237
|
+
const funcName = metadata.name || func.name || 'anonymous';
|
|
1238
|
+
|
|
1164
1239
|
// Bind 'this' if provided (for method calls)
|
|
1165
1240
|
if (thisContext !== undefined) {
|
|
1166
1241
|
funcEnv.define('this', thisContext);
|
|
@@ -1196,18 +1271,54 @@ export class Interpreter {
|
|
|
1196
1271
|
// Execute function body
|
|
1197
1272
|
// If async, use async evaluation and return a promise
|
|
1198
1273
|
if (metadata.async) {
|
|
1274
|
+
// Track call stack for async functions
|
|
1275
|
+
if (this.executionController) {
|
|
1276
|
+
this.executionController._pushCall(funcName);
|
|
1277
|
+
}
|
|
1199
1278
|
return (async () => {
|
|
1279
|
+
try {
|
|
1280
|
+
if (metadata.expression) {
|
|
1281
|
+
// Arrow function with expression body
|
|
1282
|
+
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1283
|
+
// If the result is a ThrowSignal, throw the error
|
|
1284
|
+
if (result instanceof ThrowSignal) {
|
|
1285
|
+
throw result.value;
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
} else {
|
|
1289
|
+
// Block statement body
|
|
1290
|
+
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1291
|
+
if (result instanceof ReturnValue) {
|
|
1292
|
+
return result.value;
|
|
1293
|
+
}
|
|
1294
|
+
// If the result is a ThrowSignal, throw the error
|
|
1295
|
+
if (result instanceof ThrowSignal) {
|
|
1296
|
+
throw result.value;
|
|
1297
|
+
}
|
|
1298
|
+
return undefined;
|
|
1299
|
+
}
|
|
1300
|
+
} finally {
|
|
1301
|
+
if (this.executionController) {
|
|
1302
|
+
this.executionController._popCall();
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
})();
|
|
1306
|
+
} else {
|
|
1307
|
+
// Synchronous evaluation for non-async functions
|
|
1308
|
+
// Track call stack for sync functions
|
|
1309
|
+
if (this.executionController) {
|
|
1310
|
+
this.executionController._pushCall(funcName);
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1200
1313
|
if (metadata.expression) {
|
|
1201
|
-
|
|
1202
|
-
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1314
|
+
const result = this.evaluate(metadata.body, funcEnv);
|
|
1203
1315
|
// If the result is a ThrowSignal, throw the error
|
|
1204
1316
|
if (result instanceof ThrowSignal) {
|
|
1205
1317
|
throw result.value;
|
|
1206
1318
|
}
|
|
1207
1319
|
return result;
|
|
1208
1320
|
} else {
|
|
1209
|
-
|
|
1210
|
-
const result = await this.evaluateAsync(metadata.body, funcEnv);
|
|
1321
|
+
const result = this.evaluate(metadata.body, funcEnv);
|
|
1211
1322
|
if (result instanceof ReturnValue) {
|
|
1212
1323
|
return result.value;
|
|
1213
1324
|
}
|
|
@@ -1217,26 +1328,10 @@ export class Interpreter {
|
|
|
1217
1328
|
}
|
|
1218
1329
|
return undefined;
|
|
1219
1330
|
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
if (metadata.expression) {
|
|
1224
|
-
const result = this.evaluate(metadata.body, funcEnv);
|
|
1225
|
-
// If the result is a ThrowSignal, throw the error
|
|
1226
|
-
if (result instanceof ThrowSignal) {
|
|
1227
|
-
throw result.value;
|
|
1228
|
-
}
|
|
1229
|
-
return result;
|
|
1230
|
-
} else {
|
|
1231
|
-
const result = this.evaluate(metadata.body, funcEnv);
|
|
1232
|
-
if (result instanceof ReturnValue) {
|
|
1233
|
-
return result.value;
|
|
1234
|
-
}
|
|
1235
|
-
// If the result is a ThrowSignal, throw the error
|
|
1236
|
-
if (result instanceof ThrowSignal) {
|
|
1237
|
-
throw result.value;
|
|
1331
|
+
} finally {
|
|
1332
|
+
if (this.executionController) {
|
|
1333
|
+
this.executionController._popCall();
|
|
1238
1334
|
}
|
|
1239
|
-
return undefined;
|
|
1240
1335
|
}
|
|
1241
1336
|
}
|
|
1242
1337
|
}
|
|
@@ -2175,4 +2270,253 @@ export class Interpreter {
|
|
|
2175
2270
|
// Already handled in evaluateObjectExpression
|
|
2176
2271
|
return undefined;
|
|
2177
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
|
+
}
|
|
2178
2522
|
}
|