@depup/base44__vite-plugin 1.0.4-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/README.md +34 -0
  2. package/changes.json +22 -0
  3. package/compat/agents.cjs +13 -0
  4. package/compat/base44Client.cjs +6 -0
  5. package/compat/entities.cjs +25 -0
  6. package/compat/functions.cjs +9 -0
  7. package/compat/integrations.cjs +9 -0
  8. package/dist/ErrorOverlay.d.ts +12 -0
  9. package/dist/ErrorOverlay.d.ts.map +1 -0
  10. package/dist/ErrorOverlay.js +51 -0
  11. package/dist/ErrorOverlay.js.map +1 -0
  12. package/dist/bridge.d.ts +8 -0
  13. package/dist/bridge.d.ts.map +1 -0
  14. package/dist/bridge.js +8 -0
  15. package/dist/bridge.js.map +1 -0
  16. package/dist/capabilities/inline-edit/controller.d.ts +3 -0
  17. package/dist/capabilities/inline-edit/controller.d.ts.map +1 -0
  18. package/dist/capabilities/inline-edit/controller.js +203 -0
  19. package/dist/capabilities/inline-edit/controller.js.map +1 -0
  20. package/dist/capabilities/inline-edit/dom-utils.d.ts +7 -0
  21. package/dist/capabilities/inline-edit/dom-utils.d.ts.map +1 -0
  22. package/dist/capabilities/inline-edit/dom-utils.js +59 -0
  23. package/dist/capabilities/inline-edit/dom-utils.js.map +1 -0
  24. package/dist/capabilities/inline-edit/index.d.ts +3 -0
  25. package/dist/capabilities/inline-edit/index.d.ts.map +1 -0
  26. package/dist/capabilities/inline-edit/index.js +2 -0
  27. package/dist/capabilities/inline-edit/index.js.map +1 -0
  28. package/dist/capabilities/inline-edit/types.d.ts +29 -0
  29. package/dist/capabilities/inline-edit/types.d.ts.map +1 -0
  30. package/dist/capabilities/inline-edit/types.js +2 -0
  31. package/dist/capabilities/inline-edit/types.js.map +1 -0
  32. package/dist/consts.d.ts +11 -0
  33. package/dist/consts.d.ts.map +1 -0
  34. package/dist/consts.js +11 -0
  35. package/dist/consts.js.map +1 -0
  36. package/dist/error-overlay-plugin.d.ts +3 -0
  37. package/dist/error-overlay-plugin.d.ts.map +1 -0
  38. package/dist/error-overlay-plugin.js +15 -0
  39. package/dist/error-overlay-plugin.js.map +1 -0
  40. package/dist/html-injections-plugin.d.ts +8 -0
  41. package/dist/html-injections-plugin.d.ts.map +1 -0
  42. package/dist/html-injections-plugin.js +132 -0
  43. package/dist/html-injections-plugin.js.map +1 -0
  44. package/dist/index.d.ts +9 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +158 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/injections/layer-dropdown/consts.d.ts +20 -0
  49. package/dist/injections/layer-dropdown/consts.d.ts.map +1 -0
  50. package/dist/injections/layer-dropdown/consts.js +41 -0
  51. package/dist/injections/layer-dropdown/consts.js.map +1 -0
  52. package/dist/injections/layer-dropdown/controller.d.ts +4 -0
  53. package/dist/injections/layer-dropdown/controller.d.ts.map +1 -0
  54. package/dist/injections/layer-dropdown/controller.js +88 -0
  55. package/dist/injections/layer-dropdown/controller.js.map +1 -0
  56. package/dist/injections/layer-dropdown/dropdown-ui.d.ts +13 -0
  57. package/dist/injections/layer-dropdown/dropdown-ui.d.ts.map +1 -0
  58. package/dist/injections/layer-dropdown/dropdown-ui.js +186 -0
  59. package/dist/injections/layer-dropdown/dropdown-ui.js.map +1 -0
  60. package/dist/injections/layer-dropdown/types.d.ts +26 -0
  61. package/dist/injections/layer-dropdown/types.d.ts.map +1 -0
  62. package/dist/injections/layer-dropdown/types.js +3 -0
  63. package/dist/injections/layer-dropdown/types.js.map +1 -0
  64. package/dist/injections/layer-dropdown/utils.d.ts +25 -0
  65. package/dist/injections/layer-dropdown/utils.d.ts.map +1 -0
  66. package/dist/injections/layer-dropdown/utils.js +143 -0
  67. package/dist/injections/layer-dropdown/utils.js.map +1 -0
  68. package/dist/injections/navigation-notifier.d.ts +2 -0
  69. package/dist/injections/navigation-notifier.d.ts.map +1 -0
  70. package/dist/injections/navigation-notifier.js +34 -0
  71. package/dist/injections/navigation-notifier.js.map +1 -0
  72. package/dist/injections/sandbox-hmr-notifier.d.ts +2 -0
  73. package/dist/injections/sandbox-hmr-notifier.d.ts.map +1 -0
  74. package/dist/injections/sandbox-hmr-notifier.js +10 -0
  75. package/dist/injections/sandbox-hmr-notifier.js.map +1 -0
  76. package/dist/injections/sandbox-mount-observer.d.ts +2 -0
  77. package/dist/injections/sandbox-mount-observer.d.ts.map +1 -0
  78. package/dist/injections/sandbox-mount-observer.js +18 -0
  79. package/dist/injections/sandbox-mount-observer.js.map +1 -0
  80. package/dist/injections/unhandled-errors-handlers.d.ts +2 -0
  81. package/dist/injections/unhandled-errors-handlers.d.ts.map +1 -0
  82. package/dist/injections/unhandled-errors-handlers.js +93 -0
  83. package/dist/injections/unhandled-errors-handlers.js.map +1 -0
  84. package/dist/injections/utils.d.ts +65 -0
  85. package/dist/injections/utils.d.ts.map +1 -0
  86. package/dist/injections/utils.js +186 -0
  87. package/dist/injections/utils.js.map +1 -0
  88. package/dist/injections/visual-edit-agent.d.ts +2 -0
  89. package/dist/injections/visual-edit-agent.d.ts.map +1 -0
  90. package/dist/injections/visual-edit-agent.js +583 -0
  91. package/dist/injections/visual-edit-agent.js.map +1 -0
  92. package/dist/jsx-processor.d.ts +17 -0
  93. package/dist/jsx-processor.d.ts.map +1 -0
  94. package/dist/jsx-processor.js +129 -0
  95. package/dist/jsx-processor.js.map +1 -0
  96. package/dist/jsx-utils.d.ts +16 -0
  97. package/dist/jsx-utils.d.ts.map +1 -0
  98. package/dist/jsx-utils.js +98 -0
  99. package/dist/jsx-utils.js.map +1 -0
  100. package/dist/processors/collection-id-processor.d.ts +20 -0
  101. package/dist/processors/collection-id-processor.d.ts.map +1 -0
  102. package/dist/processors/collection-id-processor.js +182 -0
  103. package/dist/processors/collection-id-processor.js.map +1 -0
  104. package/dist/processors/collection-item-field-processor.d.ts +39 -0
  105. package/dist/processors/collection-item-field-processor.d.ts.map +1 -0
  106. package/dist/processors/collection-item-field-processor.js +289 -0
  107. package/dist/processors/collection-item-field-processor.js.map +1 -0
  108. package/dist/processors/collection-item-id-processor.d.ts +12 -0
  109. package/dist/processors/collection-item-id-processor.d.ts.map +1 -0
  110. package/dist/processors/collection-item-id-processor.js +50 -0
  111. package/dist/processors/collection-item-id-processor.js.map +1 -0
  112. package/dist/processors/static-array-processor.d.ts +28 -0
  113. package/dist/processors/static-array-processor.d.ts.map +1 -0
  114. package/dist/processors/static-array-processor.js +173 -0
  115. package/dist/processors/static-array-processor.js.map +1 -0
  116. package/dist/processors/utils/collection-tracing-utils.d.ts +36 -0
  117. package/dist/processors/utils/collection-tracing-utils.d.ts.map +1 -0
  118. package/dist/processors/utils/collection-tracing-utils.js +390 -0
  119. package/dist/processors/utils/collection-tracing-utils.js.map +1 -0
  120. package/dist/processors/utils/shared-utils.d.ts +96 -0
  121. package/dist/processors/utils/shared-utils.d.ts.map +1 -0
  122. package/dist/processors/utils/shared-utils.js +600 -0
  123. package/dist/processors/utils/shared-utils.js.map +1 -0
  124. package/dist/statics/index.mjs +16 -0
  125. package/dist/statics/index.mjs.map +1 -0
  126. package/dist/utils.d.ts +2 -0
  127. package/dist/utils.d.ts.map +1 -0
  128. package/dist/utils.js +22 -0
  129. package/dist/utils.js.map +1 -0
  130. package/dist/visual-edit-plugin.d.ts +3 -0
  131. package/dist/visual-edit-plugin.d.ts.map +1 -0
  132. package/dist/visual-edit-plugin.js +100 -0
  133. package/dist/visual-edit-plugin.js.map +1 -0
  134. package/package.json +75 -0
  135. package/src/ErrorOverlay.ts +71 -0
  136. package/src/bridge.ts +8 -0
  137. package/src/capabilities/inline-edit/controller.ts +254 -0
  138. package/src/capabilities/inline-edit/dom-utils.ts +58 -0
  139. package/src/capabilities/inline-edit/index.ts +2 -0
  140. package/src/capabilities/inline-edit/types.ts +35 -0
  141. package/src/consts.ts +11 -0
  142. package/src/error-overlay-plugin.ts +19 -0
  143. package/src/html-injections-plugin.ts +166 -0
  144. package/src/index.ts +225 -0
  145. package/src/injections/layer-dropdown/LAYERS.md +258 -0
  146. package/src/injections/layer-dropdown/consts.ts +51 -0
  147. package/src/injections/layer-dropdown/controller.ts +109 -0
  148. package/src/injections/layer-dropdown/dropdown-ui.ts +242 -0
  149. package/src/injections/layer-dropdown/types.ts +30 -0
  150. package/src/injections/layer-dropdown/utils.ts +175 -0
  151. package/src/injections/navigation-notifier.ts +43 -0
  152. package/src/injections/sandbox-hmr-notifier.ts +8 -0
  153. package/src/injections/sandbox-mount-observer.ts +25 -0
  154. package/src/injections/unhandled-errors-handlers.ts +114 -0
  155. package/src/injections/utils.ts +208 -0
  156. package/src/injections/visual-edit-agent.ts +706 -0
  157. package/src/jsx-processor.ts +169 -0
  158. package/src/jsx-utils.ts +131 -0
  159. package/src/processors/collection-id-processor.ts +261 -0
  160. package/src/processors/collection-item-field-processor.ts +439 -0
  161. package/src/processors/collection-item-id-processor.ts +69 -0
  162. package/src/processors/static-array-processor.ts +260 -0
  163. package/src/processors/utils/collection-tracing-utils.ts +507 -0
  164. package/src/processors/utils/shared-utils.ts +785 -0
  165. package/src/utils.ts +27 -0
  166. package/src/visual-edit-plugin.md +358 -0
  167. package/src/visual-edit-plugin.ts +110 -0
