@farm.js/react 0.1.0-beta.0 → 0.1.0-beta.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.
package/dist/vite.mjs CHANGED
@@ -1,10 +1,18 @@
1
1
  import { normalizePath } from 'vite';
2
2
  import { transformAsync, types, traverse } from '@babel/core';
3
+ import { mkdir, writeFile } from 'fs/promises';
4
+ import { resolve, dirname, relative } from 'path';
3
5
 
4
6
  // src/vite.ts
7
+ var SAFE_GLOBAL_CALLS = /* @__PURE__ */ new Set(["Boolean", "Number", "String"]);
8
+ var SAFE_MATH_CALLS = /* @__PURE__ */ new Set(["abs", "ceil", "floor", "max", "min", "round", "sign", "trunc"]);
5
9
  function isComponentName(name) {
6
10
  return /^[A-Z]/.test(name);
7
11
  }
12
+ function isHostElement(element) {
13
+ const name = element.openingElement.name;
14
+ return types.isJSXIdentifier(name) && /^[a-z]/.test(name.name);
15
+ }
8
16
  function directiveValues(node) {
9
17
  return (node.directives || []).map((directive) => directive.value.value);
10
18
  }
@@ -27,119 +35,1672 @@ function collectStateDependencies(expression, statesByValue) {
27
35
  const state = statesByValue.get(path.node.name);
28
36
  if (state && !path.scope.hasBinding(path.node.name)) dependencies.add(state.index);
29
37
  }
