@archie/devtools 0.0.26 → 0.0.27

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.
@@ -0,0 +1,521 @@
1
+ import {
2
+ meaningfulChildren
3
+ } from "./chunk-4VGQJ3PW.mjs";
4
+
5
+ // src/babel-plugin-collection-binding.ts
6
+ import { VISITOR_KEYS } from "@babel/types";
7
+ var ATTR_ENTITY = "data-agent-entity";
8
+ var ATTR_ROW = "data-agent-row";
9
+ var ATTR_FIELD = "data-agent-field";
10
+ var ATTR_FIELD_STATE = "data-agent-field-state";
11
+ var ATTR_FIELD_PARTS = "data-agent-field-parts";
12
+ var ATTR_FIELD_SLOT = "data-agent-field-slot";
13
+ var ATTR_COLLECTION_ORDER = "data-agent-collection-order";
14
+ var SLICE_ROOT_NAME = "slice";
15
+ var CHAIN_METHODS = /* @__PURE__ */ new Set(["filter", "slice", "sort", "map"]);
16
+ var MAX_SOURCE_CHAIN_HOPS = 4;
17
+ var MAX_PROJECTION_DEPTH = 3;
18
+ function babelPluginCollectionBinding({ types: t }, opts = {}) {
19
+ const { fileExclude = /node_modules/ } = opts;
20
+ function hasOwnAttr(elementPath, name) {
21
+ return elementPath.node.openingElement.attributes.some(
22
+ (a) => t.isJSXAttribute(a) && t.isJSXIdentifier(a.name, { name })
23
+ );
24
+ }
25
+ function addAttr(elementPath, name, value) {
26
+ if (hasOwnAttr(elementPath, name)) return;
27
+ elementPath.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(name), value));
28
+ }
29
+ function getTagName(opening) {
30
+ const { name } = opening;
31
+ if (t.isJSXIdentifier(name)) return name.name;
32
+ if (t.isJSXMemberExpression(name)) {
33
+ const parts = [];
34
+ let current = name;
35
+ while (t.isJSXMemberExpression(current)) {
36
+ parts.unshift(current.property.name);
37
+ current = current.object;
38
+ }
39
+ if (t.isJSXIdentifier(current)) parts.unshift(current.name);
40
+ return parts.join(".");
41
+ }
42
+ return "";
43
+ }
44
+ function isLowercaseTag(tag) {
45
+ return tag.length > 0 && tag[0] === tag[0].toLowerCase();
46
+ }
47
+ function getAttrName(attr) {
48
+ return t.isJSXIdentifier(attr.name) ? attr.name.name : `${attr.name.namespace.name}:${attr.name.name.name}`;
49
+ }
50
+ function isPageSliceBinding(scope) {
51
+ const binding = scope.getBinding(SLICE_ROOT_NAME);
52
+ if (!binding) return true;
53
+ return binding.scope === binding.scope.getProgramParent();
54
+ }
55
+ function isUseMemoCallee(callee) {
56
+ if (t.isIdentifier(callee, { name: "useMemo" })) return true;
57
+ return t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property, { name: "useMemo" });
58
+ }
59
+ function resolveSliceSource(expr, scope, hops, hasSort) {
60
+ if (t.isCallExpression(expr)) {
61
+ const callee = expr.callee;
62
+ if (!t.isExpression(callee)) return null;
63
+ if (isUseMemoCallee(callee)) {
64
+ const [factory] = expr.arguments;
65
+ if (!factory || !t.isArrowFunctionExpression(factory)) return null;
66
+ if (!t.isExpression(factory.body)) return null;
67
+ return resolveSliceSource(factory.body, scope, hops, hasSort);
68
+ }
69
+ if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property)) {
70
+ const method = callee.property.name;
71
+ if (!CHAIN_METHODS.has(method)) return null;
72
+ if (!t.isExpression(callee.object)) return null;
73
+ return resolveSliceSource(callee.object, scope, hops, hasSort || method === "sort");
74
+ }
75
+ return null;
76
+ }
77
+ if (t.isMemberExpression(expr)) {
78
+ if (!t.isIdentifier(expr.object, { name: SLICE_ROOT_NAME })) return null;
79
+ if (!isPageSliceBinding(scope)) return null;
80
+ if (!expr.computed && t.isIdentifier(expr.property)) {
81
+ return { entity: expr.property.name, hasSort };
82
+ }
83
+ if (expr.computed && t.isStringLiteral(expr.property)) {
84
+ return { entity: expr.property.value, hasSort };
85
+ }
86
+ return null;
87
+ }
88
+ if (t.isIdentifier(expr)) {
89
+ if (hops >= MAX_SOURCE_CHAIN_HOPS) return null;
90
+ const binding = scope.getBinding(expr.name);
91
+ if (!binding || !binding.constant) return null;
92
+ const declarator = binding.path;
93
+ if (!declarator.isVariableDeclarator()) return null;
94
+ const init = declarator.node.init;
95
+ if (!init || !t.isExpression(init)) return null;
96
+ return resolveSliceSource(init, declarator.scope, hops + 1, hasSort);
97
+ }
98
+ return null;
99
+ }
100
+ function buildScopeFromParam(param) {
101
+ if (t.isIdentifier(param)) {
102
+ return { identifiers: /* @__PURE__ */ new Map([[param.name, { kind: "self" }]]) };
103
+ }
104
+ if (t.isObjectPattern(param)) {
105
+ const identifiers = /* @__PURE__ */ new Map();
106
+ for (const prop of param.properties) {
107
+ if (!t.isObjectProperty(prop) || prop.computed) continue;
108
+ const key = t.isIdentifier(prop.key) ? prop.key.name : t.isStringLiteral(prop.key) ? prop.key.value : null;
109
+ if (!key || key.includes(",")) continue;
110
+ const value = prop.value;
111
+ let localName = null;
112
+ if (t.isIdentifier(value)) localName = value.name;
113
+ else if (t.isAssignmentPattern(value) && t.isIdentifier(value.left)) localName = value.left.name;
114
+ if (!localName) continue;
115
+ identifiers.set(localName, { kind: "field", field: key });
116
+ }
117
+ if (identifiers.size === 0) return null;
118
+ return { identifiers };
119
+ }
120
+ return null;
121
+ }
122
+ function buildOptionalRowIdExpression(idValueExpr) {
123
+ return t.conditionalExpression(
124
+ t.binaryExpression("==", t.cloneNode(idValueExpr), t.nullLiteral()),
125
+ t.identifier("undefined"),
126
+ t.callExpression(t.identifier("String"), [idValueExpr])
127
+ );
128
+ }
129
+ function computeRowIdExpression(scope) {
130
+ for (const [localName, binding] of scope.identifiers) {
131
+ if (binding.kind === "self") {
132
+ return buildOptionalRowIdExpression(
133
+ t.optionalMemberExpression(t.identifier(localName), t.identifier("id"), false, true)
134
+ );
135
+ }
136
+ }
137
+ for (const [localName, binding] of scope.identifiers) {
138
+ if (binding.kind === "field" && binding.field === "id") {
139
+ return buildOptionalRowIdExpression(t.identifier(localName));
140
+ }
141
+ }
142
+ return null;
143
+ }
144
+ function isStaticLiteralExpr(node) {
145
+ if (t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node) || t.isNullLiteral(node)) {
146
+ return true;
147
+ }
148
+ return t.isTemplateLiteral(node) && node.expressions.length === 0;
149
+ }
150
+ function containsEvaluation(node) {
151
+ if (!node) return false;
152
+ if (t.isCallExpression(node) || t.isOptionalCallExpression(node) || t.isNewExpression(node) || t.isTaggedTemplateExpression(node)) {
153
+ return true;
154
+ }
155
+ const keys = VISITOR_KEYS[node.type] ?? [];
156
+ for (const key of keys) {
157
+ const value = node[key];
158
+ if (Array.isArray(value)) {
159
+ for (const child of value) {
160
+ if (child && typeof child === "object" && "type" in child) {
161
+ if (containsEvaluation(child)) return true;
162
+ }
163
+ }
164
+ } else if (value && typeof value === "object" && "type" in value) {
165
+ if (containsEvaluation(value)) return true;
166
+ }
167
+ }
168
+ return false;
169
+ }
170
+ function classifyMemberField(expr, scope) {
171
+ if (!t.isIdentifier(expr.object)) return null;
172
+ const binding = scope.identifiers.get(expr.object.name);
173
+ if (!binding || binding.kind !== "self") return null;
174
+ if (!expr.computed) {
175
+ if (!t.isIdentifier(expr.property)) return null;
176
+ if (expr.property.name === "id") return null;
177
+ return { kind: "static", field: expr.property.name };
178
+ }
179
+ if (t.isStringLiteral(expr.property)) {
180
+ if (expr.property.value === "id") return null;
181
+ if (expr.property.value.includes(",")) return null;
182
+ return { kind: "static", field: expr.property.value };
183
+ }
184
+ return { kind: "unresolved-computed" };
185
+ }
186
+ function fieldNameForBranch(expr, scope) {
187
+ if (t.isIdentifier(expr)) {
188
+ const binding = scope.identifiers.get(expr.name);
189
+ return binding?.kind === "field" ? binding.field : null;
190
+ }
191
+ if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
192
+ const verdict = classifyMemberField(expr, scope);
193
+ return verdict?.kind === "static" ? verdict.field : null;
194
+ }
195
+ return null;
196
+ }
197
+ function flattenNullish(expr) {
198
+ const parts = [];
199
+ let current = expr;
200
+ while (t.isLogicalExpression(current) && current.operator === "??") {
201
+ parts.unshift(current.right);
202
+ current = current.left;
203
+ }
204
+ parts.unshift(current);
205
+ return parts;
206
+ }
207
+ function classifyNullishChain(expr, scope) {
208
+ const parts = flattenNullish(expr);
209
+ if (parts.length < 2) return null;
210
+ const last = parts[parts.length - 1];
211
+ if (parts.length === 2 && isStaticLiteralExpr(last)) {
212
+ const field = fieldNameForBranch(parts[0], scope);
213
+ return field ? { kind: "static", field } : null;
214
+ }
215
+ const fields = parts.map((p) => fieldNameForBranch(p, scope));
216
+ if (fields.some((f) => f === null)) return null;
217
+ const distinct = new Set(fields);
218
+ if (distinct.size === 1) return { kind: "static", field: fields[0] };
219
+ let acc = t.stringLiteral(fields[fields.length - 1]);
220
+ for (let i = parts.length - 2; i >= 0; i -= 1) {
221
+ acc = t.conditionalExpression(
222
+ t.binaryExpression("!=", t.cloneNode(parts[i]), t.nullLiteral()),
223
+ t.stringLiteral(fields[i]),
224
+ acc
225
+ );
226
+ }
227
+ return { kind: "runtime", expr: acc };
228
+ }
229
+ function classifySimpleLogicalOr(expr, scope) {
230
+ if (!isStaticLiteralExpr(expr.right)) return null;
231
+ const field = fieldNameForBranch(expr.left, scope);
232
+ return field ? { kind: "static", field } : null;
233
+ }
234
+ function classifyConditional(expr, scope) {
235
+ const { test, consequent, alternate } = expr;
236
+ if (!t.isExpression(test) || !t.isExpression(consequent) || !t.isExpression(alternate)) return null;
237
+ if (isStaticLiteralExpr(consequent) && isStaticLiteralExpr(alternate)) {
238
+ const testField = fieldNameForBranch(test, scope);
239
+ return testField ? { kind: "static", field: testField } : null;
240
+ }
241
+ const consField = fieldNameForBranch(consequent, scope);
242
+ const altField = fieldNameForBranch(alternate, scope);
243
+ if (!consField || !altField) return null;
244
+ if (consField === altField) return { kind: "static", field: consField };
245
+ if (containsEvaluation(test)) return null;
246
+ return {
247
+ kind: "runtime",
248
+ expr: t.conditionalExpression(t.cloneNode(test), t.stringLiteral(consField), t.stringLiteral(altField))
249
+ };
250
+ }
251
+ function classifyFieldExpr(expr, scope) {
252
+ if (t.isIdentifier(expr)) {
253
+ const binding = scope.identifiers.get(expr.name);
254
+ return binding?.kind === "field" ? { kind: "static", field: binding.field } : null;
255
+ }
256
+ if (t.isMemberExpression(expr) || t.isOptionalMemberExpression(expr)) {
257
+ return classifyMemberField(expr, scope);
258
+ }
259
+ if (t.isLogicalExpression(expr) && expr.operator === "??") return classifyNullishChain(expr, scope);
260
+ if (t.isLogicalExpression(expr) && expr.operator === "||") return classifySimpleLogicalOr(expr, scope);
261
+ if (t.isConditionalExpression(expr)) return classifyConditional(expr, scope);
262
+ return null;
263
+ }
264
+ function applyFieldVerdict(elementPath, verdict) {
265
+ if (!verdict) return;
266
+ if (verdict.kind === "unresolved-computed") {
267
+ addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("unresolved"));
268
+ return;
269
+ }
270
+ if (verdict.kind === "static") {
271
+ addAttr(elementPath, ATTR_FIELD, t.stringLiteral(verdict.field));
272
+ return;
273
+ }
274
+ addAttr(elementPath, ATTR_FIELD, t.jsxExpressionContainer(verdict.expr));
275
+ }
276
+ function meaningfulChildPaths(elementPath) {
277
+ const meaningfulSet = new Set(meaningfulChildren(t, elementPath.node.children));
278
+ const childPaths = elementPath.get("children");
279
+ return childPaths.filter((p) => meaningfulSet.has(p.node));
280
+ }
281
+ function isStaticExpressionContainer(container) {
282
+ if (t.isJSXEmptyExpression(container.expression)) return true;
283
+ return isStaticLiteralExpr(container.expression);
284
+ }
285
+ function isInvisibleRenderExpr(expr) {
286
+ if (t.isNullLiteral(expr)) return true;
287
+ if (t.isBooleanLiteral(expr)) return true;
288
+ if (t.isStringLiteral(expr) && expr.value === "") return true;
289
+ if (t.isTemplateLiteral(expr) && expr.expressions.length === 0 && expr.quasis.length === 1 && expr.quasis[0].value.cooked === "") {
290
+ return true;
291
+ }
292
+ return false;
293
+ }
294
+ function stampPureLeaf(elementPath, expr, scope) {
295
+ if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
296
+ if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) return;
297
+ applyFieldVerdict(elementPath, classifyFieldExpr(expr, scope));
298
+ }
299
+ function stampLabelValueOrMixed(elementPath, childPaths, scope) {
300
+ if (hasOwnAttr(elementPath, ATTR_FIELD) || hasOwnAttr(elementPath, ATTR_FIELD_STATE)) return;
301
+ const hasInvisibleSibling = childPaths.some(
302
+ (p) => p.isJSXExpressionContainer() && t.isExpression(p.node.expression) && isInvisibleRenderExpr(p.node.expression)
303
+ );
304
+ if (hasInvisibleSibling) return;
305
+ const dynamicEntries = [];
306
+ childPaths.forEach((p, index) => {
307
+ if (!p.isJSXExpressionContainer()) return;
308
+ const container = p.node;
309
+ if (isStaticExpressionContainer(container)) return;
310
+ if (!t.isExpression(container.expression)) return;
311
+ dynamicEntries.push({ index, expr: container.expression });
312
+ });
313
+ if (dynamicEntries.length === 0) return;
314
+ if (dynamicEntries.length === 1) {
315
+ const { index, expr } = dynamicEntries[0];
316
+ const verdict = classifyFieldExpr(expr, scope);
317
+ if (!verdict) return;
318
+ applyFieldVerdict(elementPath, verdict);
319
+ if (verdict.kind !== "unresolved-computed") {
320
+ addAttr(elementPath, ATTR_FIELD_SLOT, t.stringLiteral(String(index)));
321
+ }
322
+ return;
323
+ }
324
+ const fields = [];
325
+ for (const { expr } of dynamicEntries) {
326
+ const verdict = classifyFieldExpr(expr, scope);
327
+ if (!verdict || verdict.kind !== "static") return;
328
+ fields.push(verdict.field);
329
+ }
330
+ if (new Set(fields).size <= 1) return;
331
+ addAttr(elementPath, ATTR_FIELD_STATE, t.stringLiteral("mixed"));
332
+ addAttr(elementPath, ATTR_FIELD_PARTS, t.stringLiteral(fields.join(",")));
333
+ }
334
+ function resolveComponentDefinition(tagName, fromPath) {
335
+ if (tagName.includes(".")) return null;
336
+ const binding = fromPath.scope.getBinding(tagName);
337
+ if (!binding) return null;
338
+ if (binding.scope !== binding.scope.getProgramParent()) return null;
339
+ const bp = binding.path;
340
+ if (bp.isFunctionDeclaration()) return bp;
341
+ if (bp.isVariableDeclarator()) {
342
+ const init = bp.get("init");
343
+ if (!Array.isArray(init) && (init.isArrowFunctionExpression() || init.isFunctionExpression())) {
344
+ return init;
345
+ }
346
+ }
347
+ return null;
348
+ }
349
+ function buildCalleePropContext(fnPath) {
350
+ const [first] = fnPath.node.params;
351
+ if (!first || !t.isObjectPattern(first)) return null;
352
+ const propMap = /* @__PURE__ */ new Map();
353
+ for (const prop of first.properties) {
354
+ if (!t.isObjectProperty(prop) || prop.computed) continue;
355
+ const key = t.isIdentifier(prop.key) ? prop.key.name : t.isStringLiteral(prop.key) ? prop.key.value : null;
356
+ if (!key) continue;
357
+ const value = prop.value;
358
+ let localName = null;
359
+ if (t.isIdentifier(value)) localName = value.name;
360
+ else if (t.isAssignmentPattern(value) && t.isIdentifier(value.left)) localName = value.left.name;
361
+ if (!localName) continue;
362
+ propMap.set(key, localName);
363
+ }
364
+ if (propMap.size === 0) return null;
365
+ return { propMap };
366
+ }
367
+ function findReturnedJsxPath(fnPath) {
368
+ const body = fnPath.get("body");
369
+ if (Array.isArray(body)) return null;
370
+ if (body.isBlockStatement()) {
371
+ const returnPaths = [];
372
+ body.traverse({
373
+ Function(inner) {
374
+ inner.skip();
375
+ },
376
+ ReturnStatement(rp) {
377
+ returnPaths.push(rp);
378
+ }
379
+ });
380
+ if (returnPaths.length !== 1) return null;
381
+ const arg = returnPaths[0].get("argument");
382
+ if (Array.isArray(arg) || !arg.node) return null;
383
+ return arg.isJSXElement() ? arg : null;
384
+ }
385
+ return body.isJSXElement() ? body : null;
386
+ }
387
+ function findSelfForwardingLocalName(usagePath, scope, propCtx) {
388
+ for (const attr of usagePath.node.openingElement.attributes) {
389
+ if (!t.isJSXAttribute(attr)) continue;
390
+ const localName = propCtx.propMap.get(getAttrName(attr));
391
+ if (!localName) continue;
392
+ const value = attr.value;
393
+ if (!t.isJSXExpressionContainer(value)) continue;
394
+ const expr = value.expression;
395
+ if (!t.isIdentifier(expr)) continue;
396
+ const binding = scope.identifiers.get(expr.name);
397
+ if (binding?.kind === "self") return localName;
398
+ }
399
+ return null;
400
+ }
401
+ function resolveRowRoot(jsxPath, scope, depth) {
402
+ const tag = getTagName(jsxPath.node.openingElement);
403
+ if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
404
+ if (depth >= MAX_PROJECTION_DEPTH) return null;
405
+ const fnPath = resolveComponentDefinition(tag, jsxPath);
406
+ if (!fnPath) return null;
407
+ const propCtx = buildCalleePropContext(fnPath);
408
+ if (!propCtx) return null;
409
+ const localName = findSelfForwardingLocalName(jsxPath, scope, propCtx);
410
+ if (!localName) return null;
411
+ const innerReturn = findReturnedJsxPath(fnPath);
412
+ if (!innerReturn) return null;
413
+ return resolveRowRoot(innerReturn, { identifiers: /* @__PURE__ */ new Map([[localName, { kind: "self" }]]) }, depth + 1);
414
+ }
415
+ function resolveFieldProjection(jsxPath, scope, depth) {
416
+ const tag = getTagName(jsxPath.node.openingElement);
417
+ if (isLowercaseTag(tag)) return { hostPath: jsxPath, scope };
418
+ if (depth >= MAX_PROJECTION_DEPTH) return null;
419
+ const fnPath = resolveComponentDefinition(tag, jsxPath);
420
+ if (!fnPath) return null;
421
+ const propCtx = buildCalleePropContext(fnPath);
422
+ if (!propCtx) return null;
423
+ const identifiers = /* @__PURE__ */ new Map();
424
+ for (const attr of jsxPath.node.openingElement.attributes) {
425
+ if (!t.isJSXAttribute(attr)) continue;
426
+ const localName = propCtx.propMap.get(getAttrName(attr));
427
+ if (!localName) continue;
428
+ const value = attr.value;
429
+ if (!t.isJSXExpressionContainer(value)) continue;
430
+ const expr = value.expression;
431
+ if (t.isJSXEmptyExpression(expr) || !t.isExpression(expr)) continue;
432
+ if (t.isIdentifier(expr)) {
433
+ const binding = scope.identifiers.get(expr.name);
434
+ if (binding?.kind === "self") {
435
+ identifiers.set(localName, { kind: "self" });
436
+ continue;
437
+ }
438
+ }
439
+ const verdict = classifyFieldExpr(expr, scope);
440
+ if (verdict?.kind === "static") {
441
+ identifiers.set(localName, { kind: "field", field: verdict.field });
442
+ }
443
+ }
444
+ if (identifiers.size === 0) return null;
445
+ const innerReturn = findReturnedJsxPath(fnPath);
446
+ if (!innerReturn) return null;
447
+ return resolveFieldProjection(innerReturn, { identifiers }, depth + 1);
448
+ }
449
+ function walkFieldsWithinRow(elementPath, scope) {
450
+ const childPaths = meaningfulChildPaths(elementPath);
451
+ const hasElementChild = childPaths.some((p) => p.isJSXElement());
452
+ if (hasElementChild) {
453
+ for (const childPath of childPaths) {
454
+ if (!childPath.isJSXElement()) continue;
455
+ handleStructuralChild(childPath, scope);
456
+ }
457
+ return;
458
+ }
459
+ if (childPaths.length === 0) return;
460
+ const exprChildren = childPaths.filter((p) => p.isJSXExpressionContainer());
461
+ if (childPaths.length === 1 && exprChildren.length === 1) {
462
+ stampPureLeaf(elementPath, exprChildren[0].node.expression, scope);
463
+ return;
464
+ }
465
+ if (childPaths.length > 1) {
466
+ stampLabelValueOrMixed(elementPath, childPaths, scope);
467
+ }
468
+ }
469
+ function handleStructuralChild(childPath, scope) {
470
+ const tag = getTagName(childPath.node.openingElement);
471
+ if (isLowercaseTag(tag)) {
472
+ walkFieldsWithinRow(childPath, scope);
473
+ return;
474
+ }
475
+ const projected = resolveFieldProjection(childPath, scope, 0);
476
+ if (!projected) return;
477
+ walkFieldsWithinRow(projected.hostPath, projected.scope);
478
+ }
479
+ function processSliceMap(callPath) {
480
+ const node = callPath.node;
481
+ const callee = node.callee;
482
+ if (!t.isMemberExpression(callee) || callee.computed) return;
483
+ if (!t.isIdentifier(callee.property, { name: "map" })) return;
484
+ if (!t.isExpression(callee.object)) return;
485
+ const source = resolveSliceSource(callee.object, callPath.scope, 0, false);
486
+ if (!source) return;
487
+ const [callback] = node.arguments;
488
+ if (!callback || !t.isArrowFunctionExpression(callback) && !t.isFunctionExpression(callback)) return;
489
+ if (callback.params.length === 0) return;
490
+ const callbackPath = callPath.get("arguments.0");
491
+ if (Array.isArray(callbackPath)) return;
492
+ if (!callbackPath.isArrowFunctionExpression() && !callbackPath.isFunctionExpression()) return;
493
+ const initialScope = buildScopeFromParam(callback.params[0]);
494
+ if (!initialScope) return;
495
+ const returnedJsx = findReturnedJsxPath(callbackPath);
496
+ if (!returnedJsx) return;
497
+ const resolvedRoot = resolveRowRoot(returnedJsx, initialScope, 0);
498
+ if (!resolvedRoot) return;
499
+ const rowIdExpr = computeRowIdExpression(resolvedRoot.scope);
500
+ if (!rowIdExpr) return;
501
+ const { hostPath, scope } = resolvedRoot;
502
+ if (hasOwnAttr(hostPath, ATTR_ENTITY)) return;
503
+ addAttr(hostPath, ATTR_ENTITY, t.stringLiteral(source.entity));
504
+ addAttr(hostPath, ATTR_ROW, t.jsxExpressionContainer(rowIdExpr));
505
+ addAttr(hostPath, ATTR_COLLECTION_ORDER, t.stringLiteral(source.hasSort ? "derived" : "stable"));
506
+ walkFieldsWithinRow(hostPath, scope);
507
+ }
508
+ return {
509
+ name: "babel-plugin-collection-binding",
510
+ visitor: {
511
+ CallExpression(path, state) {
512
+ const filename = state.filename ?? "";
513
+ if (fileExclude.test(filename)) return;
514
+ processSliceMap(path);
515
+ }
516
+ }
517
+ };
518
+ }
519
+ export {
520
+ babelPluginCollectionBinding
521
+ };
package/dist/index.js CHANGED
@@ -206,6 +206,13 @@ function babelPluginDndAuto({ types: t }, opts = {}) {
206
206
 
207
207
  // src/babel-plugin-editor-meta.ts
208
208
  var nodePath = __toESM(require("path"));
209
+ function meaningfulChildren(t, children) {
210
+ return children.filter((child) => {
211
+ if (t.isJSXText(child)) return child.value.trim() !== "";
212
+ if (t.isJSXExpressionContainer(child)) return !t.isJSXEmptyExpression(child.expression);
213
+ return true;
214
+ });
215
+ }
209
216
  var KIT_SLOT_ATTR = "data-kit-slot";
210
217
  var MAX_SLOT_SEGMENTS = 4;
211
218
  var NON_BINDING_ATTRS = /* @__PURE__ */ new Set(["__editorMeta", "key", "ref", KIT_SLOT_ATTR]);
@@ -450,8 +457,7 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
450
457
  let fn = path.getFunctionParent();
451
458
  while (fn) {
452
459
  const [first] = fn.node.params;
453
- if (first && t.isObjectPattern(first)) {
454
- if (fn.scope.parent !== fn.scope.getProgramParent()) return null;
460
+ if (first && t.isObjectPattern(first) && fn.scope.parent === fn.scope.getProgramParent()) {
455
461
  return {
456
462
  componentScope: fn.scope,
457
463
  propMap: buildPropMap(first),
@@ -575,13 +581,6 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
575
581
  const props = collectPropIdentifiers(expr, scope, ctx);
576
582
  return props.length > 0 ? { kind: "formatted", prop: props[0] } : null;
577
583
  }
578
- function meaningfulChildren(children) {
579
- return children.filter((child) => {
580
- if (t.isJSXText(child)) return child.value.trim() !== "";
581
- if (t.isJSXExpressionContainer(child)) return !t.isJSXEmptyExpression(child.expression);
582
- return true;
583
- });
584
- }
585
584
  function collectPropBindings(path, attrs) {
586
585
  const ctx = resolvePropContext(path);
587
586
  if (!ctx || ctx.propMap.size === 0) return { bindings: [], owner: null };
@@ -590,7 +589,7 @@ function babelPluginEditorMeta({ types: t }, opts = {}) {
590
589
  const bindings = [];
591
590
  const elementPath = path.parentPath;
592
591
  if (elementPath && elementPath.isJSXElement()) {
593
- const children = meaningfulChildren(elementPath.node.children);
592
+ const children = meaningfulChildren(t, elementPath.node.children);
594
593
  const isSoleChild = children.length === 1;
595
594
  for (const child of children) {
596
595
  if (!t.isJSXExpressionContainer(child)) continue;