@@ -0,0 +1,439 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type * as t from "@babel/types";
3
+ import {
4
+ DATA_COLLECTION_ITEM_FIELD,
5
+ DATA_COLLECTION_ITEM_ID,
6
+ EXCLUDED_FIELDS,
7
+ } from "../consts.js";
8
+ import { JSXUtils } from "../jsx-utils.js";
9
+ import {
10
+ JSXAttributeUtils,
11
+ ExpressionAnalysisUtils,
12
+ StaticValueUtils,
13
+ } from "./utils/shared-utils.js";
14
+
15
+ export class DataItemFieldProcessor {
16
+ private attributeUtils: JSXAttributeUtils;
17
+ private expressionUtils: ExpressionAnalysisUtils;
18
+ private staticUtils: StaticValueUtils;
19
+
20
+ constructor(private types: typeof t) {
21
+ this.attributeUtils = new JSXAttributeUtils(types);
22
+ this.expressionUtils = new ExpressionAnalysisUtils(types);
23
+ this.staticUtils = new StaticValueUtils(types);
24
+ }
25
+
26
+ process(path: NodePath<t.JSXOpeningElement>): void {
27
+ if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ITEM_FIELD)) {
28
+ return;
29
+ }
30
+
31
+ const jsxElement = path.parentPath;
32
+ if (!jsxElement?.isJSXElement()) return;
33
+
34
+ const result = this.extractFieldFromChildren(jsxElement);
35
+ if (!result) return;
36
+
37
+ this.attributeUtils.addStringAttribute(
38
+ path,
39
+ DATA_COLLECTION_ITEM_FIELD,
40
+ result.fieldPath
41
+ );
42
+
43
+ this.tryAddItemId(path, result.rootIdentifier, result.fieldPath);
44
+ }
45
+
46
+ private extractFieldFromChildren(
47
+ jsxElement: NodePath<t.JSXElement>
48
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
49
+ const children = jsxElement.get("children");
50
+
51
+ for (const child of children) {
52
+ const result = this.extractFieldFromChild(child);
53
+ if (result) return result;
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ private extractFieldFromChild(
60
+ child: NodePath
61
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
62
+ if (child.isJSXExpressionContainer()) {
63
+ return this.extractFromExpressionContainer(child);
64
+ }
65
+
66
+ if (child.isJSXElement()) {
67
+ return this.extractFromChildElement(child);
68
+ }
69
+
70
+ return null;
71
+ }
72
+
73
+ private extractFromExpressionContainer(
74
+ container: NodePath<t.JSXExpressionContainer>
75
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
76
+ const expression = container.get("expression");
77
+
78
+ if (expression.isJSXEmptyExpression()) return null;
79
+
80
+ const expr = expression as NodePath<t.Expression>;
81
+
82
+ if (expr.isMemberExpression() || expr.isOptionalMemberExpression()) {
83
+ return this.extractFromMemberExpression(expr);
84
+ }
85
+
86
+ if (expr.isLogicalExpression() && expr.node.operator === "&&") {
87
+ const left = expr.get("left") as NodePath<t.Expression>;
88
+ if (
89
+ left.isMemberExpression() ||
90
+ left.isOptionalMemberExpression()
91
+ ) {
92
+ return this.extractFromMemberExpression(left);
93
+ }
94
+ }
95
+
96
+ if (expr.isCallExpression()) {
97
+ const callee = expr.get("callee");
98
+ if (callee.isMemberExpression()) {
99
+ const obj = callee.get("object") as NodePath<t.Expression>;
100
+ if (obj.isMemberExpression() || obj.isOptionalMemberExpression()) {
101
+ return this.extractFromMemberExpression(obj);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (expr.isIdentifier()) {
107
+ return this.extractFromSimpleIdentifier(expr);
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ private extractFromMemberExpression(
114
+ expr: NodePath<t.MemberExpression | t.OptionalMemberExpression>
115
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
116
+ const rootId = this.expressionUtils.extractRootIdentifier(
117
+ expr.node as t.Expression
118
+ );
119
+ if (!rootId) return null;
120
+
121
+ if (this.staticUtils.isDerivedFromStaticData(rootId.name, expr)) {
122
+ return null;
123
+ }
124
+
125
+ const parts = this.expressionUtils.collectMemberExpressionPath(
126
+ expr.node as t.Expression
127
+ );
128
+ if (parts.length < 2) return null;
129
+
130
+ const fieldParts = parts.slice(1);
131
+ const fieldPath = fieldParts.join(".");
132
+
133
+ if (EXCLUDED_FIELDS.includes(fieldPath)) return null;
134
+ if (fieldParts.some((p) => EXCLUDED_FIELDS.includes(p))) return null;
135
+
136
+ return { fieldPath, rootIdentifier: rootId };
137
+ }
138
+
139
+ private extractFromSimpleIdentifier(
140
+ expr: NodePath<t.Identifier>
141
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
142
+ const name = expr.node.name;
143
+ if (EXCLUDED_FIELDS.includes(name)) return null;
144
+ if (this.staticUtils.isDerivedFromStaticData(name, expr)) return null;
145
+
146
+ return { fieldPath: name, rootIdentifier: expr.node };
147
+ }
148
+
149
+ private extractFromChildElement(
150
+ child: NodePath<t.JSXElement>
151
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
152
+ const elementName = JSXUtils.getElementName(child.node.openingElement);
153
+ if (!elementName) return null;
154
+
155
+ if (elementName === "Image" || elementName === "img") {
156
+ return this.extractFromImageElement(child);
157
+ }
158
+
159
+ const logicalParent = child.parentPath;
160
+ if (logicalParent?.isJSXExpressionContainer()) {
161
+ const logicalExpr = logicalParent.parentPath;
162
+ if (logicalExpr?.isLogicalExpression()) {
163
+ return this.extractFromConditionalImage(child);
164
+ }
165
+ }
166
+
167
+ return null;
168
+ }
169
+
170
+ private extractFromImageElement(
171
+ imageElement: NodePath<t.JSXElement>
172
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
173
+ const openingElement = imageElement.get("openingElement");
174
+ const attrs = openingElement.node.attributes;
175
+
176
+ for (const attr of attrs) {
177
+ if (
178
+ this.types.isJSXAttribute(attr) &&
179
+ this.types.isJSXIdentifier(attr.name) &&
180
+ attr.name.name === "src"
181
+ ) {
182
+ if (
183
+ attr.value &&
184
+ this.types.isJSXExpressionContainer(attr.value) &&
185
+ this.types.isExpression(attr.value.expression) &&
186
+ !this.types.isJSXEmptyExpression(attr.value.expression)
187
+ ) {
188
+ const expr = attr.value.expression;
189
+ if (
190
+ this.types.isMemberExpression(expr) ||
191
+ this.types.isOptionalMemberExpression(expr)
192
+ ) {
193
+ const rootId = this.expressionUtils.extractRootIdentifier(expr);
194
+ if (!rootId) return null;
195
+
196
+ const parts = this.expressionUtils.collectMemberExpressionPath(expr);
197
+ if (parts.length < 2) return null;
198
+
199
+ const fieldPath = parts.slice(1).join(".");
200
+ if (EXCLUDED_FIELDS.includes(fieldPath)) return null;
201
+
202
+ return { fieldPath, rootIdentifier: rootId };
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ return null;
209
+ }
210
+
211
+ private extractFromConditionalImage(
212
+ child: NodePath<t.JSXElement>
213
+ ): { fieldPath: string; rootIdentifier: t.Identifier } | null {
214
+ const elementName = JSXUtils.getElementName(child.node.openingElement);
215
+ if (elementName === "Image" || elementName === "img") {
216
+ return this.extractFromImageElement(child);
217
+ }
218
+ return null;
219
+ }
220
+
221
+ private tryAddItemId(
222
+ path: NodePath<t.JSXOpeningElement>,
223
+ rootIdentifier: t.Identifier,
224
+ fieldPath: string
225
+ ): void {
226
+ if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ITEM_ID)) {
227
+ return;
228
+ }
229
+
230
+ const idField = this.resolveIdFieldName(path);
231
+
232
+ if (rootIdentifier.name === fieldPath) {
233
+ this.tryAddItemIdFromDestructuredParams(path, idField);
234
+ return;
235
+ }
236
+
237
+ const idExpr = this.buildIdExpression(rootIdentifier.name, idField);
238
+
239
+ const binding = path.scope.getBinding(rootIdentifier.name);
240
+ if (!binding) {
241
+ this.tryAddItemIdFromParentComponent(path, rootIdentifier, idField);
242
+ return;
243
+ }
244
+
245
+ if (
246
+ binding.path.isVariableDeclarator() ||
247
+ binding.kind === "param"
248
+ ) {
249
+ this.attributeUtils.addExpressionAttribute(
250
+ path,
251
+ DATA_COLLECTION_ITEM_ID,
252
+ idExpr
253
+ );
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Build the ID expression for a given root object name.
259
+ * If a specific idField was resolved from a key attribute, generates root?.id (or root?._id).
260
+ * If unknown (cross-file component), generates root?.id || root?._id to cover both conventions.
261
+ */
262
+ private buildIdExpression(
263
+ rootName: string,
264
+ idField: string | null
265
+ ): t.Expression {
266
+ if (idField) {
267
+ return this.types.optionalMemberExpression(
268
+ this.types.identifier(rootName),
269
+ this.types.identifier(idField),
270
+ false,
271
+ true
272
+ );
273
+ }
274
+
275
+ return this.types.logicalExpression(
276
+ "||",
277
+ this.types.optionalMemberExpression(
278
+ this.types.identifier(rootName),
279
+ this.types.identifier("id"),
280
+ false,
281
+ true
282
+ ),
283
+ this.types.optionalMemberExpression(
284
+ this.types.identifier(rootName),
285
+ this.types.identifier("_id"),
286
+ false,
287
+ true
288
+ )
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Walk up the JSX tree to find a key attribute that accesses .id or ._id.
294
+ * Returns null when no key is found (e.g. cross-file component receiving props).
295
+ */
296
+ private resolveIdFieldName(
297
+ path: NodePath<t.JSXOpeningElement>
298
+ ): string | null {
299
+ let current: NodePath | null = path.parentPath;
300
+ while (current) {
301
+ if (current.isJSXElement()) {
302
+ const opening = current.get("openingElement");
303
+ const field = this.extractIdFieldFromKey(opening);
304
+ if (field) return field;
305
+ }
306
+ current = current.parentPath;
307
+ }
308
+ return null;
309
+ }
310
+
311
+ private extractIdFieldFromKey(
312
+ path: NodePath<t.JSXOpeningElement>
313
+ ): string | null {
314
+ for (const attr of path.node.attributes) {
315
+ if (
316
+ this.types.isJSXAttribute(attr) &&
317
+ this.types.isJSXIdentifier(attr.name) &&
318
+ attr.name.name === "key" &&
319
+ attr.value &&
320
+ this.types.isJSXExpressionContainer(attr.value)
321
+ ) {
322
+ const expr = attr.value.expression;
323
+ if (
324
+ this.types.isMemberExpression(expr) &&
325
+ this.types.isIdentifier(expr.property)
326
+ ) {
327
+ const name = expr.property.name;
328
+ if (name === "id" || name === "_id") return name;
329
+ }
330
+ }
331
+ }
332
+ return null;
333
+ }
334
+
335
+ /**
336
+ * For simple identifiers from destructured component props like ({ name, price }),
337
+ * find or inject the id field in the ObjectPattern so we can reference it directly.
338
+ */
339
+ private tryAddItemIdFromDestructuredParams(
340
+ path: NodePath<t.JSXOpeningElement>,
341
+ idField: string | null
342
+ ): void {
343
+ const fn = path.getFunctionParent();
344
+ if (!fn) return;
345
+
346
+ const params = fn.get("params");
347
+ for (const param of Array.isArray(params) ? params : [params]) {
348
+ if (!param.isObjectPattern()) continue;
349
+
350
+ for (const prop of param.get("properties")) {
351
+ if (
352
+ prop.isObjectProperty() &&
353
+ this.types.isIdentifier(prop.node.key) &&
354
+ (prop.node.key.name === "id" || prop.node.key.name === "_id") &&
355
+ this.types.isIdentifier(prop.node.value)
356
+ ) {
357
+ this.attributeUtils.addExpressionAttribute(
358
+ path,
359
+ DATA_COLLECTION_ITEM_ID,
360
+ this.types.identifier(prop.node.value.name)
361
+ );
362
+ return;
363
+ }
364
+ }
365
+
366
+ const fieldToInject = idField ?? "id";
367
+ const newProp = this.types.objectProperty(
368
+ this.types.identifier(fieldToInject),
369
+ this.types.identifier(fieldToInject),
370
+ false,
371
+ true
372
+ );
373
+
374
+ const props = param.node.properties;
375
+ const lastProp = props[props.length - 1];
376
+ if (lastProp && this.types.isRestElement(lastProp)) {
377
+ props.splice(props.length - 1, 0, newProp);
378
+ } else {
379
+ props.push(newProp);
380
+ }
381
+ this.attributeUtils.addExpressionAttribute(
382
+ path,
383
+ DATA_COLLECTION_ITEM_ID,
384
+ this.types.identifier(fieldToInject)
385
+ );
386
+ return;
387
+ }
388
+ }
389
+
390
+ private tryAddItemIdFromParentComponent(
391
+ path: NodePath<t.JSXOpeningElement>,
392
+ rootIdentifier: t.Identifier,
393
+ idField: string | null
394
+ ): void {
395
+ let current: NodePath | null = path.parentPath;
396
+ while (current) {
397
+ if (current.isJSXElement()) {
398
+ const opening = current.get("openingElement");
399
+ const name = JSXUtils.getElementName(opening.node);
400
+ if (name && JSXUtils.isCustomComponent(name)) {
401
+ const keyAttr = this.findKeyWithId(opening);
402
+ if (keyAttr) {
403
+ const idExpr = this.buildIdExpression(
404
+ rootIdentifier.name,
405
+ idField
406
+ );
407
+ this.attributeUtils.addExpressionAttribute(
408
+ path,
409
+ DATA_COLLECTION_ITEM_ID,
410
+ idExpr
411
+ );
412
+ return;
413
+ }
414
+ }
415
+ }
416
+ current = current.parentPath;
417
+ }
418
+ }
419
+
420
+ private findKeyWithId(
421
+ path: NodePath<t.JSXOpeningElement>
422
+ ): boolean {
423
+ for (const attr of path.node.attributes) {
424
+ if (
425
+ this.types.isJSXAttribute(attr) &&
426
+ this.types.isJSXIdentifier(attr.name) &&
427
+ attr.name.name === "key" &&
428
+ attr.value &&
429
+ this.types.isJSXExpressionContainer(attr.value)
430
+ ) {
431
+ const expr = attr.value.expression;
432
+ if (this.types.isExpression(expr)) {
433
+ return this.expressionUtils.isIdAccess(expr);
434
+ }
435
+ }
436
+ }
437
+ return false;
438
+ }
439
+ }
@@ -0,0 +1,69 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type * as t from "@babel/types";
3
+ import { DATA_COLLECTION_ITEM_ID } from "../consts.js";
4
+ import {
5
+ JSXAttributeUtils,
6
+ ExpressionAnalysisUtils,
7
+ } from "./utils/shared-utils.js";
8
+
9
+ export class DataItemIdProcessor {
10
+ private attributeUtils: JSXAttributeUtils;
11
+ private expressionUtils: ExpressionAnalysisUtils;
12
+
13
+ constructor(private types: typeof t) {
14
+ this.attributeUtils = new JSXAttributeUtils(types);
15
+ this.expressionUtils = new ExpressionAnalysisUtils(types);
16
+ }
17
+
18
+ process(path: NodePath<t.JSXOpeningElement>): void {
19
+ if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ITEM_ID)) {
20
+ return;
21
+ }
22
+
23
+ const keyAttr = this.findKeyAttribute(path);
24
+ if (!keyAttr) return;
25
+
26
+ const expression = this.extractKeyExpression(keyAttr);
27
+ if (!expression) return;
28
+
29
+ if (!this.expressionUtils.isIdAccess(expression)) return;
30
+
31
+ const optionalExpr = this.types.isMemberExpression(expression)
32
+ ? this.expressionUtils.createOptionalChainExpression(expression)
33
+ : expression;
34
+
35
+ this.attributeUtils.addExpressionAttribute(
36
+ path,
37
+ DATA_COLLECTION_ITEM_ID,
38
+ optionalExpr
39
+ );
40
+ }
41
+
42
+ private findKeyAttribute(
43
+ path: NodePath<t.JSXOpeningElement>
44
+ ): t.JSXAttribute | null {
45
+ for (const attr of path.node.attributes) {
46
+ if (
47
+ this.types.isJSXAttribute(attr) &&
48
+ this.types.isJSXIdentifier(attr.name) &&
49
+ attr.name.name === "key"
50
+ ) {
51
+ return attr;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ private extractKeyExpression(
58
+ attr: t.JSXAttribute
59
+ ): t.Expression | null {
60
+ if (!attr.value) return null;
61
+
62
+ if (this.types.isJSXExpressionContainer(attr.value)) {
63
+ const expr = attr.value.expression;
64
+ if (this.types.isExpression(expr)) return expr;
65
+ }
66
+
67
+ return null;
68
+ }
69
+ }