30
- });
31
- return [...dependencies].sort((left, right) => left - right);
32
- }
33
- function referencesIdentifier(expression, name) {
34
- let referenced = false;
35
- traverse(expressionFile(cloneExpression(expression)), {
36
- ReferencedIdentifier(path) {
37
- if (path.node.name === name && !path.scope.hasBinding(name)) {
38
- referenced = true;
38
+ });
39
+ return [...dependencies].sort((left, right) => left - right);
40
+ }
41
+ function referencesIdentifier(expression, name) {
42
+ let referenced = false;
43
+ traverse(expressionFile(cloneExpression(expression)), {
44
+ ReferencedIdentifier(path) {
45
+ if (path.node.name === name && !path.scope.hasBinding(name)) {
46
+ referenced = true;
47
+ }
48
+ }
49
+ });
50
+ return referenced;
51
+ }
52
+ function collectReferencedLocals(expression, names) {
53
+ const references = /* @__PURE__ */ new Set();
54
+ traverse(expressionFile(cloneExpression(expression)), {
55
+ ReferencedIdentifier(path) {
56
+ if (names.has(path.node.name) && !path.scope.hasBinding(path.node.name)) {
57
+ references.add(path.node.name);
58
+ }
59
+ }
60
+ });
61
+ return [...references];
62
+ }
63
+ function rewriteDerivedAccess(expression, derivedByName) {
64
+ const file = expressionFile(cloneExpression(expression));
65
+ traverse(file, {
66
+ ReferencedIdentifier(path) {
67
+ if (path.scope.hasBinding(path.node.name)) return;
68
+ const derived = derivedByName.get(path.node.name);
69
+ if (!derived) return;
70
+ path.replaceWith(cloneExpression(derived));
71
+ path.skip();
72
+ }
73
+ });
74
+ return file.program.body[0].expression;
75
+ }
76
+ function rewriteDestructuredPropAccess(expression, names, propsParameter) {
77
+ if (names.size === 0) return cloneExpression(expression);
78
+ const file = expressionFile(cloneExpression(expression));
79
+ traverse(file, {
80
+ ReferencedIdentifier(path) {
81
+ if (!names.has(path.node.name) || path.scope.hasBinding(path.node.name)) return;
82
+ if (path.parentPath.isJSXOpeningElement() || path.parentPath.isJSXClosingElement()) return;
83
+ path.replaceWith(
84
+ types.memberExpression(types.cloneNode(propsParameter), types.identifier(path.node.name))
85
+ );
86
+ path.skip();
87
+ }
88
+ });
89
+ return file.program.body[0].expression;
90
+ }
91
+ function rewriteHandlerAccess(root, handlersByName) {
92
+ if (handlersByName.size === 0) return { root: types.cloneNode(root, true) };
93
+ const file = expressionFile(types.cloneNode(root, true));
94
+ let reason;
95
+ traverse(file, {
96
+ ReferencedIdentifier(path) {
97
+ if (reason || path.scope.hasBinding(path.node.name)) return;
98
+ const handler = handlersByName.get(path.node.name);
99
+ if (!handler) return;
100
+ const container = path.parentPath;
101
+ const attribute = container.parentPath;
102
+ const eventName = attribute?.isJSXAttribute() && jsxAttributeName(attribute.node);
103
+ if (container.isJSXExpressionContainer() && container.node.expression === path.node && eventName && /^on[A-Z]/.test(eventName)) {
104
+ path.replaceWith(types.cloneNode(handler.value, true));
105
+ path.skip();
106
+ return;
107
+ }
108
+ const call = container.isCallExpression() && container.node.callee === path.node ? container : null;
109
+ const event = call && findContainingEvent(call);
110
+ const eventExpression = event?.node.value && types.isJSXExpressionContainer(event.node.value) && event.node.value.expression;
111
+ const isInsideInlineHandler = eventExpression && (types.isArrowFunctionExpression(eventExpression) || types.isFunctionExpression(eventExpression)) && Boolean(call?.findParent((parent) => parent.node === eventExpression));
112
+ if (!call || !event || !isInsideInlineHandler) {
113
+ reason = `event handler ${handler.name} must be passed directly to a JSX event or called inside its inline handler`;
114
+ path.stop();
115
+ return;
116
+ }
117
+ path.replaceWith(types.cloneNode(handler.value, true));
118
+ path.skip();
119
+ }
120
+ });
121
+ return reason ? { reason } : { root: file.program.body[0].expression };
122
+ }
123
+ function isSafeCompilerCall(expression, safeGlobals) {
124
+ if (types.isIdentifier(expression.callee)) {
125
+ return safeGlobals.has(expression.callee.name) && SAFE_GLOBAL_CALLS.has(expression.callee.name);
126
+ }
127
+ return types.isMemberExpression(expression.callee) && !expression.callee.computed && types.isIdentifier(expression.callee.object, { name: "Math" }) && safeGlobals.has("Math") && types.isIdentifier(expression.callee.property) && SAFE_MATH_CALLS.has(expression.callee.property.name);
128
+ }
129
+ function validateDerivedExpression(expression, safeGlobals) {
130
+ let unsupported;
131
+ const reject = (reason, path) => {
132
+ unsupported = reason;
133
+ path.stop();
134
+ };
135
+ traverse(expressionFile(cloneExpression(expression)), {
136
+ ArrayExpression: (path) => reject("array literals", path),
137
+ AssignmentExpression: (path) => reject("assignments", path),
138
+ AwaitExpression: (path) => reject("await expressions", path),
139
+ CallExpression(path) {
140
+ if (!isSafeCompilerCall(path.node, safeGlobals)) reject("function calls", path);
141
+ },
142
+ ClassExpression: (path) => reject("class expressions", path),
143
+ Function: (path) => reject("function expressions", path),
144
+ JSXElement: (path) => reject("JSX", path),
145
+ JSXFragment: (path) => reject("JSX", path),
146
+ NewExpression: (path) => reject("constructor calls", path),
147
+ ObjectExpression: (path) => reject("object literals", path),
148
+ OptionalCallExpression: (path) => reject("function calls", path),
149
+ TaggedTemplateExpression: (path) => reject("tagged templates", path),
150
+ ThisExpression: (path) => reject("this expressions", path),
151
+ UnaryExpression(path) {
152
+ if (path.node.operator === "delete") reject("delete expressions", path);
153
+ },
154
+ UpdateExpression: (path) => reject("update expressions", path),
155
+ YieldExpression: (path) => reject("yield expressions", path)
156
+ });
157
+ return unsupported;
158
+ }
159
+ function findContainingEvent(path) {
160
+ const attribute = path.findParent((parent) => parent.isJSXAttribute());
161
+ if (!attribute?.isJSXAttribute() || !types.isJSXIdentifier(attribute.node.name)) return null;
162
+ return /^on[A-Z]/.test(attribute.node.name.name) ? attribute : null;
163
+ }
164
+ function validateSetterUsage(root, statesBySetter) {
165
+ const file = expressionFile(types.cloneNode(root, true));
166
+ let reason;
167
+ traverse(file, {
168
+ ReferencedIdentifier(path) {
169
+ if (reason) return;
170
+ const state = statesBySetter.get(path.node.name);
171
+ if (!state || path.scope.hasBinding(path.node.name)) return;
172
+ const parent = path.parentPath;
173
+ if (!parent.isCallExpression() || parent.node.callee !== path.node || !findContainingEvent(parent)) {
174
+ reason = `${state.setterName} must be called inside a JSX event handler`;
175
+ }
176
+ }
177
+ });
178
+ return reason;
179
+ }
180
+ function rewriteStateAccess(expression, stateParameter, statesByValue, statesBySetter) {
181
+ const file = expressionFile(cloneExpression(expression));
182
+ traverse(file, {
183
+ CallExpression(path) {
184
+ const callee = path.get("callee");
185
+ if (!callee.isIdentifier() || callee.scope.hasBinding(callee.node.name)) return;
186
+ const state = statesBySetter.get(callee.node.name);
187
+ if (!state) return;
188
+ callee.replaceWith(
189
+ types.memberExpression(
190
+ types.memberExpression(types.cloneNode(stateParameter), types.numericLiteral(state.index), true),
191
+ types.identifier("set")
192
+ )
193
+ );
194
+ },
195
+ ReferencedIdentifier(path) {
196
+ const state = statesByValue.get(path.node.name);
197
+ if (!state || path.scope.hasBinding(path.node.name)) return;
198
+ path.replaceWith(
199
+ types.callExpression(
200
+ types.memberExpression(
201
+ types.memberExpression(types.cloneNode(stateParameter), types.numericLiteral(state.index), true),
202
+ types.identifier("get")
203
+ ),
204
+ []
205
+ )
206
+ );
207
+ }
208
+ });
209
+ return file.program.body[0].expression;
210
+ }
211
+ function cleanJsxText(value) {
212
+ const lines = value.replace(/\r/g, "").split("\n");
213
+ let result = "";
214
+ for (let index = 0; index < lines.length; index += 1) {
215
+ const lastNonEmptyLine = lines.slice(index + 1).every((line2) => line2.trim().length === 0);
216
+ let line = lines[index].replace(/\t/g, " ").replace(/ +/g, " ");
217
+ if (index > 0) line = line.replace(/^ /, "");
218
+ if (!lastNonEmptyLine) line = line.replace(/ $/, "");
219
+ if (line) result += line + (!lastNonEmptyLine ? " " : "");
220
+ }
221
+ return result;
222
+ }
223
+ function isTextExpression(expression, statesByValue, safeGlobals) {
224
+ if (collectStateDependencies(expression, statesByValue).length > 0) {
225
+ return validateDerivedExpression(expression, safeGlobals) === void 0;
226
+ }
227
+ let supported = true;
228
+ traverse(expressionFile(cloneExpression(expression)), {
229
+ CallExpression(path) {
230
+ if (!isSafeCompilerCall(path.node, safeGlobals)) {
231
+ supported = false;
232
+ path.stop();
233
+ }
234
+ },
235
+ OptionalCallExpression(path) {
236
+ supported = false;
237
+ path.stop();
238
+ },
239
+ JSXElement(path) {
240
+ supported = false;
241
+ path.stop();
242
+ },
243
+ JSXFragment(path) {
244
+ supported = false;
245
+ path.stop();
246
+ },
247
+ ObjectExpression(path) {
248
+ supported = false;
249
+ path.stop();
250
+ },
251
+ Function(path) {
252
+ supported = false;
253
+ path.stop();
254
+ }
255
+ });
256
+ return supported;
257
+ }
258
+ function jsxAttributeName(attribute) {
259
+ return types.isJSXIdentifier(attribute.name) ? attribute.name.name : void 0;
260
+ }
261
+ function isEmptyConditionalBranch(expression) {
262
+ return types.isNullLiteral(expression) || types.isBooleanLiteral(expression, { value: false });
263
+ }
264
+ function conditionalBlockShape(expression) {
265
+ if (types.isLogicalExpression(expression, { operator: "&&" }) && types.isExpression(expression.left) && types.isJSXElement(expression.right)) {
266
+ return {
267
+ test: expression.left,
268
+ logical: true,
269
+ truthy: expression.right,
270
+ branches: [expression.right]
271
+ };
272
+ }
273
+ if (!types.isConditionalExpression(expression)) return null;
274
+ const branches = [expression.consequent, expression.alternate];
275
+ if (!branches.every((branch) => types.isJSXElement(branch) || isEmptyConditionalBranch(branch)) || !branches.some((branch) => types.isJSXElement(branch))) {
276
+ return null;
277
+ }
278
+ return {
279
+ test: expression.test,
280
+ logical: false,
281
+ truthy: types.isJSXElement(expression.consequent) ? expression.consequent : void 0,
282
+ falsy: types.isJSXElement(expression.alternate) ? expression.alternate : void 0,
283
+ branches: branches.filter((branch) => types.isJSXElement(branch))
284
+ };
285
+ }
286
+ function isMapCall(expression) {
287
+ return types.isCallExpression(expression) && types.isMemberExpression(expression.callee) && !expression.callee.computed && types.isExpression(expression.callee.object) && types.isIdentifier(expression.callee.property, { name: "map" });
288
+ }
289
+ function returnedExpression(callback) {
290
+ if (types.isExpression(callback.body)) return callback.body;
291
+ if (callback.body.body.length !== 1 || !types.isReturnStatement(callback.body.body[0])) {
292
+ return void 0;
293
+ }
294
+ const value = callback.body.body[0].argument;
295
+ return value && types.isExpression(value) ? value : void 0;
296
+ }
297
+ var SAFE_COLLECTION_PIPELINE_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "toReversed", "toSorted"]);
298
+ function collectionPipelineMethod(expression) {
299
+ if (!types.isCallExpression(expression) || !types.isMemberExpression(expression.callee) || expression.callee.computed || !types.isExpression(expression.callee.object) || !types.isIdentifier(expression.callee.property)) {
300
+ return void 0;
301
+ }
302
+ return SAFE_COLLECTION_PIPELINE_METHODS.has(expression.callee.property.name) ? expression.callee.property.name : void 0;
303
+ }
304
+ function validateCollectionCallback(callback, method, safeGlobals) {
305
+ const expectedParameters = method === "filter" ? "one item and an optional index" : "two items";
306
+ const validParameterCount = method === "filter" ? callback.params.length >= 1 && callback.params.length <= 2 : callback.params.length === 2;
307
+ if (callback.async || callback.generator || !validParameterCount || callback.params.some((parameter) => !types.isIdentifier(parameter))) {
308
+ return `${method} callbacks must be synchronous and use ${expectedParameters}`;
309
+ }
310
+ if (containsDirectHookCall(callback)) {
311
+ return `Hooks cannot be called inside a ${method} callback`;
312
+ }
313
+ const value = returnedExpression(callback);
314
+ if (!value) return `${method} callbacks must return one expression`;
315
+ const unsupported = validateDerivedExpression(value, safeGlobals);
316
+ return unsupported ? `${method} callbacks cannot use ${unsupported}` : void 0;
317
+ }
318
+ function validateCollectionPipeline(expression, safeGlobals) {
319
+ const method = collectionPipelineMethod(expression);
320
+ if (!method || !types.isCallExpression(expression)) {
321
+ return validateDerivedExpression(expression, safeGlobals);
322
+ }
323
+ const source = expression.callee.object;
324
+ const sourceUnsupported = collectionPipelineMethod(source) ? validateCollectionPipeline(source, safeGlobals) : validateDerivedExpression(source, safeGlobals);
325
+ if (sourceUnsupported) return sourceUnsupported;
326
+ if (method === "filter") {
327
+ if (expression.arguments.length !== 1 || !types.isArrowFunctionExpression(expression.arguments[0]) && !types.isFunctionExpression(expression.arguments[0])) {
328
+ return "filter requires one inline callback";
329
+ }
330
+ return validateCollectionCallback(expression.arguments[0], "filter", safeGlobals);
331
+ }
332
+ if (method === "toSorted") {
333
+ if (expression.arguments.length === 0) return void 0;
334
+ if (expression.arguments.length !== 1 || !types.isArrowFunctionExpression(expression.arguments[0]) && !types.isFunctionExpression(expression.arguments[0])) {
335
+ return "toSorted requires an optional inline comparator";
336
+ }
337
+ return validateCollectionCallback(expression.arguments[0], "toSorted", safeGlobals);
338
+ }
339
+ const maximumArguments = method === "slice" ? 2 : 0;
340
+ if (expression.arguments.length > maximumArguments) {
341
+ return `${method} accepts at most ${maximumArguments} compiler-safe arguments`;
342
+ }
343
+ for (const argument of expression.arguments) {
344
+ if (!types.isExpression(argument)) return `${method} does not support spread arguments`;
345
+ const unsupported = validateDerivedExpression(argument, safeGlobals);
346
+ if (unsupported) return `${method} arguments cannot use ${unsupported}`;
347
+ }
348
+ return void 0;
349
+ }
350
+ function containsDirectHookCall(callback) {
351
+ let found = false;
352
+ traverse(expressionFile(types.cloneNode(callback, true)), {
353
+ CallExpression(path) {
354
+ const callee = path.node.callee;
355
+ const name = types.isIdentifier(callee) ? callee.name : types.isMemberExpression(callee) && !callee.computed && types.isIdentifier(callee.property) ? callee.property.name : "";
356
+ if (/^use[A-Z0-9]/.test(name)) {
357
+ found = true;
358
+ path.stop();
359
+ }
360
+ }
361
+ });
362
+ return found;
363
+ }
364
+ function jsxKeyExpression(element) {
365
+ for (const attribute of element.openingElement.attributes) {
366
+ if (!types.isJSXAttribute(attribute) || !types.isJSXIdentifier(attribute.name, { name: "key" })) {
367
+ continue;
368
+ }
369
+ if (types.isStringLiteral(attribute.value)) return types.stringLiteral(attribute.value.value);
370
+ if (types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression))
371
+ return attribute.value.expression;
372
+ }
373
+ return void 0;
374
+ }
375
+ function validateKeyFunction(callback, keyExpression, safeGlobals) {
376
+ if (callback.async || callback.generator || callback.params.length < 1 || callback.params.length > 2 || !types.isIdentifier(callback.params[0]) || callback.params[1] !== void 0 && !types.isIdentifier(callback.params[1])) {
377
+ return "keyed list callbacks must be synchronous and use item and optional index identifiers";
378
+ }
379
+ const item = callback.params[0].name;
380
+ const index = types.isIdentifier(callback.params[1]) ? callback.params[1].name : void 0;
381
+ if (index && referencesIdentifier(keyExpression, index)) {
382
+ return "keyed list keys cannot depend on the array index";
383
+ }
384
+ if (!referencesIdentifier(keyExpression, item)) {
385
+ return "keyed list keys must depend on the mapped item";
386
+ }
387
+ const unsupported = validateDerivedExpression(keyExpression, safeGlobals);
388
+ return unsupported ? `keyed list keys cannot use ${unsupported}` : void 0;
389
+ }
390
+ function isPublicListElement(element, listNames) {
391
+ const name = element.openingElement.name;
392
+ return types.isJSXIdentifier(name) && listNames.has(name.name);
393
+ }
394
+ function meaningfulJsxChildren(element) {
395
+ return element.children.filter((child) => {
396
+ if (types.isJSXText(child)) return child.value.trim().length > 0;
397
+ return !(types.isJSXExpressionContainer(child) && types.isJSXEmptyExpression(child.expression));
398
+ });
399
+ }
400
+ function analyzeMapList(expression, statesByValue, safeGlobals) {
401
+ const callee = expression.callee;
402
+ const collection = callee.object;
403
+ const collectionUnsupported = validateCollectionPipeline(collection, safeGlobals);
404
+ if (collectionUnsupported) {
405
+ return { reason: `keyed list collection cannot use ${collectionUnsupported}` };
406
+ }
407
+ if (expression.arguments.length !== 1 || !types.isArrowFunctionExpression(expression.arguments[0]) && !types.isFunctionExpression(expression.arguments[0])) {
408
+ return { reason: "keyed list map must use one inline render callback" };
409
+ }
410
+ const callback = expression.arguments[0];
411
+ if (containsDirectHookCall(callback)) {
412
+ return { reason: "Hooks cannot be called directly inside a keyed list callback" };
413
+ }
414
+ const row = returnedExpression(callback);
415
+ if (!row || !types.isJSXElement(row)) {
416
+ return { reason: "keyed list map callbacks must return one React element" };
417
+ }
418
+ const key = jsxKeyExpression(row);
419
+ if (!key) return { reason: "automatically compiled lists require an explicit item key" };
420
+ const keyReason = validateKeyFunction(callback, key, safeGlobals);
421
+ if (keyReason) return { reason: keyReason };
422
+ return { dependencies: collectStateDependencies(expression, statesByValue) };
423
+ }
424
+ function listAttributeExpression(element, name) {
425
+ for (const attribute of element.openingElement.attributes) {
426
+ if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name, { name }) && types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression)) {
427
+ return attribute.value.expression;
428
+ }
429
+ }
430
+ return void 0;
431
+ }
432
+ function analyzePublicList(element, statesByValue, safeGlobals) {
433
+ if (element.openingElement.attributes.some((attribute) => types.isJSXSpreadAttribute(attribute))) {
434
+ return { reason: "compiled List boundaries do not support JSX attribute spreads" };
435
+ }
436
+ const each = listAttributeExpression(element, "each");
437
+ const by = listAttributeExpression(element, "by");
438
+ if (!each || !by) return { reason: "List requires explicit each and by properties" };
439
+ const collectionUnsupported = validateCollectionPipeline(each, safeGlobals);
440
+ if (collectionUnsupported) {
441
+ return { reason: `List each cannot use ${collectionUnsupported}` };
442
+ }
443
+ if (!types.isArrowFunctionExpression(by) && !types.isFunctionExpression(by)) {
444
+ return { reason: "compiled List by must use an inline key function" };
445
+ }
446
+ const key = returnedExpression(by);
447
+ if (!key) return { reason: "compiled List by must return one key expression" };
448
+ const keyReason = validateKeyFunction(by, key, safeGlobals);
449
+ if (keyReason) return { reason: keyReason };
450
+ const children = meaningfulJsxChildren(element);
451
+ if (children.length !== 1 || !types.isJSXExpressionContainer(children[0]) || types.isJSXEmptyExpression(children[0].expression) || !types.isArrowFunctionExpression(children[0].expression) && !types.isFunctionExpression(children[0].expression)) {
452
+ return { reason: "compiled List children must use one inline render function" };
453
+ }
454
+ const render = children[0].expression;
455
+ if (!returnedExpression(render)) {
456
+ return { reason: "compiled List children must return one React element" };
457
+ }
458
+ if (containsDirectHookCall(render)) {
459
+ return { reason: "Hooks cannot be called directly inside List children" };
460
+ }
461
+ return { dependencies: collectStateDependencies(element, statesByValue) };
462
+ }
463
+ function validateKeyedRowEventHandler(handler) {
464
+ let reason;
465
+ traverse(expressionFile(types.cloneNode(handler, true)), {
466
+ MetaProperty(path) {
467
+ reason = "meta properties";
468
+ path.stop();
469
+ },
470
+ ReferencedIdentifier(path) {
471
+ if (path.node.name !== "arguments") return;
472
+ reason = "arguments access";
473
+ path.stop();
474
+ },
475
+ Super(path) {
476
+ reason = "super access";
477
+ path.stop();
478
+ },
479
+ ThisExpression(path) {
480
+ reason = "this access";
481
+ path.stop();
482
+ }
483
+ });
484
+ return reason;
485
+ }
486
+ function analyzeKeyedRowTree(row, renderCallback, safeGlobals) {
487
+ if (renderCallback.async || renderCallback.generator || renderCallback.params.length < 1 || renderCallback.params.length > 2 || !types.isIdentifier(renderCallback.params[0]) || renderCallback.params[1] !== void 0 && !types.isIdentifier(renderCallback.params[1])) {
488
+ return { reason: "compiled keyed rows require item and optional index identifiers" };
489
+ }
490
+ const bindings = [];
491
+ const events = [];
492
+ const conditionals = [];
493
+ const visit = (element, path) => {
494
+ const tag = element.openingElement.name;
495
+ if (!types.isJSXIdentifier(tag) || !/^[a-z]/.test(tag.name)) {
496
+ return "compiled keyed rows support host elements only";
497
+ }
498
+ if (tag.name === "svg") return "compiled keyed rows do not support SVG yet";
499
+ const attributeNames = new Set(
500
+ element.openingElement.attributes.filter((attribute) => types.isJSXAttribute(attribute)).map(jsxAttributeName).filter((name) => name !== void 0)
501
+ );
502
+ if ((tag.name === "input" || tag.name === "textarea" || tag.name === "select") && (attributeNames.has("value") || attributeNames.has("checked")) && [...attributeNames].some((name) => /^on[A-Z]/.test(name))) {
503
+ return "controlled form elements in interactive compiled keyed rows require React ownership";
504
+ }
505
+ for (const attribute of element.openingElement.attributes) {
506
+ if (types.isJSXSpreadAttribute(attribute)) {
507
+ return "compiled keyed rows do not support JSX attribute spreads";
508
+ }
509
+ const name = jsxAttributeName(attribute);
510
+ if (!name) return "compiled keyed rows do not support namespaced JSX attributes";
511
+ if (name === "ref" || name === "dangerouslySetInnerHTML") {
512
+ return `compiled keyed row ${name} requires React ownership`;
513
+ }
514
+ if (name === "key") continue;
515
+ if (/^on[A-Z]/.test(name)) {
516
+ if (!types.isJSXExpressionContainer(attribute.value) || !types.isArrowFunctionExpression(attribute.value.expression) && !types.isFunctionExpression(attribute.value.expression)) {
517
+ return "interactive compiled keyed rows require inline synchronous event handlers";
518
+ }
519
+ const handler = attribute.value.expression;
520
+ if (handler.async || handler.generator) {
521
+ return "interactive compiled keyed row handlers must be synchronous";
522
+ }
523
+ if (containsDirectHookCall(handler)) {
524
+ return "Hooks cannot be called inside compiled keyed row handlers";
525
+ }
526
+ const unsupported2 = validateKeyedRowEventHandler(handler);
527
+ if (unsupported2) {
528
+ return `interactive compiled keyed row handlers cannot use ${unsupported2}`;
529
+ }
530
+ events.push({
531
+ id: events.length,
532
+ path: [...path],
533
+ name,
534
+ value: types.cloneNode(handler, true)
535
+ });
536
+ continue;
537
+ }
538
+ if (!types.isJSXExpressionContainer(attribute.value) || types.isJSXEmptyExpression(attribute.value.expression)) {
539
+ continue;
540
+ }
541
+ const expression = attribute.value.expression;
542
+ if (name === "style") {
543
+ if (!types.isObjectExpression(expression)) {
544
+ return "compiled keyed row styles must use one inline object literal";
545
+ }
546
+ for (const property of expression.properties) {
547
+ if (!types.isObjectProperty(property) || property.computed || !types.isExpression(property.value)) {
548
+ return "compiled keyed row styles do not support spreads, methods, or computed properties";
549
+ }
550
+ const propertyName = types.isIdentifier(property.key) ? property.key.name : types.isStringLiteral(property.key) ? property.key.value : void 0;
551
+ if (!propertyName || propertyName.includes("-") && !propertyName.startsWith("--")) {
552
+ return "compiled keyed row style names must use camelCase or a CSS custom property";
553
+ }
554
+ const unsupported2 = validateDerivedExpression(property.value, safeGlobals);
555
+ if (unsupported2) {
556
+ return `compiled keyed row style ${propertyName} cannot use ${unsupported2}`;
557
+ }
558
+ bindings.push({
559
+ kind: "style",
560
+ path: [...path],
561
+ name: propertyName,
562
+ value: cloneExpression(property.value)
563
+ });
564
+ }
565
+ continue;
566
+ }
567
+ if (name === "children") return "compiled keyed row children props are not supported yet";
568
+ const unsupported = validateDerivedExpression(expression, safeGlobals);
569
+ if (unsupported) return `compiled keyed row ${name} cannot use ${unsupported}`;
570
+ bindings.push({
571
+ kind: "attribute",
572
+ path: [...path],
573
+ name,
574
+ value: cloneExpression(expression)
575
+ });
576
+ }
577
+ const meaningfulChildren = meaningfulJsxChildren(element);
578
+ const conditionalChild = meaningfulChildren.find(
579
+ (child) => types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression) && conditionalBlockShape(child.expression) !== null
580
+ );
581
+ if (conditionalChild && !types.isJSXEmptyExpression(conditionalChild.expression)) {
582
+ if (meaningfulChildren.length !== 1) {
583
+ return "compiled keyed row conditionals must be the only child of a host container";
584
+ }
585
+ const shape = conditionalBlockShape(conditionalChild.expression);
586
+ if (!shape) return "compiled keyed row conditional shape is not supported";
587
+ const testUnsupported = validateDerivedExpression(shape.test, safeGlobals);
588
+ if (testUnsupported) {
589
+ return `compiled keyed row conditional test cannot use ${testUnsupported}`;
590
+ }
591
+ const truthyAnalysis = shape.truthy ? analyzeHostConditionalTree(shape.truthy, safeGlobals) : { bindings: [] };
592
+ const falsyAnalysis = shape.falsy ? analyzeHostConditionalTree(shape.falsy, safeGlobals) : { bindings: [] };
593
+ if (truthyAnalysis.reason || falsyAnalysis.reason) {
594
+ return truthyAnalysis.reason || falsyAnalysis.reason || "compiled keyed row conditional branch is not supported";
595
+ }
596
+ conditionals.push({
597
+ id: conditionals.length,
598
+ path: [...path],
599
+ test: cloneExpression(shape.test),
600
+ logical: shape.logical,
601
+ truthy: shape.truthy ? types.cloneNode(shape.truthy, true) : void 0,
602
+ falsy: shape.falsy ? types.cloneNode(shape.falsy, true) : void 0,
603
+ truthyBindings: truthyAnalysis.bindings || [],
604
+ falsyBindings: falsyAnalysis.bindings || []
605
+ });
606
+ return void 0;
607
+ }
608
+ const nestedElements = element.children.filter(
609
+ (child) => types.isJSXElement(child)
610
+ );
611
+ if (element.children.some((child) => types.isJSXFragment(child))) {
612
+ return "compiled keyed rows do not support fragments yet";
613
+ }
614
+ const expressions = element.children.filter(
615
+ (child) => types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression)
616
+ );
617
+ for (const child of expressions) {
618
+ const unsupported = validateDerivedExpression(child.expression, safeGlobals);
619
+ if (unsupported) return `compiled keyed row text cannot use ${unsupported}`;
620
+ }
621
+ if (nestedElements.length > 0 && expressions.length > 0) {
622
+ return "compiled keyed rows do not support dynamic text beside nested elements yet";
623
+ }
624
+ if (nestedElements.length === 0 && expressions.length > 0) {
625
+ const parts = [];
626
+ for (const child of element.children) {
627
+ if (types.isJSXText(child)) {
628
+ const text = cleanJsxText(child.value);
629
+ if (text) parts.push(types.stringLiteral(text));
630
+ } else if (types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression)) {
631
+ parts.push(cloneExpression(child.expression));
632
+ }
633
+ }
634
+ bindings.push({ kind: "text", path: [...path], value: types.arrayExpression(parts) });
635
+ }
636
+ let elementIndex = 0;
637
+ for (const child of element.children) {
638
+ if (!types.isJSXElement(child)) continue;
639
+ const reason2 = visit(child, [...path, elementIndex]);
640
+ if (reason2) return reason2;
641
+ elementIndex += 1;
642
+ }
643
+ return void 0;
644
+ };
645
+ const reason = visit(row, []);
646
+ return reason ? { reason } : { bindings, events, conditionals };
647
+ }
648
+ function analyzeKeyedRowsContainer(container, statesByValue, safeGlobals, listNames) {
649
+ if (container.openingElement.attributes.some(
650
+ (attribute) => types.isJSXSpreadAttribute(attribute) || types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
651
+ )) {
652
+ return void 0;
653
+ }
654
+ for (const attribute of container.openingElement.attributes) {
655
+ if (types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression) && !/^on[A-Z]/.test(attribute.name.name) && collectStateDependencies(attribute.value.expression, statesByValue).length > 0) {
656
+ return void 0;
657
+ }
658
+ }
659
+ const children = meaningfulJsxChildren(container);
660
+ if (children.length !== 1) return void 0;
661
+ let collection;
662
+ let keyCallback;
663
+ let renderCallback;
664
+ let row;
665
+ let dependencies;
666
+ let syntax;
667
+ const child = children[0];
668
+ if (types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression) && isMapCall(child.expression)) {
669
+ const result = analyzeMapList(child.expression, statesByValue, safeGlobals);
670
+ if (result.reason) return void 0;
671
+ const callback = child.expression.arguments[0];
672
+ if (!types.isArrowFunctionExpression(callback) && !types.isFunctionExpression(callback))
673
+ return void 0;
674
+ const returned = returnedExpression(callback);
675
+ if (!returned || !types.isJSXElement(returned)) return void 0;
676
+ const key = jsxKeyExpression(returned);
677
+ if (!key) return void 0;
678
+ collection = child.expression.callee.object;
679
+ keyCallback = types.arrowFunctionExpression(
680
+ callback.params.map((parameter) => types.cloneNode(parameter, true)),
681
+ cloneExpression(key)
682
+ );
683
+ renderCallback = callback;
684
+ row = returned;
685
+ dependencies = result.dependencies || [];
686
+ syntax = "map";
687
+ } else if (types.isJSXElement(child) && isPublicListElement(child, listNames)) {
688
+ const result = analyzePublicList(child, statesByValue, safeGlobals);
689
+ if (result.reason) return void 0;
690
+ const each = listAttributeExpression(child, "each");
691
+ const by = listAttributeExpression(child, "by");
692
+ const renderChild = meaningfulJsxChildren(child)[0];
693
+ if (!each || !types.isArrowFunctionExpression(by) && !types.isFunctionExpression(by) || !types.isJSXExpressionContainer(renderChild) || types.isJSXEmptyExpression(renderChild.expression) || !types.isArrowFunctionExpression(renderChild.expression) && !types.isFunctionExpression(renderChild.expression)) {
694
+ return void 0;
695
+ }
696
+ const returned = returnedExpression(renderChild.expression);
697
+ if (!returned || !types.isJSXElement(returned)) return void 0;
698
+ collection = each;
699
+ keyCallback = by;
700
+ renderCallback = renderChild.expression;
701
+ row = returned;
702
+ dependencies = result.dependencies || [];
703
+ syntax = "list";
704
+ } else {
705
+ return void 0;
706
+ }
707
+ const rowAnalysis = analyzeKeyedRowTree(row, renderCallback, safeGlobals);
708
+ if (rowAnalysis.reason) return void 0;
709
+ return {
710
+ collection,
711
+ keyCallback,
712
+ renderCallback,
713
+ row,
714
+ dependencies,
715
+ bindings: rowAnalysis.bindings || [],
716
+ events: rowAnalysis.events || [],
717
+ conditionals: rowAnalysis.conditionals || [],
718
+ syntax
719
+ };
720
+ }
721
+ function analyzeHostConditionalTree(branch, safeGlobals) {
722
+ const bindings = [];
723
+ const visit = (element, path) => {
724
+ const tag = element.openingElement.name;
725
+ if (!types.isJSXIdentifier(tag) || !/^[a-z]/.test(tag.name)) {
726
+ return "compiler-owned conditionals support host elements only";
727
+ }
728
+ if (tag.name === "svg") return "compiler-owned conditionals do not support SVG yet";
729
+ for (const attribute of element.openingElement.attributes) {
730
+ if (types.isJSXSpreadAttribute(attribute)) {
731
+ return "compiler-owned conditionals do not support JSX attribute spreads";
732
+ }
733
+ const name = jsxAttributeName(attribute);
734
+ if (!name) return "compiler-owned conditionals do not support namespaced JSX attributes";
735
+ if (name === "ref" || name === "dangerouslySetInnerHTML" || name === "key") {
736
+ return `compiler-owned conditional ${name} requires React ownership`;
737
+ }
738
+ if (/^on[A-Z]/.test(name)) {
739
+ return "compiler-owned conditional events require React ownership";
740
+ }
741
+ if (!types.isJSXExpressionContainer(attribute.value) || types.isJSXEmptyExpression(attribute.value.expression)) {
742
+ continue;
743
+ }
744
+ const expression = attribute.value.expression;
745
+ if (name === "style") {
746
+ if (!types.isObjectExpression(expression)) {
747
+ return "compiler-owned conditional styles must use one inline object literal";
748
+ }
749
+ for (const property of expression.properties) {
750
+ if (!types.isObjectProperty(property) || property.computed || !types.isExpression(property.value)) {
751
+ return "compiler-owned conditional styles do not support spreads, methods, or computed properties";
752
+ }
753
+ const propertyName = types.isIdentifier(property.key) ? property.key.name : types.isStringLiteral(property.key) ? property.key.value : void 0;
754
+ if (!propertyName || propertyName.includes("-") && !propertyName.startsWith("--")) {
755
+ return "compiler-owned conditional style names must use camelCase or a CSS custom property";
756
+ }
757
+ const unsupported2 = validateDerivedExpression(property.value, safeGlobals);
758
+ if (unsupported2) {
759
+ return `compiler-owned conditional style ${propertyName} cannot use ${unsupported2}`;
760
+ }
761
+ bindings.push({
762
+ kind: "style",
763
+ path: [...path],
764
+ name: propertyName,
765
+ value: cloneExpression(property.value)
766
+ });
767
+ }
768
+ continue;
769
+ }
770
+ if (name === "children") {
771
+ return "compiler-owned conditional children props are not supported yet";
772
+ }
773
+ const unsupported = validateDerivedExpression(expression, safeGlobals);
774
+ if (unsupported) return `compiler-owned conditional ${name} cannot use ${unsupported}`;
775
+ bindings.push({
776
+ kind: "attribute",
777
+ path: [...path],
778
+ name,
779
+ value: cloneExpression(expression)
780
+ });
781
+ }
782
+ const nestedElements = element.children.filter(
783
+ (child) => types.isJSXElement(child)
784
+ );
785
+ if (element.children.some((child) => types.isJSXFragment(child))) {
786
+ return "compiler-owned conditionals do not support fragments yet";
787
+ }
788
+ const expressions = element.children.filter(
789
+ (child) => types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression)
790
+ );
791
+ for (const child of expressions) {
792
+ const unsupported = validateDerivedExpression(child.expression, safeGlobals);
793
+ if (unsupported) return `compiler-owned conditional text cannot use ${unsupported}`;
794
+ }
795
+ if (nestedElements.length > 0 && expressions.length > 0) {
796
+ return "compiler-owned conditionals do not support dynamic text beside nested elements yet";
797
+ }
798
+ if (nestedElements.length === 0 && expressions.length > 0) {
799
+ const parts = [];
800
+ for (const child of element.children) {
801
+ if (types.isJSXText(child)) {
802
+ const text = cleanJsxText(child.value);
803
+ if (text) parts.push(types.stringLiteral(text));
804
+ } else if (types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression)) {
805
+ parts.push(cloneExpression(child.expression));
806
+ }
807
+ }
808
+ bindings.push({ kind: "text", path: [...path], value: types.arrayExpression(parts) });
809
+ }
810
+ let elementIndex = 0;
811
+ for (const child of element.children) {
812
+ if (!types.isJSXElement(child)) continue;
813
+ const reason2 = visit(child, [...path, elementIndex]);
814
+ if (reason2) return reason2;
815
+ elementIndex += 1;
816
+ }
817
+ return void 0;
818
+ };
819
+ const reason = visit(branch, []);
820
+ return reason ? { reason } : { bindings };
821
+ }
822
+ function analyzeHostConditionalContainer(container, statesByValue, safeGlobals) {
823
+ const containerName = container.openingElement.name;
824
+ if (!types.isJSXIdentifier(containerName) || !/^[a-z]/.test(containerName.name) || containerName.name === "svg") {
825
+ return void 0;
826
+ }
827
+ if (container.openingElement.attributes.some(
828
+ (attribute) => types.isJSXSpreadAttribute(attribute) || types.isJSXAttribute(attribute) && types.isJSXIdentifier(attribute.name) && (attribute.name.name === "ref" || attribute.name.name === "dangerouslySetInnerHTML")
829
+ )) {
830
+ return void 0;
831
+ }
832
+ for (const attribute of container.openingElement.attributes) {
833
+ if (!types.isJSXAttribute(attribute) || !types.isJSXIdentifier(attribute.name)) return void 0;
834
+ if (/^on[A-Z]/.test(attribute.name.name) || attribute.name.name === "key") return void 0;
835
+ if (types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression))
836
+ return void 0;
837
+ }
838
+ const children = meaningfulJsxChildren(container);
839
+ if (children.length !== 1 || !types.isJSXExpressionContainer(children[0]) || types.isJSXEmptyExpression(children[0].expression)) {
840
+ return void 0;
841
+ }
842
+ const shape = conditionalBlockShape(children[0].expression);
843
+ if (!shape || validateDerivedExpression(shape.test, safeGlobals)) return void 0;
844
+ const truthyAnalysis = shape.truthy ? analyzeHostConditionalTree(shape.truthy, safeGlobals) : { bindings: [] };
845
+ const falsyAnalysis = shape.falsy ? analyzeHostConditionalTree(shape.falsy, safeGlobals) : { bindings: [] };
846
+ if (truthyAnalysis.reason || falsyAnalysis.reason) return void 0;
847
+ const dependencies = new Set(collectStateDependencies(shape.test, statesByValue));
848
+ for (const binding of [...truthyAnalysis.bindings || [], ...falsyAnalysis.bindings || []]) {
849
+ for (const dependency of collectStateDependencies(binding.value, statesByValue)) {
850
+ dependencies.add(dependency);
851
+ }
852
+ }
853
+ return {
854
+ dependencies: [...dependencies].sort((left, right) => left - right),
855
+ test: cloneExpression(shape.test),
856
+ logical: shape.logical,
857
+ truthy: shape.truthy,
858
+ falsy: shape.falsy,
859
+ truthyBindings: truthyAnalysis.bindings || [],
860
+ falsyBindings: falsyAnalysis.bindings || []
861
+ };
862
+ }
863
+ function keyedListBoundary(blockRuntime, plan, source) {
864
+ const name = types.jsxMemberExpression(
865
+ types.jsxIdentifier(blockRuntime.name),
866
+ types.jsxIdentifier("KeyedList")
867
+ );
868
+ return types.jsxElement(
869
+ types.jsxOpeningElement(
870
+ name,
871
+ [
872
+ types.jsxAttribute(types.jsxIdentifier("id"), types.jsxExpressionContainer(types.numericLiteral(plan.id))),
873
+ types.jsxAttribute(
874
+ types.jsxIdentifier("render"),
875
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], cloneExpression(source)))
876
+ )
877
+ ],
878
+ true
879
+ ),
880
+ null,
881
+ [],
882
+ true
883
+ );
884
+ }
885
+ function hostElementDescriptor(element) {
886
+ const tag = element.openingElement.name;
887
+ const attributes = [];
888
+ const styles = [];
889
+ for (const attribute of element.openingElement.attributes) {
890
+ if (!types.isJSXAttribute(attribute)) continue;
891
+ const name = jsxAttributeName(attribute);
892
+ if (!name || name === "key" || /^on[A-Z]/.test(name)) continue;
893
+ if (name === "style") {
894
+ if (types.isJSXExpressionContainer(attribute.value) && types.isObjectExpression(attribute.value.expression)) {
895
+ for (const property of attribute.value.expression.properties) {
896
+ if (!types.isObjectProperty(property) || !types.isExpression(property.value)) continue;
897
+ const propertyName = types.isIdentifier(property.key) ? property.key.name : property.key.value;
898
+ styles.push(
899
+ types.objectExpression([
900
+ types.objectProperty(types.identifier("name"), types.stringLiteral(propertyName)),
901
+ types.objectProperty(types.identifier("value"), cloneExpression(property.value))
902
+ ])
903
+ );
904
+ }
905
+ }
906
+ continue;
907
+ }
908
+ const value = attribute.value ? types.isStringLiteral(attribute.value) ? types.stringLiteral(attribute.value.value) : types.isJSXExpressionContainer(attribute.value) && !types.isJSXEmptyExpression(attribute.value.expression) ? cloneExpression(attribute.value.expression) : types.identifier("undefined") : types.booleanLiteral(true);
909
+ attributes.push(
910
+ types.objectExpression([
911
+ types.objectProperty(types.identifier("name"), types.stringLiteral(name)),
912
+ types.objectProperty(types.identifier("value"), value)
913
+ ])
914
+ );
915
+ }
916
+ const children = [];
917
+ for (const child of element.children) {
918
+ if (types.isJSXText(child)) {
919
+ const text = cleanJsxText(child.value);
920
+ if (text) children.push(types.stringLiteral(text));
921
+ } else if (types.isJSXElement(child)) {
922
+ children.push(hostElementDescriptor(child));
923
+ } else if (types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression)) {
924
+ children.push(cloneExpression(child.expression));
925
+ }
926
+ }
927
+ return types.objectExpression([
928
+ types.objectProperty(types.identifier("kind"), types.stringLiteral("element")),
929
+ types.objectProperty(types.identifier("tag"), types.stringLiteral(tag.name)),
930
+ types.objectProperty(types.identifier("attributes"), types.arrayExpression(attributes)),
931
+ types.objectProperty(types.identifier("styles"), types.arrayExpression(styles)),
932
+ types.objectProperty(types.identifier("children"), types.arrayExpression(children))
933
+ ]);
934
+ }
935
+ function keyedRowBindingObject(binding, parameters) {
936
+ const properties = [
937
+ types.objectProperty(types.identifier("kind"), types.stringLiteral(binding.kind)),
938
+ types.objectProperty(
939
+ types.identifier("path"),
940
+ types.arrayExpression(binding.path.map((part) => types.numericLiteral(part)))
941
+ )
942
+ ];
943
+ if (binding.name) {
944
+ properties.push(types.objectProperty(types.identifier("name"), types.stringLiteral(binding.name)));
945
+ }
946
+ properties.push(
947
+ types.objectProperty(
948
+ types.identifier("read"),
949
+ types.arrowFunctionExpression(
950
+ parameters.map((parameter) => types.cloneNode(parameter, true)),
951
+ cloneExpression(binding.value)
952
+ )
953
+ )
954
+ );
955
+ return types.objectExpression(properties);
956
+ }
957
+ function uniqueLocalIdentifier(base, nodes) {
958
+ const names = /* @__PURE__ */ new Set();
959
+ for (const node of nodes) {
960
+ types.traverseFast(node, (child) => {
961
+ if (types.isIdentifier(child)) names.add(child.name);
962
+ });
963
+ }
964
+ let name = base;
965
+ let suffix = 2;
966
+ while (names.has(name)) name = `${base}${suffix++}`;
967
+ return types.identifier(name);
968
+ }
969
+ function rewriteKeyedRowEventAttributes(row, events, eventFactory, item, index) {
970
+ const eventByLocation = new Map(
971
+ events.map((event) => [`${event.path.join(".")}:${event.name}`, event])
972
+ );
973
+ const visit = (element, path) => {
974
+ for (const attribute of element.openingElement.attributes) {
975
+ if (!types.isJSXAttribute(attribute)) continue;
976
+ const name = jsxAttributeName(attribute);
977
+ if (!name || !/^on[A-Z]/.test(name)) continue;
978
+ const event = eventByLocation.get(`${path.join(".")}:${name}`);
979
+ if (!event) continue;
980
+ attribute.value = types.jsxExpressionContainer(
981
+ types.callExpression(types.cloneNode(eventFactory), [
982
+ types.cloneNode(item),
983
+ types.cloneNode(index),
984
+ types.numericLiteral(event.id)
985
+ ])
986
+ );
987
+ }
988
+ let elementIndex = 0;
989
+ for (const child of element.children) {
990
+ if (!types.isJSXElement(child)) continue;
991
+ visit(child, [...path, elementIndex]);
992
+ elementIndex += 1;
993
+ }
994
+ };
995
+ visit(row, []);
996
+ }
997
+ function rewriteKeyedRowConditionalExpressions(row, conditionals, conditionalFactory, item, index, renderParameters) {
998
+ const conditionalByLocation = new Map(
999
+ conditionals.map((conditional) => [conditional.path.join("."), conditional])
1000
+ );
1001
+ const visit = (element, path) => {
1002
+ const conditional = conditionalByLocation.get(path.join("."));
1003
+ if (conditional) {
1004
+ element.children = element.children.map((child) => {
1005
+ if (!types.isJSXExpressionContainer(child) || types.isJSXEmptyExpression(child.expression) || !conditionalBlockShape(child.expression)) {
1006
+ return child;
1007
+ }
1008
+ return types.jsxExpressionContainer(
1009
+ types.callExpression(types.cloneNode(conditionalFactory), [
1010
+ types.cloneNode(item),
1011
+ types.cloneNode(index),
1012
+ types.numericLiteral(conditional.id),
1013
+ types.arrowFunctionExpression(
1014
+ renderParameters.map((parameter) => types.cloneNode(parameter, true)),
1015
+ cloneExpression(child.expression)
1016
+ )
1017
+ ])
1018
+ );
1019
+ });
1020
+ return;
1021
+ }
1022
+ let elementIndex = 0;
1023
+ for (const child of element.children) {
1024
+ if (!types.isJSXElement(child)) continue;
1025
+ visit(child, [...path, elementIndex]);
1026
+ elementIndex += 1;
1027
+ }
1028
+ };
1029
+ visit(row, []);
1030
+ }
1031
+ function keyedRowsRenderSource(plan, eventFactory, conditionalFactory) {
1032
+ const source = types.cloneNode(plan.source, true);
1033
+ const child = meaningfulJsxChildren(source)[0];
1034
+ let callback;
1035
+ if (plan.syntax === "map" && types.isJSXExpressionContainer(child) && !types.isJSXEmptyExpression(child.expression) && isMapCall(child.expression)) {
1036
+ const candidate = child.expression.arguments[0];
1037
+ if (types.isArrowFunctionExpression(candidate) || types.isFunctionExpression(candidate)) {
1038
+ callback = candidate;
1039
+ }
1040
+ } else if (plan.syntax === "list" && types.isJSXElement(child)) {
1041
+ const renderChild = meaningfulJsxChildren(child)[0];
1042
+ if (types.isJSXExpressionContainer(renderChild) && !types.isJSXEmptyExpression(renderChild.expression) && (types.isArrowFunctionExpression(renderChild.expression) || types.isFunctionExpression(renderChild.expression))) {
1043
+ callback = renderChild.expression;
1044
+ }
1045
+ }
1046
+ const row = callback && returnedExpression(callback);
1047
+ const item = callback?.params[0];
1048
+ const index = callback?.params[1];
1049
+ if (!callback || !types.isJSXElement(row) || !types.isIdentifier(item)) return source;
1050
+ const runtimeIndex = types.isIdentifier(index) ? index : types.numericLiteral(0);
1051
+ if (plan.events.length > 0) {
1052
+ rewriteKeyedRowEventAttributes(row, plan.events, eventFactory, item, runtimeIndex);
1053
+ }
1054
+ if (plan.conditionals.length > 0) {
1055
+ rewriteKeyedRowConditionalExpressions(
1056
+ row,
1057
+ plan.conditionals,
1058
+ conditionalFactory,
1059
+ item,
1060
+ runtimeIndex,
1061
+ callback.params
1062
+ );
1063
+ }
1064
+ return source;
1065
+ }
1066
+ function keyedRowEventObject(event, renderParameters) {
1067
+ const nodes = [event.value, ...renderParameters];
1068
+ const index = renderParameters[1] ? types.cloneNode(renderParameters[1], true) : uniqueLocalIdentifier("_farmRowIndex", nodes);
1069
+ const syntheticEvent = uniqueLocalIdentifier("_farmEvent", [...nodes, index]);
1070
+ const parameters = [types.cloneNode(renderParameters[0], true), index, syntheticEvent];
1071
+ return types.objectExpression([
1072
+ types.objectProperty(types.identifier("name"), types.stringLiteral(event.name)),
1073
+ types.objectProperty(
1074
+ types.identifier("invoke"),
1075
+ types.arrowFunctionExpression(
1076
+ parameters,
1077
+ types.callExpression(types.parenthesizedExpression(types.cloneNode(event.value, true)), [
1078
+ types.cloneNode(syntheticEvent)
1079
+ ])
1080
+ )
1081
+ )
1082
+ ]);
1083
+ }
1084
+ function keyedRowConditionalBranchObject(bindings, parameters) {
1085
+ return types.objectExpression([
1086
+ types.objectProperty(
1087
+ types.identifier("bindings"),
1088
+ types.arrayExpression(bindings.map((binding) => keyedRowBindingObject(binding, parameters)))
1089
+ )
1090
+ ]);
1091
+ }
1092
+ function keyedRowConditionalObject(conditional, parameters) {
1093
+ const properties = [
1094
+ types.objectProperty(types.identifier("id"), types.numericLiteral(conditional.id)),
1095
+ types.objectProperty(
1096
+ types.identifier("path"),
1097
+ types.arrayExpression(conditional.path.map((part) => types.numericLiteral(part)))
1098
+ ),
1099
+ types.objectProperty(
1100
+ types.identifier("test"),
1101
+ types.arrowFunctionExpression(
1102
+ parameters.map((parameter) => types.cloneNode(parameter, true)),
1103
+ cloneExpression(conditional.test)
1104
+ )
1105
+ ),
1106
+ types.objectProperty(types.identifier("logical"), types.booleanLiteral(conditional.logical))
1107
+ ];
1108
+ if (conditional.truthy) {
1109
+ properties.push(
1110
+ types.objectProperty(
1111
+ types.identifier("truthy"),
1112
+ keyedRowConditionalBranchObject(conditional.truthyBindings, parameters)
1113
+ )
1114
+ );
1115
+ }
1116
+ if (conditional.falsy) {
1117
+ properties.push(
1118
+ types.objectProperty(
1119
+ types.identifier("falsy"),
1120
+ keyedRowConditionalBranchObject(conditional.falsyBindings, parameters)
1121
+ )
1122
+ );
1123
+ }
1124
+ return types.objectExpression(properties);
1125
+ }
1126
+ function keyedRowsBoundary(blockRuntime, plan) {
1127
+ const name = types.jsxMemberExpression(
1128
+ types.jsxIdentifier(blockRuntime.name),
1129
+ types.jsxIdentifier("KeyedRows")
1130
+ );
1131
+ const parameters = plan.renderCallback.params.map(
1132
+ (parameter) => types.cloneNode(parameter, true)
1133
+ );
1134
+ const eventFactory = uniqueLocalIdentifier("_farmRowEvent", [
1135
+ plan.source,
1136
+ ...plan.events.map((event) => event.value)
1137
+ ]);
1138
+ const conditionalFactory = uniqueLocalIdentifier("_farmRowConditional", [
1139
+ plan.source,
1140
+ ...plan.conditionals.map((conditional) => conditional.test)
1141
+ ]);
1142
+ const renderSource = keyedRowsRenderSource(plan, eventFactory, conditionalFactory);
1143
+ const renderFactoryParameters = plan.conditionals.length > 0 ? [types.cloneNode(eventFactory), types.cloneNode(conditionalFactory)] : plan.events.length > 0 ? [types.cloneNode(eventFactory)] : [];
1144
+ return types.jsxElement(
1145
+ types.jsxOpeningElement(
1146
+ name,
1147
+ [
1148
+ types.jsxAttribute(types.jsxIdentifier("id"), types.jsxExpressionContainer(types.numericLiteral(plan.id))),
1149
+ types.jsxAttribute(
1150
+ types.jsxIdentifier("render"),
1151
+ types.jsxExpressionContainer(
1152
+ types.arrowFunctionExpression(renderFactoryParameters, renderSource)
1153
+ )
1154
+ ),
1155
+ types.jsxAttribute(
1156
+ types.jsxIdentifier("items"),
1157
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], cloneExpression(plan.collection)))
1158
+ ),
1159
+ types.jsxAttribute(
1160
+ types.jsxIdentifier("rowKey"),
1161
+ types.jsxExpressionContainer(types.cloneNode(plan.keyCallback, true))
1162
+ ),
1163
+ types.jsxAttribute(
1164
+ types.jsxIdentifier("create"),
1165
+ types.jsxExpressionContainer(
1166
+ types.arrowFunctionExpression(
1167
+ parameters.map((parameter) => types.cloneNode(parameter, true)),
1168
+ hostElementDescriptor(plan.row)
1169
+ )
1170
+ )
1171
+ ),
1172
+ types.jsxAttribute(
1173
+ types.jsxIdentifier("bindings"),
1174
+ types.jsxExpressionContainer(
1175
+ types.arrayExpression(
1176
+ plan.bindings.map((binding) => keyedRowBindingObject(binding, parameters))
1177
+ )
1178
+ )
1179
+ ),
1180
+ ...plan.events.length > 0 ? [
1181
+ types.jsxAttribute(
1182
+ types.jsxIdentifier("events"),
1183
+ types.jsxExpressionContainer(
1184
+ types.arrayExpression(
1185
+ plan.events.map((event) => keyedRowEventObject(event, parameters))
1186
+ )
1187
+ )
1188
+ )
1189
+ ] : [],
1190
+ ...plan.conditionals.length > 0 ? [
1191
+ types.jsxAttribute(
1192
+ types.jsxIdentifier("conditionals"),
1193
+ types.jsxExpressionContainer(
1194
+ types.arrayExpression(
1195
+ plan.conditionals.map(
1196
+ (conditional) => keyedRowConditionalObject(conditional, parameters)
1197
+ )
1198
+ )
1199
+ )
1200
+ )
1201
+ ] : []
1202
+ ],
1203
+ true
1204
+ ),
1205
+ null,
1206
+ [],
1207
+ true
1208
+ );
1209
+ }
1210
+ function hostConditionalBranchObject(branch, bindings) {
1211
+ return types.objectExpression([
1212
+ types.objectProperty(
1213
+ types.identifier("create"),
1214
+ types.arrowFunctionExpression([], hostElementDescriptor(branch))
1215
+ ),
1216
+ types.objectProperty(
1217
+ types.identifier("bindings"),
1218
+ types.arrayExpression(bindings.map((binding) => keyedRowBindingObject(binding, [])))
1219
+ )
1220
+ ]);
1221
+ }
1222
+ function hostConditionalBoundary(blockRuntime, plan) {
1223
+ const name = types.jsxMemberExpression(
1224
+ types.jsxIdentifier(blockRuntime.name),
1225
+ types.jsxIdentifier("HostConditional")
1226
+ );
1227
+ const attributes = [
1228
+ types.jsxAttribute(types.jsxIdentifier("id"), types.jsxExpressionContainer(types.numericLiteral(plan.id))),
1229
+ types.jsxAttribute(
1230
+ types.jsxIdentifier("render"),
1231
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], types.cloneNode(plan.source, true)))
1232
+ ),
1233
+ types.jsxAttribute(
1234
+ types.jsxIdentifier("test"),
1235
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], cloneExpression(plan.test)))
1236
+ )
1237
+ ];
1238
+ if (plan.logical) attributes.push(types.jsxAttribute(types.jsxIdentifier("logical")));
1239
+ if (plan.truthy) {
1240
+ attributes.push(
1241
+ types.jsxAttribute(
1242
+ types.jsxIdentifier("truthy"),
1243
+ types.jsxExpressionContainer(hostConditionalBranchObject(plan.truthy, plan.truthyBindings))
1244
+ )
1245
+ );
1246
+ }
1247
+ if (plan.falsy) {
1248
+ attributes.push(
1249
+ types.jsxAttribute(
1250
+ types.jsxIdentifier("falsy"),
1251
+ types.jsxExpressionContainer(hostConditionalBranchObject(plan.falsy, plan.falsyBindings))
1252
+ )
1253
+ );
1254
+ }
1255
+ return types.jsxElement(types.jsxOpeningElement(name, attributes, true), null, [], true);
1256
+ }
1257
+ function analyzeComponentIslandElement(element, statesByValue, safeGlobals, allowedComponentNames) {
1258
+ const name = element.openingElement.name;
1259
+ if (!types.isJSXIdentifier(name) || !isComponentName(name.name)) {
1260
+ return { reason: "component islands require a direct component identifier" };
1261
+ }
1262
+ if (!allowedComponentNames.has(name.name)) {
1263
+ return {
1264
+ reason: `component island ${name.name} must reference a stable module-level component`
1265
+ };
1266
+ }
1267
+ if (meaningfulJsxChildren(element).length > 0) {
1268
+ return { reason: `component island ${name.name} does not support children yet` };
1269
+ }
1270
+ const dependencies = /* @__PURE__ */ new Set();
1271
+ for (const attribute of element.openingElement.attributes) {
1272
+ if (types.isJSXSpreadAttribute(attribute)) {
1273
+ return { reason: `component island ${name.name} does not support JSX attribute spreads` };
1274
+ }
1275
+ const attributeName = jsxAttributeName(attribute);
1276
+ if (!attributeName) {
1277
+ return { reason: `component island ${name.name} does not support namespaced JSX attributes` };
1278
+ }
1279
+ if (attributeName === "ref" || attributeName === "key" || attributeName === "children") {
1280
+ return { reason: `component island ${name.name} does not support ${attributeName} yet` };
1281
+ }
1282
+ if (!types.isJSXExpressionContainer(attribute.value) || types.isJSXEmptyExpression(attribute.value.expression)) {
1283
+ continue;
1284
+ }
1285
+ const expression = attribute.value.expression;
1286
+ const isEventFunction = /^on[A-Z]/.test(attributeName) && (types.isArrowFunctionExpression(expression) || types.isFunctionExpression(expression));
1287
+ if (!isEventFunction) {
1288
+ const unsupported = validateDerivedExpression(expression, safeGlobals);
1289
+ if (unsupported) {
1290
+ return {
1291
+ reason: `component island ${name.name} prop ${attributeName} cannot use ${unsupported}`
1292
+ };
1293
+ }
1294
+ }
1295
+ collectStateDependencies(expression, statesByValue).forEach(
1296
+ (dependency) => dependencies.add(dependency)
1297
+ );
1298
+ }
1299
+ return { dependencies: [...dependencies].sort((left, right) => left - right) };
1300
+ }
1301
+ function componentIslandBoundary(blockRuntime, plan, source) {
1302
+ const name = types.jsxMemberExpression(
1303
+ types.jsxIdentifier(blockRuntime.name),
1304
+ types.jsxIdentifier("Component")
1305
+ );
1306
+ return types.jsxElement(
1307
+ types.jsxOpeningElement(
1308
+ name,
1309
+ [
1310
+ types.jsxAttribute(types.jsxIdentifier("id"), types.jsxExpressionContainer(types.numericLiteral(plan.id))),
1311
+ types.jsxAttribute(
1312
+ types.jsxIdentifier("render"),
1313
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], types.cloneNode(source, true)))
1314
+ )
1315
+ ],
1316
+ true
1317
+ ),
1318
+ null,
1319
+ [],
1320
+ true
1321
+ );
1322
+ }
1323
+ function analyzeComposableBlocks(root, statesByValue, safeGlobals, listNames, allowedComponentNames) {
1324
+ const plans = [];
1325
+ const componentElements = /* @__PURE__ */ new Set();
1326
+ const conditionalExpressions = /* @__PURE__ */ new Set();
1327
+ const ownedElements = /* @__PURE__ */ new Set();
1328
+ const keyedExpressions = /* @__PURE__ */ new Set();
1329
+ let nextId = 0;
1330
+ const mergeDependencies = (target, dependencies) => {
1331
+ for (const dependency of dependencies) target.add(dependency);
1332
+ };
1333
+ const visitHost = (element, parent, insideConditional) => {
1334
+ const dependencies = /* @__PURE__ */ new Set();
1335
+ if (!isHostElement(element)) {
1336
+ return {
1337
+ dependencies,
1338
+ reason: insideConditional ? "conditional blocks currently support host elements only" : "compiled trees currently support host elements only"
1339
+ };
1340
+ }
1341
+ if (insideConditional) {
1342
+ for (const attribute of element.openingElement.attributes) {
1343
+ if (types.isJSXSpreadAttribute(attribute)) {
1344
+ return {
1345
+ dependencies,
1346
+ reason: "conditional blocks do not support JSX attribute spreads"
1347
+ };
1348
+ }
1349
+ const name = jsxAttributeName(attribute);
1350
+ if (!name) {
1351
+ return {
1352
+ dependencies,
1353
+ reason: "conditional blocks do not support namespaced JSX attributes"
1354
+ };
1355
+ }
1356
+ if (name === "ref" || name === "dangerouslySetInnerHTML") {
1357
+ return {
1358
+ dependencies,
1359
+ reason: `conditional block ${name} requires React ownership`
1360
+ };
1361
+ }
1362
+ if (!types.isJSXExpressionContainer(attribute.value) || types.isJSXEmptyExpression(attribute.value.expression) || /^on[A-Z]/.test(name)) {
1363
+ continue;
1364
+ }
1365
+ const expression = attribute.value.expression;
1366
+ if (name === "style") {
1367
+ if (!types.isObjectExpression(expression)) {
1368
+ return {
1369
+ dependencies,
1370
+ reason: "conditional block styles must use one inline object literal"
1371
+ };
1372
+ }
1373
+ for (const property of expression.properties) {
1374
+ if (!types.isObjectProperty(property) || property.computed) {
1375
+ return {
1376
+ dependencies,
1377
+ reason: "conditional block styles do not support spreads, methods, or computed properties"
1378
+ };
1379
+ }
1380
+ if (!types.isExpression(property.value)) {
1381
+ return {
1382
+ dependencies,
1383
+ reason: "conditional block style properties must use expression values"
1384
+ };
1385
+ }
1386
+ const unsupported2 = validateDerivedExpression(property.value, safeGlobals);
1387
+ if (unsupported2) {
1388
+ return {
1389
+ dependencies,
1390
+ reason: `conditional block style cannot use ${unsupported2}`
1391
+ };
1392
+ }
1393
+ mergeDependencies(
1394
+ dependencies,
1395
+ collectStateDependencies(property.value, statesByValue)
1396
+ );
1397
+ }
1398
+ continue;
1399
+ }
1400
+ const unsupported = validateDerivedExpression(expression, safeGlobals);
1401
+ if (unsupported) {
1402
+ return {
1403
+ dependencies,
1404
+ reason: `conditional block attribute ${name} cannot use ${unsupported}`
1405
+ };
1406
+ }
1407
+ mergeDependencies(dependencies, collectStateDependencies(expression, statesByValue));
1408
+ }
1409
+ }
1410
+ for (const child of element.children) {
1411
+ if (types.isJSXFragment(child)) {
1412
+ if (insideConditional) {
1413
+ return {
1414
+ dependencies,
1415
+ reason: "conditional blocks do not support fragments yet"
1416
+ };
1417
+ }
1418
+ continue;
1419
+ }
1420
+ if (types.isJSXElement(child)) {
1421
+ if (isHostElement(child)) {
1422
+ const hostConditional = analyzeHostConditionalContainer(
1423
+ child,
1424
+ statesByValue,
1425
+ safeGlobals
1426
+ );
1427
+ if (hostConditional) {
1428
+ plans.push({
1429
+ kind: "host-conditional",
1430
+ id: nextId++,
1431
+ parent,
1432
+ source: child,
1433
+ ...hostConditional
1434
+ });
1435
+ ownedElements.add(child);
1436
+ continue;
1437
+ }
1438
+ const keyedRows = analyzeKeyedRowsContainer(child, statesByValue, safeGlobals, listNames);
1439
+ if (keyedRows) {
1440
+ plans.push({
1441
+ kind: "keyed-rows",
1442
+ id: nextId++,
1443
+ parent,
1444
+ dependencies: keyedRows.dependencies,
1445
+ source: child,
1446
+ collection: keyedRows.collection,
1447
+ keyCallback: keyedRows.keyCallback,
1448
+ renderCallback: keyedRows.renderCallback,
1449
+ row: keyedRows.row,
1450
+ bindings: keyedRows.bindings,
1451
+ events: keyedRows.events,
1452
+ conditionals: keyedRows.conditionals,
1453
+ syntax: keyedRows.syntax
1454
+ });
1455
+ ownedElements.add(child);
1456
+ continue;
1457
+ }
1458
+ const nested = visitHost(child, parent, insideConditional);
1459
+ if (nested.reason) return { dependencies, reason: nested.reason };
1460
+ mergeDependencies(dependencies, nested.dependencies);
1461
+ continue;
1462
+ }
1463
+ if (isPublicListElement(child, listNames)) {
1464
+ const result3 = analyzePublicList(child, statesByValue, safeGlobals);
1465
+ if (result3.reason) return { dependencies, reason: result3.reason };
1466
+ const plan = {
1467
+ kind: "keyed-list",
1468
+ id: nextId++,
1469
+ parent,
1470
+ dependencies: result3.dependencies || [],
1471
+ source: child,
1472
+ syntax: "list"
1473
+ };
1474
+ plans.push(plan);
1475
+ ownedElements.add(child);
1476
+ continue;
1477
+ }
1478
+ const result2 = analyzeComponentIslandElement(
1479
+ child,
1480
+ statesByValue,
1481
+ safeGlobals,
1482
+ allowedComponentNames
1483
+ );
1484
+ if (result2.reason) return { dependencies, reason: result2.reason };
1485
+ componentElements.add(child);
1486
+ if ((result2.dependencies || []).length > 0) {
1487
+ plans.push({
1488
+ kind: "component",
1489
+ id: nextId++,
1490
+ parent,
1491
+ dependencies: result2.dependencies || [],
1492
+ source: child
1493
+ });
1494
+ }
1495
+ continue;
1496
+ }
1497
+ if (!types.isJSXExpressionContainer(child) || types.isJSXEmptyExpression(child.expression)) {
1498
+ continue;
1499
+ }
1500
+ const expression = child.expression;
1501
+ const shape = conditionalBlockShape(expression);
1502
+ if (shape) {
1503
+ const unsupported = validateDerivedExpression(shape.test, safeGlobals);
1504
+ if (unsupported) {
1505
+ return {
1506
+ dependencies,
1507
+ reason: `conditional block test cannot use ${unsupported}`
1508
+ };
1509
+ }
1510
+ const plan = {
1511
+ kind: "conditional",
1512
+ id: nextId++,
1513
+ parent,
1514
+ dependencies: collectStateDependencies(shape.test, statesByValue),
1515
+ source: expression
1516
+ };
1517
+ plans.push(plan);
1518
+ conditionalExpressions.add(expression);
1519
+ const ownedDependencies = new Set(plan.dependencies);
1520
+ for (const branch of shape.branches) {
1521
+ const nested = visitHost(branch, plan.id, true);
1522
+ if (nested.reason) return { dependencies, reason: nested.reason };
1523
+ mergeDependencies(ownedDependencies, nested.dependencies);
1524
+ }
1525
+ plan.dependencies = [...ownedDependencies].sort((left, right) => left - right);
1526
+ continue;
1527
+ }
1528
+ if (isMapCall(expression)) {
1529
+ const result2 = analyzeMapList(expression, statesByValue, safeGlobals);
1530
+ if (result2.reason) return { dependencies, reason: result2.reason };
1531
+ plans.push({
1532
+ kind: "keyed-list",
1533
+ id: nextId++,
1534
+ parent,
1535
+ dependencies: result2.dependencies || [],
1536
+ source: expression,
1537
+ syntax: "map"
1538
+ });
1539
+ keyedExpressions.add(expression);
1540
+ continue;
1541
+ }
1542
+ if (insideConditional) {
1543
+ if (!isTextExpression(expression, statesByValue, safeGlobals)) {
1544
+ return {
1545
+ dependencies,
1546
+ reason: "conditional block children must keep one supported block or static host tree"
1547
+ };
1548
+ }
1549
+ mergeDependencies(dependencies, collectStateDependencies(expression, statesByValue));
39
1550
  }
40
1551
  }
41
- });
42
- return referenced;
1552
+ return { dependencies };
1553
+ };
1554
+ const result = visitHost(root, void 0, false);
1555
+ if (result.reason) return { reason: result.reason };
1556
+ return {
1557
+ plans,
1558
+ componentElements,
1559
+ conditionalExpressions,
1560
+ ownedElements,
1561
+ keyedExpressions
1562
+ };
43
1563
  }
