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