44
- function findContainingEvent(path) {
45
- const attribute = path.findParent((parent) => parent.isJSXAttribute());
46
- if (!attribute?.isJSXAttribute() || !types.isJSXIdentifier(attribute.node.name)) return null;
47
- return /^on[A-Z]/.test(attribute.node.name.name) ? attribute : null;
1564
+ function conditionalBlockBoundary(blockRuntime, plan, source) {
1565
+ const name = types.jsxMemberExpression(
1566
+ types.jsxIdentifier(blockRuntime.name),
1567
+ types.jsxIdentifier("Conditional")
1568
+ );
1569
+ return types.jsxElement(
1570
+ types.jsxOpeningElement(
1571
+ name,
1572
+ [
1573
+ types.jsxAttribute(types.jsxIdentifier("id"), types.jsxExpressionContainer(types.numericLiteral(plan.id))),
1574
+ types.jsxAttribute(
1575
+ types.jsxIdentifier("render"),
1576
+ types.jsxExpressionContainer(types.arrowFunctionExpression([], cloneExpression(source)))
1577
+ )
1578
+ ],
1579
+ true
1580
+ ),
1581
+ null,
1582
+ [],
1583
+ true
1584
+ );
48
1585
  }
49
- function validateSetterUsage(root, statesBySetter) {
50
- const file = expressionFile(types.cloneNode(root, true));
51
- let reason;
52
- traverse(file, {
53
- ReferencedIdentifier(path) {
54
- if (reason) return;
55
- const state = statesBySetter.get(path.node.name);
56
- if (!state || path.scope.hasBinding(path.node.name)) return;
57
- const parent = path.parentPath;
58
- if (!parent.isCallExpression() || parent.node.callee !== path.node || !findContainingEvent(parent)) {
59
- reason = `${state.setterName} must be called inside a JSX event handler`;
1586
+ function lowerComposableBlocks(root, plans, blockRuntime, listNames) {
1587
+ const planBySource = new Map(
1588
+ plans.map((plan) => [plan.source, plan])
1589
+ );
1590
+ const visit = (element) => {
1591
+ element.children = element.children.map((child) => {
1592
+ if (types.isJSXElement(child)) {
1593
+ const plan2 = planBySource.get(child);
1594
+ if (plan2?.kind === "host-conditional") {
1595
+ return hostConditionalBoundary(blockRuntime, plan2);
1596
+ }
1597
+ if (plan2?.kind === "keyed-rows") {
1598
+ return keyedRowsBoundary(blockRuntime, plan2);
1599
+ }
1600
+ if (plan2?.kind === "component") {
1601
+ return componentIslandBoundary(blockRuntime, plan2, child);
1602
+ }
1603
+ if (plan2?.kind === "keyed-list" && isPublicListElement(child, listNames)) {
1604
+ return keyedListBoundary(blockRuntime, plan2, child);
1605
+ }
1606
+ if (isHostElement(child)) visit(child);
1607
+ return child;
1608
+ }
1609
+ if (!types.isJSXExpressionContainer(child) || types.isJSXEmptyExpression(child.expression)) {
1610
+ return child;
1611
+ }
1612
+ const plan = planBySource.get(child.expression);
1613
+ if (plan?.kind === "conditional") {
1614
+ const shape = conditionalBlockShape(child.expression);
1615
+ for (const branch of shape?.branches || []) visit(branch);
1616
+ return conditionalBlockBoundary(blockRuntime, plan, child.expression);
1617
+ }
1618
+ if (plan?.kind === "keyed-list") {
1619
+ return keyedListBoundary(blockRuntime, plan, child.expression);
60
1620
  }
1621
+ return child;
1622
+ });
1623
+ };
1624
+ visit(root);
1625
+ return root;
1626
+ }
1627
+ function assignStableBindingTargets(bindings) {
1628
+ const targetByPath = /* @__PURE__ */ new Map();
1629
+ for (const binding of bindings) {
1630
+ if (binding.kind === "block") continue;
1631
+ const key = binding.path.join(".");
1632
+ let target = targetByPath.get(key);
1633
+ if (target === void 0) {
1634
+ target = targetByPath.size;
1635
+ targetByPath.set(key, target);
61
1636
  }
62
- });
63
- return reason;
1637
+ binding.target = target;
1638
+ }
64
1639
  }
65
- function rewriteStateAccess(expression, stateParameter, statesByValue, statesBySetter) {
66
- const file = expressionFile(cloneExpression(expression));
67
- traverse(file, {
68
- CallExpression(path) {
69
- const callee = path.get("callee");
70
- if (!callee.isIdentifier() || callee.scope.hasBinding(callee.node.name)) return;
71
- const state = statesBySetter.get(callee.node.name);
72
- if (!state) return;
73
- callee.replaceWith(
74
- types.memberExpression(
75
- types.memberExpression(types.cloneNode(stateParameter), types.numericLiteral(state.index), true),
76
- types.identifier("set")
77
- )
78
- );
79
- },
80
- ReferencedIdentifier(path) {
81
- const state = statesByValue.get(path.node.name);
82
- if (!state || path.scope.hasBinding(path.node.name)) return;
83
- path.replaceWith(
84
- types.callExpression(
85
- types.memberExpression(
86
- types.memberExpression(types.cloneNode(stateParameter), types.numericLiteral(state.index), true),
87
- types.identifier("get")
88
- ),
89
- []
1640
+ function lowerStableBindingTargets(root, bindings, blockRuntime, ownedElements) {
1641
+ const targetByPath = /* @__PURE__ */ new Map();
1642
+ for (const binding of bindings) {
1643
+ if (binding.kind !== "block" && binding.target !== void 0) {
1644
+ targetByPath.set(binding.path.join("."), binding.target);
1645
+ }
1646
+ }
1647
+ const visit = (element, path) => {
1648
+ const target = targetByPath.get(path.join("."));
1649
+ if (target !== void 0 && path.length > 0) {
1650
+ element.openingElement.attributes.push(
1651
+ types.jsxAttribute(
1652
+ types.jsxIdentifier("ref"),
1653
+ types.jsxExpressionContainer(
1654
+ types.callExpression(
1655
+ types.memberExpression(types.cloneNode(blockRuntime), types.identifier("target")),
1656
+ [types.numericLiteral(target)]
1657
+ )
1658
+ )
90
1659
  )
91
1660
  );
92
1661
  }
93
- });
94
- return file.program.body[0].expression;
95
- }
96
- function cleanJsxText(value) {
97
- const lines = value.replace(/\r/g, "").split("\n");
98
- let result = "";
99
- for (let index = 0; index < lines.length; index += 1) {
100
- const lastNonEmptyLine = lines.slice(index + 1).every((line2) => line2.trim().length === 0);
101
- let line = lines[index].replace(/\t/g, " ").replace(/ +/g, " ");
102
- if (index > 0) line = line.replace(/^ /, "");
103
- if (!lastNonEmptyLine) line = line.replace(/ $/, "");
104
- if (line) result += line + (!lastNonEmptyLine ? " " : "");
105
- }
106
- return result;
107
- }
108
- function isTextExpression(expression) {
109
- let supported = true;
110
- traverse(expressionFile(cloneExpression(expression)), {
111
- CallExpression(path) {
112
- supported = false;
113
- path.stop();
114
- },
115
- OptionalCallExpression(path) {
116
- supported = false;
117
- path.stop();
118
- },
119
- JSXElement(path) {
120
- supported = false;
121
- path.stop();
122
- },
123
- JSXFragment(path) {
124
- supported = false;
125
- path.stop();
126
- },
127
- ObjectExpression(path) {
128
- supported = false;
129
- path.stop();
130
- },
131
- Function(path) {
132
- supported = false;
133
- path.stop();
1662
+ let elementIndex = 0;
1663
+ for (const child of element.children) {
1664
+ if (!types.isJSXElement(child) || !isHostElement(child)) continue;
1665
+ if (ownedElements.has(child)) continue;
1666
+ visit(child, [...path, elementIndex]);
1667
+ elementIndex += 1;
134
1668
  }
135
- });
136
- return supported;
137
- }
138
- function jsxAttributeName(attribute) {
139
- return types.isJSXIdentifier(attribute.name) ? attribute.name.name : void 0;
1669
+ };
1670
+ visit(root, []);
1671
+ return root;
140
1672
  }
141
- function analyzeHostTree(root, statesByValue) {
1673
+ function analyzeHostTree(root, statesByValue, safeGlobals, conditionalExpressions, keyedExpressions, ownedElements, componentElements) {
142
1674
  const bindings = [];
1675
+ const analyzeStyle = (expression, path) => {
1676
+ if (!types.isObjectExpression(expression)) {
1677
+ return "stateful style bindings must use one inline object literal";
1678
+ }
1679
+ for (const property of expression.properties) {
1680
+ if (!types.isObjectProperty(property) || property.computed) {
1681
+ return "stateful style bindings do not support spreads, methods, or computed properties";
1682
+ }
1683
+ const name = types.isIdentifier(property.key) ? property.key.name : types.isStringLiteral(property.key) ? property.key.value : void 0;
1684
+ if (!name || name.includes("-") && !name.startsWith("--")) {
1685
+ return "stateful style property names must use camelCase or a CSS custom property";
1686
+ }
1687
+ if (!types.isExpression(property.value)) {
1688
+ return `stateful style property ${name} must use an expression value`;
1689
+ }
1690
+ const dependencies = collectStateDependencies(property.value, statesByValue);
1691
+ if (dependencies.length === 0) continue;
1692
+ const unsupported = validateDerivedExpression(property.value, safeGlobals);
1693
+ if (unsupported) return `stateful style property ${name} cannot use ${unsupported}`;
1694
+ bindings.push({
1695
+ kind: "style",
1696
+ path: [...path],
1697
+ dependencies,
1698
+ name,
1699
+ value: cloneExpression(property.value)
1700
+ });
1701
+ }
1702
+ return void 0;
1703
+ };
143
1704
  const visit = (element, path) => {
144
1705
  const tag = element.openingElement.name;
145
1706
  if (!types.isJSXIdentifier(tag) || !/^[a-z]/.test(tag.name)) {
@@ -158,9 +1719,16 @@ function analyzeHostTree(root, statesByValue) {
158
1719
  const expression = attribute.value.expression;
159
1720
  const dependencies = collectStateDependencies(expression, statesByValue);
160
1721
  if (dependencies.length === 0 || /^on[A-Z]/.test(name)) continue;
161
- if (name === "style" || name === "children" || name === "key") {
1722
+ if (name === "style") {
1723
+ const reason2 = analyzeStyle(expression, path);
1724
+ if (reason2) return reason2;
1725
+ continue;
1726
+ }
1727
+ if (name === "children" || name === "key") {
162
1728
  return `stateful ${name} bindings are not supported yet`;
163
1729
  }
1730
+ const unsupported = validateDerivedExpression(expression, safeGlobals);
1731
+ if (unsupported) return `stateful ${name} binding cannot use ${unsupported}`;
164
1732
  bindings.push({
165
1733
  kind: "attribute",
166
1734
  path: [...path],
@@ -170,18 +1738,29 @@ function analyzeHostTree(root, statesByValue) {
170
1738
  });
171
1739
  }
172
1740
  const nestedElements = element.children.filter(
173
- (child) => types.isJSXElement(child)
1741
+ (child) => types.isJSXElement(child) && isHostElement(child) && !ownedElements.has(child)
174
1742
  );
175
1743
  const expressionChildren = element.children.filter(
176
1744
  (child) => types.isJSXExpressionContainer(child)
177
1745
  );
178
1746
  for (const child of expressionChildren) {
179
1747
  if (types.isJSXEmptyExpression(child.expression)) continue;
180
- if (!isTextExpression(child.expression)) {
1748
+ if (conditionalExpressions.has(child.expression)) continue;
1749
+ if (keyedExpressions.has(child.expression)) continue;
1750
+ if (!isTextExpression(child.expression, statesByValue, safeGlobals)) {
181
1751
  return "dynamic child structures require React reconciliation";
182
1752
  }
183
1753
  }
184
- if (nestedElements.length === 0) {
1754
+ const hasOwnedElement = element.children.some(
1755
+ (child) => types.isJSXElement(child) && ownedElements.has(child)
1756
+ );
1757
+ const hasComponentElement = element.children.some(
1758
+ (child) => types.isJSXElement(child) && componentElements.has(child)
1759
+ );
1760
+ const hasDynamicBlocks = hasOwnedElement || hasComponentElement || expressionChildren.some(
1761
+ (child) => !types.isJSXEmptyExpression(child.expression) && (conditionalExpressions.has(child.expression) || keyedExpressions.has(child.expression))
1762
+ );
1763
+ if (nestedElements.length === 0 && !hasDynamicBlocks) {
185
1764
  const dependencies = /* @__PURE__ */ new Set();
186
1765
  const parts = [];
187
1766
  for (const child of element.children) {
@@ -205,13 +1784,13 @@ function analyzeHostTree(root, statesByValue) {
205
1784
  }
206
1785
  } else {
207
1786
  for (const child of expressionChildren) {
208
- if (!types.isJSXEmptyExpression(child.expression) && collectStateDependencies(child.expression, statesByValue).length > 0) {
1787
+ if (!types.isJSXEmptyExpression(child.expression) && !conditionalExpressions.has(child.expression) && !keyedExpressions.has(child.expression) && collectStateDependencies(child.expression, statesByValue).length > 0) {
209
1788
  return "mixed element and stateful text children are not supported yet";
210
1789
  }
211
1790
  }
212
1791
  let elementIndex = 0;
213
1792
  for (const child of element.children) {
214
- if (!types.isJSXElement(child)) continue;
1793
+ if (!types.isJSXElement(child) || !isHostElement(child) || ownedElements.has(child)) continue;
215
1794
  const reason2 = visit(child, [...path, elementIndex]);
216
1795
  if (reason2) return reason2;
217
1796
  elementIndex += 1;
@@ -246,29 +1825,94 @@ function lazyInitialValue(expression) {
246
1825
  []
247
1826
  );
248
1827
  }
249
- function clonePropsParameter(path) {
1828
+ function analyzePropsParameter(path) {
250
1829
  const parameter = path.node.params[0];
251
- return parameter && types.isIdentifier(parameter) ? types.cloneNode(parameter, true) : types.identifier("_props");
1830
+ if (!parameter) {
1831
+ return {
1832
+ definitionParameter: path.scope.generateUidIdentifier("props"),
1833
+ destructuredNames: /* @__PURE__ */ new Set()
1834
+ };
1835
+ }
1836
+ if (types.isIdentifier(parameter)) {
1837
+ return {
1838
+ definitionParameter: types.cloneNode(parameter, true),
1839
+ wrapperProps: types.cloneNode(parameter, true),
1840
+ destructuredNames: /* @__PURE__ */ new Set()
1841
+ };
1842
+ }
1843
+ if (!types.isObjectPattern(parameter)) {
1844
+ return "components must use zero parameters, one props identifier, or flat object props destructuring";
1845
+ }
1846
+ const localNames = /* @__PURE__ */ new Set();
1847
+ for (const property of parameter.properties) {
1848
+ if (types.isRestElement(property)) {
1849
+ return "rest properties in component props destructuring are not supported yet";
1850
+ }
1851
+ if (property.computed) {
1852
+ return "computed component props destructuring is not supported yet";
1853
+ }
1854
+ let local;
1855
+ if (types.isIdentifier(property.value)) {
1856
+ local = property.value;
1857
+ } else if (types.isAssignmentPattern(property.value) && types.isIdentifier(property.value.left)) {
1858
+ local = property.value.left;
1859
+ }
1860
+ if (!local) {
1861
+ return "nested component props destructuring is not supported yet";
1862
+ }
1863
+ if (localNames.has(local.name)) {
1864
+ return "component props destructuring local names must be unique";
1865
+ }
1866
+ localNames.add(local.name);
1867
+ }
1868
+ const definitionParameter = path.scope.generateUidIdentifier("props");
1869
+ return {
1870
+ definitionParameter,
1871
+ wrapperProps: types.objectExpression(
1872
+ [...localNames].map(
1873
+ (name) => types.objectProperty(types.identifier(name), types.identifier(name), false, true)
1874
+ )
1875
+ ),
1876
+ destructuredNames: localNames
1877
+ };
252
1878
  }
253
- function bindingObject(binding, componentPath, stateParameter, statesByValue, statesBySetter) {
1879
+ function bindingObject(binding, propsParameter, stateParameter, statesByValue, statesBySetter) {
254
1880
  const properties = [
255
1881
  types.objectProperty(types.identifier("kind"), types.stringLiteral(binding.kind)),
256
- types.objectProperty(
257
- types.identifier("path"),
258
- types.arrayExpression(binding.path.map((part) => types.numericLiteral(part)))
259
- ),
260
1882
  types.objectProperty(
261
1883
  types.identifier("dependencies"),
262
1884
  types.arrayExpression(binding.dependencies.map((part) => types.numericLiteral(part)))
263
1885
  )
264
1886
  ];
1887
+ if (binding.kind === "block") {
1888
+ properties.push(types.objectProperty(types.identifier("id"), types.numericLiteral(binding.id)));
1889
+ if (binding.parent !== void 0) {
1890
+ properties.push(types.objectProperty(types.identifier("parent"), types.numericLiteral(binding.parent)));
1891
+ }
1892
+ return types.objectExpression(properties);
1893
+ }
1894
+ properties.splice(
1895
+ 1,
1896
+ 0,
1897
+ types.objectProperty(
1898
+ types.identifier("path"),
1899
+ types.arrayExpression(binding.path.map((part) => types.numericLiteral(part)))
1900
+ )
1901
+ );
1902
+ if (binding.target !== void 0) {
1903
+ properties.splice(
1904
+ 2,
1905
+ 0,
1906
+ types.objectProperty(types.identifier("target"), types.numericLiteral(binding.target))
1907
+ );
1908
+ }
265
1909
  if (binding.name)
266
1910
  properties.push(types.objectProperty(types.identifier("name"), types.stringLiteral(binding.name)));
267
1911
  properties.push(
268
1912
  types.objectProperty(
269
1913
  types.identifier("read"),
270
1914
  types.arrowFunctionExpression(
271
- [clonePropsParameter(componentPath), types.cloneNode(stateParameter)],
1915
+ [types.cloneNode(propsParameter), types.cloneNode(stateParameter)],
272
1916
  rewriteStateAccess(binding.value, stateParameter, statesByValue, statesBySetter)
273
1917
  )
274
1918
  )
@@ -280,16 +1924,20 @@ function wrapperElement(definitionIdentifier, props) {
280
1924
  const attributes = props ? [types.jsxSpreadAttribute(types.cloneNode(props))] : [];
281
1925
  return types.jsxElement(types.jsxOpeningElement(name, attributes, true), null, [], true);
282
1926
  }
283
- function compileCandidate(candidate, createComponentIdentifier, useStateNames, reactNames) {
1927
+ function compileCandidate(candidate, createComponentIdentifier, useStateNames, reactNames, listNames, moduleId) {
284
1928
  const { path, name, statementPath } = candidate;
285
1929
  if (path.node.async || path.node.generator)
286
1930
  return "async and generator components are not supported";
287
1931
  if (path.node.typeParameters) return "generic components are not supported yet";
288
- if (path.node.params.length > 1 || path.node.params[0] && !types.isIdentifier(path.node.params[0])) {
289
- return "components must use zero parameters or one props identifier";
290
- }
1932
+ if (path.node.params.length > 1) return "components must use at most one props parameter";
1933
+ const propsPlan = analyzePropsParameter(path);
1934
+ if (typeof propsPlan === "string") return propsPlan;
291
1935
  if (!types.isBlockStatement(path.node.body)) return "components must use a block body";
1936
+ const safeGlobals = new Set(
1937
+ [...SAFE_GLOBAL_CALLS, "Math"].filter((name2) => !path.scope.getBinding(name2))
1938
+ );
292
1939
  const states = [];
1940
+ const locals = [];
293
1941
  let returned;
294
1942
  for (const statement of path.node.body.body) {
295
1943
  if (types.isReturnStatement(statement)) {
@@ -297,19 +1945,48 @@ function compileCandidate(candidate, createComponentIdentifier, useStateNames, r
297
1945
  returned = statement;
298
1946
  continue;
299
1947
  }
300
- if (!types.isVariableDeclaration(statement) || statement.declarations.length !== 1) {
301
- return "only top-level useState declarations and one return are supported in Group 1";
1948
+ if (types.isFunctionDeclaration(statement)) {
1949
+ if (!statement.id) return "named event handler declarations must have a name";
1950
+ if (statement.async || statement.generator || statement.typeParameters) {
1951
+ return `event handler ${statement.id.name} must be synchronous and non-generic`;
1952
+ }
1953
+ locals.push({
1954
+ kind: "handler",
1955
+ name: statement.id.name,
1956
+ value: types.toExpression(types.cloneNode(statement, true))
1957
+ });
1958
+ continue;
1959
+ }
1960
+ if (!types.isVariableDeclaration(statement) || statement.kind !== "const" || statement.declarations.length !== 1) {
1961
+ return "only top-level useState declarations, pure derived const values, named synchronous handlers, and one return are supported by the current compiler";
302
1962
  }
303
1963
  const declaration = statement.declarations[0];
304
- if (statement.kind !== "const" || !types.isArrayPattern(declaration.id) || declaration.id.elements.length !== 2 || !types.isIdentifier(declaration.id.elements[0]) || !types.isIdentifier(declaration.id.elements[1]) || !isUseStateCall(declaration.init, useStateNames, reactNames) || declaration.init.arguments.length > 1 || declaration.init.arguments[0] && !types.isExpression(declaration.init.arguments[0])) {
305
- return "only const [value, setValue] = useState(initial) declarations are supported";
306
- }
307
- states.push({
308
- valueName: declaration.id.elements[0].name,
309
- setterName: declaration.id.elements[1].name,
310
- index: states.length,
311
- initialValue: declaration.init.arguments[0]
312
- });
1964
+ if (types.isArrayPattern(declaration.id)) {
1965
+ if (declaration.id.elements.length !== 2 || !types.isIdentifier(declaration.id.elements[0]) || !types.isIdentifier(declaration.id.elements[1]) || !isUseStateCall(declaration.init, useStateNames, reactNames) || declaration.init.arguments.length > 1 || declaration.init.arguments[0] && !types.isExpression(declaration.init.arguments[0])) {
1966
+ return "only const [value, setValue] = useState(initial) state declarations are supported";
1967
+ }
1968
+ if (locals.length > 0) {
1969
+ return "useState declarations must appear before derived local values and event handlers";
1970
+ }
1971
+ states.push({
1972
+ valueName: declaration.id.elements[0].name,
1973
+ setterName: declaration.id.elements[1].name,
1974
+ index: states.length,
1975
+ initialValue: declaration.init.arguments[0]
1976
+ });
1977
+ continue;
1978
+ }
1979
+ if (!types.isIdentifier(declaration.id) || !types.isExpression(declaration.init)) {
1980
+ return "derived const values and event handlers must use one identifier and one expression";
1981
+ }
1982
+ if (types.isArrowFunctionExpression(declaration.init) || types.isFunctionExpression(declaration.init)) {
1983
+ if (declaration.init.async || declaration.init.generator) {
1984
+ return `event handler ${declaration.id.name} must be synchronous`;
1985
+ }
1986
+ locals.push({ kind: "handler", name: declaration.id.name, value: declaration.init });
1987
+ } else {
1988
+ locals.push({ kind: "derived", name: declaration.id.name, value: declaration.init });
1989
+ }
313
1990
  }
314
1991
  if (states.length === 0) return "no local useState binding was found";
315
1992
  if (!returned?.argument || !types.isJSXElement(returned.argument)) {
@@ -317,22 +1994,105 @@ function compileCandidate(candidate, createComponentIdentifier, useStateNames, r
317
1994
  }
318
1995
  const statesByValue = new Map(states.map((state) => [state.valueName, state]));
319
1996
  const statesBySetter = new Map(states.map((state) => [state.setterName, state]));
1997
+ const localNames = new Set(locals.map((binding) => binding.name));
1998
+ if (localNames.size !== locals.length) return "local binding names must be unique";
320
1999
  for (const state of states) {
321
- if (state.initialValue && (collectStateDependencies(state.initialValue, statesByValue).length > 0 || [...statesBySetter].some(
2000
+ if (state.initialValue && (collectStateDependencies(state.initialValue, statesByValue).length > 0 || collectReferencedLocals(state.initialValue, localNames).length > 0 || [...statesBySetter].some(
322
2001
  ([setterName]) => referencesIdentifier(state.initialValue, setterName)
323
2002
  ))) {
324
- return "useState initializers cannot reference another local state binding";
2003
+ return "useState initializers cannot reference another local binding";
2004
+ }
2005
+ }
2006
+ const derivedByName = /* @__PURE__ */ new Map();
2007
+ const handlersByName = /* @__PURE__ */ new Map();
2008
+ for (const binding of locals) {
2009
+ const forwardReferences = collectReferencedLocals(binding.value, localNames).filter(
2010
+ (reference) => !derivedByName.has(reference)
2011
+ );
2012
+ if (forwardReferences.length > 0) {
2013
+ return `${binding.kind === "handler" ? "event handler" : "derived local"} ${binding.name} can only reference earlier derived local values`;
2014
+ }
2015
+ const expanded = rewriteDerivedAccess(binding.value, derivedByName);
2016
+ if (binding.kind === "handler") {
2017
+ handlersByName.set(binding.name, {
2018
+ name: binding.name,
2019
+ value: expanded
2020
+ });
2021
+ continue;
2022
+ }
2023
+ const unsupported = collectionPipelineMethod(expanded) ? validateCollectionPipeline(expanded, safeGlobals) : validateDerivedExpression(expanded, safeGlobals);
2024
+ if (unsupported) {
2025
+ return `derived local ${binding.name} cannot use ${unsupported}`;
325
2026
  }
2027
+ derivedByName.set(binding.name, expanded);
326
2028
  }
327
- const setterReason = validateSetterUsage(returned.argument, statesBySetter);
2029
+ const rootWithDerived = rewriteDerivedAccess(returned.argument, derivedByName);
2030
+ const handlerRewrite = rewriteHandlerAccess(rootWithDerived, handlersByName);
2031
+ if (handlerRewrite.reason) return handlerRewrite.reason;
2032
+ if (!handlerRewrite.root) return "event handlers could not be prepared safely";
2033
+ const expandedRoot = rewriteDestructuredPropAccess(
2034
+ handlerRewrite.root,
2035
+ propsPlan.destructuredNames,
2036
+ propsPlan.definitionParameter
2037
+ );
2038
+ const setterReason = validateSetterUsage(expandedRoot, statesBySetter);
328
2039
  if (setterReason) return setterReason;
329
- const analysis = analyzeHostTree(returned.argument, statesByValue);
2040
+ const referencedComponentNames = /* @__PURE__ */ new Set();
2041
+ traverse(expressionFile(types.cloneNode(expandedRoot, true)), {
2042
+ JSXOpeningElement(componentPath) {
2043
+ const componentName = componentPath.node.name;
2044
+ if (types.isJSXIdentifier(componentName) && isComponentName(componentName.name)) {
2045
+ referencedComponentNames.add(componentName.name);
2046
+ }
2047
+ }
2048
+ });
2049
+ const allowedComponentNames = new Set(
2050
+ [...referencedComponentNames].filter((componentName) => {
2051
+ if (listNames.has(componentName)) return false;
2052
+ const binding = path.scope.getBinding(componentName);
2053
+ return Boolean(
2054
+ binding?.constant && binding.scope.path.isProgram() && binding.kind !== "let" && binding.kind !== "var"
2055
+ );
2056
+ })
2057
+ );
2058
+ const blockAnalysis = analyzeComposableBlocks(
2059
+ expandedRoot,
2060
+ statesByValue,
2061
+ safeGlobals,
2062
+ listNames,
2063
+ allowedComponentNames
2064
+ );
2065
+ if (blockAnalysis.reason) return blockAnalysis.reason;
2066
+ const blockPlans = blockAnalysis.plans || [];
2067
+ const analysis = analyzeHostTree(
2068
+ expandedRoot,
2069
+ statesByValue,
2070
+ safeGlobals,
2071
+ blockAnalysis.conditionalExpressions || /* @__PURE__ */ new Set(),
2072
+ blockAnalysis.keyedExpressions || /* @__PURE__ */ new Set(),
2073
+ blockAnalysis.ownedElements || /* @__PURE__ */ new Set(),
2074
+ blockAnalysis.componentElements || /* @__PURE__ */ new Set()
2075
+ );
330
2076
  if (analysis.reason) return analysis.reason;
2077
+ assignStableBindingTargets(analysis.bindings || []);
331
2078
  const stateParameter = path.scope.generateUidIdentifier("farmState");
2079
+ const blockParameter = path.scope.generateUidIdentifier("farmBlocks");
332
2080
  const definitionIdentifier = path.scope.generateUidIdentifier(`${name}Compiled`);
333
- const propsParameter = clonePropsParameter(path);
2081
+ const propsParameter = propsPlan.definitionParameter;
2082
+ const rootWithTargets = lowerStableBindingTargets(
2083
+ expandedRoot,
2084
+ analysis.bindings || [],
2085
+ blockParameter,
2086
+ blockAnalysis.ownedElements || /* @__PURE__ */ new Set()
2087
+ );
2088
+ const rootWithBlocks = lowerComposableBlocks(
2089
+ rootWithTargets,
2090
+ blockPlans,
2091
+ blockParameter,
2092
+ listNames
2093
+ );
334
2094
  const rewrittenRoot = rewriteStateAccess(
335
- returned.argument,
2095
+ rootWithBlocks,
336
2096
  stateParameter,
337
2097
  statesByValue,
338
2098
  statesBySetter
@@ -340,39 +2100,67 @@ function compileCandidate(candidate, createComponentIdentifier, useStateNames, r
340
2100
  const definition = types.callExpression(types.cloneNode(createComponentIdentifier), [
341
2101
  types.objectExpression([
342
2102
  types.objectProperty(types.identifier("displayName"), types.stringLiteral(name)),
2103
+ types.spreadElement(
2104
+ types.conditionalExpression(
2105
+ types.memberExpression(
2106
+ types.metaProperty(types.identifier("import"), types.identifier("meta")),
2107
+ types.identifier("hot")
2108
+ ),
2109
+ types.objectExpression([
2110
+ types.objectProperty(types.identifier("hmrId"), types.stringLiteral(`${moduleId}#${name}`)),
2111
+ types.objectProperty(
2112
+ types.identifier("stateSignature"),
2113
+ types.stringLiteral(String(states.length))
2114
+ )
2115
+ ]),
2116
+ types.objectExpression([])
2117
+ )
2118
+ ),
343
2119
  types.objectProperty(
344
2120
  types.identifier("initialize"),
345
2121
  types.arrowFunctionExpression(
346
2122
  [types.cloneNode(propsParameter)],
347
- types.arrayExpression(states.map((state) => lazyInitialValue(state.initialValue)))
2123
+ types.arrayExpression(
2124
+ states.map(
2125
+ (state) => lazyInitialValue(
2126
+ state.initialValue ? rewriteDestructuredPropAccess(
2127
+ state.initialValue,
2128
+ propsPlan.destructuredNames,
2129
+ propsParameter
2130
+ ) : void 0
2131
+ )
2132
+ )
2133
+ )
348
2134
  )
349
2135
  ),
350
2136
  types.objectProperty(
351
2137
  types.identifier("render"),
352
2138
  types.arrowFunctionExpression(
353
- [types.cloneNode(propsParameter), types.cloneNode(stateParameter)],
2139
+ [types.cloneNode(propsParameter), types.cloneNode(stateParameter), types.cloneNode(blockParameter)],
354
2140
  rewrittenRoot
355
2141
  )
356
2142
  ),
357
2143
  types.objectProperty(
358
2144
  types.identifier("bindings"),
359
2145
  types.arrayExpression(
360
- (analysis.bindings || []).map(
361
- (binding) => bindingObject(binding, path, stateParameter, statesByValue, statesBySetter)
2146
+ [
2147
+ ...analysis.bindings || [],
2148
+ ...blockPlans.map((block) => ({
2149
+ kind: "block",
2150
+ id: block.id,
2151
+ parent: block.parent,
2152
+ dependencies: block.dependencies
2153
+ }))
2154
+ ].map(
2155
+ (binding) => bindingObject(binding, propsParameter, stateParameter, statesByValue, statesBySetter)
362
2156
  )
363
2157
  )
364
2158
  )
365
2159
  ])
366
2160
  ]);
367
- const originalProps = path.node.params[0];
368
2161
  path.get("body").replaceWith(
369
2162
  types.blockStatement([
370
- types.returnStatement(
371
- wrapperElement(
372
- definitionIdentifier,
373
- originalProps && types.isIdentifier(originalProps) ? originalProps : void 0
374
- )
375
- )
2163
+ types.returnStatement(wrapperElement(definitionIdentifier, propsPlan.wrapperProps))
376
2164
  ])
377
2165
  );
378
2166
  statementPath.insertAfter(
@@ -423,8 +2211,18 @@ async function compileReactModule(code, id, options) {
423
2211
  const moduleDirectives = new Set(directiveValues(programPath.node));
424
2212
  const useStateNames = /* @__PURE__ */ new Set();
425
2213
  const reactNames = /* @__PURE__ */ new Set();
2214
+ const listNames = /* @__PURE__ */ new Set();
426
2215
  for (const statement of programPath.node.body) {
427
- if (!types.isImportDeclaration(statement) || statement.source.value !== "react") continue;
2216
+ if (!types.isImportDeclaration(statement)) continue;
2217
+ if (statement.source.value === "@farm.js/react/list") {
2218
+ for (const specifier of statement.specifiers) {
2219
+ if (types.isImportSpecifier(specifier) && types.isIdentifier(specifier.imported, { name: "List" })) {
2220
+ listNames.add(specifier.local.name);
2221
+ }
2222
+ }
2223
+ continue;
2224
+ }
2225
+ if (statement.source.value !== "react") continue;
428
2226
  for (const specifier of statement.specifiers) {
429
2227
  if (types.isImportDefaultSpecifier(specifier) || types.isImportNamespaceSpecifier(specifier)) {
430
2228
  reactNames.add(specifier.local.name);
@@ -446,7 +2244,9 @@ async function compileReactModule(code, id, options) {
446
2244
  candidate,
447
2245
  createComponentIdentifier,
448
2246
  useStateNames,
449
- reactNames
2247
+ reactNames,
2248
+ listNames,
2249
+ id
450
2250
  );
451
2251
  if (reason) {
452
2252
  diagnostics.push({
@@ -500,9 +2300,65 @@ async function compileReactModule(code, id, options) {
500
2300
  diagnostics
501
2301
  };
502
2302
  }
2303
+ function projectRelativeId(projectRoot, id) {
2304
+ return relative(projectRoot, id).replace(/\\/g, "/");
2305
+ }
2306
+ function createReactCompilerReport(projectRoot, observations) {
2307
+ const reasonCounts = /* @__PURE__ */ new Map();
2308
+ let componentsConsidered = 0;
2309
+ let compiled = 0;
2310
+ let fallback = 0;
2311
+ const modules = [...observations].map(({ id, result }) => {
2312
+ componentsConsidered += result.compiled.length + result.diagnostics.length;
2313
+ compiled += result.compiled.length;
2314
+ fallback += result.diagnostics.length;
2315
+ const moduleId = projectRelativeId(projectRoot, id);
2316
+ const fallbacks = result.diagnostics.map((diagnostic) => {
2317
+ reasonCounts.set(diagnostic.reason, (reasonCounts.get(diagnostic.reason) || 0) + 1);
2318
+ return { module: moduleId, ...diagnostic };
2319
+ });
2320
+ return {
2321
+ id: moduleId,
2322
+ compiled: [...result.compiled].sort(),
2323
+ fallbacks
2324
+ };
2325
+ }).sort((left, right) => left.id.localeCompare(right.id));
2326
+ const fallbackReasons = [...reasonCounts].map(([reason, count]) => ({ count, reason })).sort((left, right) => right.count - left.count || left.reason.localeCompare(right.reason));
2327
+ return {
2328
+ version: 1,
2329
+ summary: {
2330
+ modules: modules.length,
2331
+ componentsConsidered,
2332
+ compiled,
2333
+ fallback
2334
+ },
2335
+ fallbackReasons,
2336
+ modules
2337
+ };
2338
+ }
2339
+ async function writeReactCompilerReport(projectRoot, reportFile, observations) {
2340
+ const outputPath = resolve(projectRoot, reportFile);
2341
+ const report = createReactCompilerReport(projectRoot, observations);
2342
+ await mkdir(dirname(outputPath), { recursive: true });
2343
+ await writeFile(outputPath, `${JSON.stringify(report, null, 2)}
2344
+ `, "utf8");
2345
+ return outputPath;
2346
+ }
503
2347
 
504
2348
  // src/index.ts
505
2349
  var DEFAULT_COMPILER_DIRECTIVE = "use compiler";
2350
+ var DEFAULT_COMPILER_REPORT_FILE = ".farm/react-compiler.json";
2351
+ function normalizeCompilerReportFile(value) {
2352
+ if (value === void 0) return DEFAULT_COMPILER_REPORT_FILE;
2353
+ if (typeof value !== "string" || !value.trim()) {
2354
+ throw new TypeError("The React compiler report file must be a non-empty relative path.");
2355
+ }
2356
+ const normalized = value.trim().replace(/\\/g, "/");
2357
+ if (normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || normalized.split("/").includes("..")) {
2358
+ throw new TypeError("The React compiler report file must stay inside the project root.");
2359
+ }
2360
+ return normalized;
2361
+ }
506
2362
  function normalizeReactCompilerOptions(value) {
507
2363
  const options = value === true ? {} : value;
508
2364
  const mode = options.mode || "infer";
@@ -517,7 +2373,17 @@ function normalizeReactCompilerOptions(value) {
517
2373
  if (!["fallback", "warn", "error"].includes(onUnsupported)) {
518
2374
  throw new TypeError(`Unknown unsupported-component behavior: ${String(onUnsupported)}`);
519
2375
  }
520
- return { mode, directive, onUnsupported };
2376
+ if (options.report !== void 0 && typeof options.report !== "boolean") {
2377
+ throw new TypeError("The React compiler report option must be a boolean.");
2378
+ }
2379
+ if (options.report === false && options.reportFile !== void 0) {
2380
+ throw new TypeError(
2381
+ "A React compiler report file cannot be configured when reporting is false."
2382
+ );
2383
+ }
2384
+ const reportFile = normalizeCompilerReportFile(options.reportFile);
2385
+ const report = options.report === true || options.reportFile !== void 0;
2386
+ return { mode, directive, onUnsupported, report, reportFile };
521
2387
  }
522
2388
 
523
2389
  // src/vite.ts
@@ -542,6 +2408,8 @@ function createFarmRendererPlugin(options = {}) {
542
2408
  const compilerOptions = readCompilerOptions(options.rendererOptions);
543
2409
  if (!compilerOptions) return [];
544
2410
  let projectRoot = "";
2411
+ const reportEnabled = compilerOptions.report && options.ssr !== true;
2412
+ const reportObservations = /* @__PURE__ */ new Map();
545
2413
  return [
546
2414
  {
547
2415
  name: "farm:react-aot-compiler",
@@ -549,11 +2417,21 @@ function createFarmRendererPlugin(options = {}) {
549
2417
  configResolved(config) {
550
2418
  projectRoot = normalizePath(config.root).replace(/\/$/, "");
551
2419
  },
2420
+ buildStart() {
2421
+ reportObservations.clear();
2422
+ },
552
2423
  async transform(code, id) {
553
2424
  const cleanId = normalizePath(id.split("?", 1)[0]);
554
2425
  if (!/\.[jt]sx$/.test(cleanId) || cleanId.includes("/node_modules/") || projectRoot && !cleanId.startsWith(`${projectRoot}/`))
555
2426
  return null;
556
2427
  const result = await compileReactModule(code, cleanId, compilerOptions);
2428
+ if (reportEnabled) {
2429
+ if (result.compiled.length > 0 || result.diagnostics.length > 0) {
2430
+ reportObservations.set(cleanId, { id: cleanId, result });
2431
+ } else {
2432
+ reportObservations.delete(cleanId);
2433
+ }
2434
+ }
557
2435
  for (const diagnostic of result.diagnostics) {
558
2436
  const message = `[react-compiler] ${diagnostic.component}: ${diagnostic.reason}; using React.`;
559
2437
  if (compilerOptions.onUnsupported === "error") this.error(message);
@@ -561,6 +2439,14 @@ function createFarmRendererPlugin(options = {}) {
561
2439
  }
562
2440
  if (result.compiled.length === 0) return null;
563
2441
  return { code: result.code, map: result.map };
2442
+ },
2443
+ async buildEnd(error) {
2444
+ if (error || !reportEnabled || !projectRoot) return;
2445
+ await writeReactCompilerReport(
2446
+ projectRoot,
2447
+ compilerOptions.reportFile,
2448
+ reportObservations.values()
2449
+ );
564
2450
  }
565
2451
  }
566
2452
  ];