@base44-preview/vite-plugin 0.2.25-pr.36.792e85b → 0.2.26-pr.43.056eecd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/consts.d.ts +13 -0
- package/dist/consts.d.ts.map +1 -0
- package/dist/consts.js +13 -0
- package/dist/consts.js.map +1 -0
- package/dist/jsx-processor.d.ts +10 -1
- package/dist/jsx-processor.d.ts.map +1 -1
- package/dist/jsx-processor.js +47 -5
- package/dist/jsx-processor.js.map +1 -1
- package/dist/jsx-utils.d.ts +9 -0
- package/dist/jsx-utils.d.ts.map +1 -1
- package/dist/jsx-utils.js +86 -0
- package/dist/jsx-utils.js.map +1 -1
- package/dist/processors/collection-id-processor.d.ts +20 -0
- package/dist/processors/collection-id-processor.d.ts.map +1 -0
- package/dist/processors/collection-id-processor.js +182 -0
- package/dist/processors/collection-id-processor.js.map +1 -0
- package/dist/processors/collection-item-field-processor.d.ts +22 -0
- package/dist/processors/collection-item-field-processor.d.ts.map +1 -0
- package/dist/processors/collection-item-field-processor.js +215 -0
- package/dist/processors/collection-item-field-processor.js.map +1 -0
- package/dist/processors/collection-item-id-processor.d.ts +12 -0
- package/dist/processors/collection-item-id-processor.d.ts.map +1 -0
- package/dist/processors/collection-item-id-processor.js +50 -0
- package/dist/processors/collection-item-id-processor.js.map +1 -0
- package/dist/processors/collection-reference-field-processor.d.ts +31 -0
- package/dist/processors/collection-reference-field-processor.d.ts.map +1 -0
- package/dist/processors/collection-reference-field-processor.js +174 -0
- package/dist/processors/collection-reference-field-processor.js.map +1 -0
- package/dist/processors/collection-tracing-utils.d.ts +31 -0
- package/dist/processors/collection-tracing-utils.d.ts.map +1 -0
- package/dist/processors/collection-tracing-utils.js +326 -0
- package/dist/processors/collection-tracing-utils.js.map +1 -0
- package/dist/processors/shared-utils.d.ts +64 -0
- package/dist/processors/shared-utils.d.ts.map +1 -1
- package/dist/processors/shared-utils.js +464 -0
- package/dist/processors/shared-utils.js.map +1 -1
- package/dist/processors/static-array-processor.d.ts +2 -3
- package/dist/processors/static-array-processor.d.ts.map +1 -1
- package/dist/processors/static-array-processor.js +2 -3
- package/dist/processors/static-array-processor.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/visual-edit-plugin.d.ts +0 -1
- package/dist/visual-edit-plugin.d.ts.map +1 -1
- package/dist/visual-edit-plugin.js +29 -178
- package/dist/visual-edit-plugin.js.map +1 -1
- package/package.json +1 -1
- package/src/consts.ts +14 -0
- package/src/jsx-processor.ts +58 -9
- package/src/jsx-utils.ts +116 -0
- package/src/processors/collection-id-processor.ts +261 -0
- package/src/processors/collection-item-field-processor.ts +321 -0
- package/src/processors/collection-item-id-processor.ts +69 -0
- package/src/processors/collection-reference-field-processor.ts +225 -0
- package/src/processors/collection-tracing-utils.ts +436 -0
- package/src/processors/shared-utils.ts +595 -0
- package/src/processors/static-array-processor.ts +6 -3
- package/src/types.ts +4 -0
- package/src/visual-edit-plugin.ts +34 -215
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { NodePath } from "@babel/traverse";
|
|
2
|
+
import type * as t from "@babel/types";
|
|
3
|
+
import type { CollectionInfo } from "../types.js";
|
|
4
|
+
import {
|
|
5
|
+
DATA_COLLECTION_ID,
|
|
6
|
+
DATA_COLLECTION_REFERENCE,
|
|
7
|
+
MAX_JSX_DEPTH,
|
|
8
|
+
} from "../consts.js";
|
|
9
|
+
import { JSXUtils } from "../jsx-utils.js";
|
|
10
|
+
import {
|
|
11
|
+
JSXAttributeUtils,
|
|
12
|
+
PathNavigationUtils,
|
|
13
|
+
ExpressionAnalysisUtils,
|
|
14
|
+
} from "./shared-utils.js";
|
|
15
|
+
import { CollectionTracingUtils } from "./collection-tracing-utils.js";
|
|
16
|
+
|
|
17
|
+
export class CollectionIdProcessor {
|
|
18
|
+
private attributeUtils: JSXAttributeUtils;
|
|
19
|
+
private pathUtils: PathNavigationUtils;
|
|
20
|
+
private expressionUtils: ExpressionAnalysisUtils;
|
|
21
|
+
private tracingUtils: CollectionTracingUtils;
|
|
22
|
+
|
|
23
|
+
constructor(private types: typeof t) {
|
|
24
|
+
this.attributeUtils = new JSXAttributeUtils(types);
|
|
25
|
+
this.pathUtils = new PathNavigationUtils(types);
|
|
26
|
+
this.expressionUtils = new ExpressionAnalysisUtils(types);
|
|
27
|
+
this.tracingUtils = new CollectionTracingUtils(types);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process(path: NodePath<t.JSXOpeningElement>): void {
|
|
31
|
+
if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ID)) return;
|
|
32
|
+
|
|
33
|
+
const info =
|
|
34
|
+
this.tryDirectMapInChildren(path) ??
|
|
35
|
+
this.tryGetByIdInChildren(path) ??
|
|
36
|
+
this.tryComponentRootWithCollectionProp(path);
|
|
37
|
+
|
|
38
|
+
if (!info) return;
|
|
39
|
+
|
|
40
|
+
const target = this.pathUtils.findDOMElementTarget(path) ?? path;
|
|
41
|
+
|
|
42
|
+
this.attributeUtils.addStringAttribute(target, DATA_COLLECTION_ID, info.id);
|
|
43
|
+
|
|
44
|
+
if (info.references.length > 0) {
|
|
45
|
+
this.attributeUtils.addStringAttribute(
|
|
46
|
+
target,
|
|
47
|
+
DATA_COLLECTION_REFERENCE,
|
|
48
|
+
info.references.join(",")
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private tryDirectMapInChildren(
|
|
54
|
+
path: NodePath<t.JSXOpeningElement>
|
|
55
|
+
): CollectionInfo | null {
|
|
56
|
+
const jsxElement = path.parentPath;
|
|
57
|
+
if (!jsxElement?.isJSXElement()) return null;
|
|
58
|
+
|
|
59
|
+
const children = jsxElement.get("children");
|
|
60
|
+
for (const child of children) {
|
|
61
|
+
if (!child.isJSXExpressionContainer()) continue;
|
|
62
|
+
|
|
63
|
+
const expression = child.get("expression");
|
|
64
|
+
if (expression.isJSXEmptyExpression()) continue;
|
|
65
|
+
|
|
66
|
+
const mapSource = this.extractMapSource(
|
|
67
|
+
expression as NodePath<t.Expression>
|
|
68
|
+
);
|
|
69
|
+
if (mapSource) return mapSource;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private extractMapSource(
|
|
76
|
+
expr: NodePath<t.Expression>
|
|
77
|
+
): CollectionInfo | null {
|
|
78
|
+
if (expr.isCallExpression()) {
|
|
79
|
+
return this.traceMapCall(expr);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (expr.isLogicalExpression()) {
|
|
83
|
+
if (expr.node.operator === "&&") {
|
|
84
|
+
const right = expr.get("right") as NodePath<t.Expression>;
|
|
85
|
+
return this.extractMapSource(right);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (expr.isConditionalExpression()) {
|
|
90
|
+
const consequent = expr.get("consequent") as NodePath<t.Expression>;
|
|
91
|
+
const alternate = expr.get("alternate") as NodePath<t.Expression>;
|
|
92
|
+
return (
|
|
93
|
+
this.extractMapSource(consequent) ?? this.extractMapSource(alternate)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (expr.isOptionalCallExpression()) {
|
|
98
|
+
return this.traceOptionalMapCall(expr);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private traceMapCall(
|
|
105
|
+
callPath: NodePath<t.CallExpression>
|
|
106
|
+
): CollectionInfo | null {
|
|
107
|
+
const callee = callPath.get("callee");
|
|
108
|
+
if (!callee.isMemberExpression()) return null;
|
|
109
|
+
|
|
110
|
+
const property = callee.get("property") as NodePath;
|
|
111
|
+
if (
|
|
112
|
+
!property.isIdentifier() ||
|
|
113
|
+
(property.node.name !== "map" && property.node.name !== "flatMap")
|
|
114
|
+
) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!this.callbackProducesJSX(callPath)) return null;
|
|
119
|
+
|
|
120
|
+
const arrayObj = callee.get("object") as NodePath<t.Expression>;
|
|
121
|
+
return this.tracingUtils.traceCollectionSource(arrayObj);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private traceOptionalMapCall(
|
|
125
|
+
callPath: NodePath<t.OptionalCallExpression>
|
|
126
|
+
): CollectionInfo | null {
|
|
127
|
+
const callee = callPath.get("callee");
|
|
128
|
+
if (
|
|
129
|
+
!callee.isMemberExpression() &&
|
|
130
|
+
!callee.isOptionalMemberExpression()
|
|
131
|
+
) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const property = (callee as NodePath<t.MemberExpression>).get(
|
|
136
|
+
"property"
|
|
137
|
+
) as NodePath;
|
|
138
|
+
if (
|
|
139
|
+
!property.isIdentifier() ||
|
|
140
|
+
(property.node.name !== "map" && property.node.name !== "flatMap")
|
|
141
|
+
) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const arrayObj = (callee as NodePath<t.MemberExpression>).get(
|
|
146
|
+
"object"
|
|
147
|
+
) as NodePath<t.Expression>;
|
|
148
|
+
return this.tracingUtils.traceCollectionSource(arrayObj);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private callbackProducesJSX(
|
|
152
|
+
callPath: NodePath<t.CallExpression>
|
|
153
|
+
): boolean {
|
|
154
|
+
const args = callPath.get("arguments");
|
|
155
|
+
const callback = args[0];
|
|
156
|
+
if (!callback) return false;
|
|
157
|
+
|
|
158
|
+
if (callback.isArrowFunctionExpression()) {
|
|
159
|
+
const body = callback.get("body");
|
|
160
|
+
if (body.isBlockStatement()) {
|
|
161
|
+
return JSXUtils.doesBlockReturnJSX(body.node);
|
|
162
|
+
}
|
|
163
|
+
return JSXUtils.producesJSX(body.node);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (callback.isFunctionExpression()) {
|
|
167
|
+
return JSXUtils.doesBlockReturnJSX(callback.node.body);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private tryGetByIdInChildren(
|
|
174
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
175
|
+
depth: number = 0
|
|
176
|
+
): CollectionInfo | null {
|
|
177
|
+
if (depth > MAX_JSX_DEPTH) return null;
|
|
178
|
+
|
|
179
|
+
const jsxElement = path.parentPath;
|
|
180
|
+
if (!jsxElement?.isJSXElement()) return null;
|
|
181
|
+
|
|
182
|
+
const children = jsxElement.get("children");
|
|
183
|
+
for (const child of children) {
|
|
184
|
+
const info = this.checkChildForGetById(child, depth);
|
|
185
|
+
if (info) return info;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const attr of path.node.attributes) {
|
|
189
|
+
if (
|
|
190
|
+
this.types.isJSXAttribute(attr) &&
|
|
191
|
+
attr.value &&
|
|
192
|
+
this.types.isJSXExpressionContainer(attr.value)
|
|
193
|
+
) {
|
|
194
|
+
const expr = attr.value.expression;
|
|
195
|
+
if (this.types.isMemberExpression(expr)) {
|
|
196
|
+
const info = this.tracingUtils.traceGetByIdSource(path);
|
|
197
|
+
if (info) return info;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private checkChildForGetById(
|
|
206
|
+
child: NodePath,
|
|
207
|
+
depth: number
|
|
208
|
+
): CollectionInfo | null {
|
|
209
|
+
if (child.isJSXExpressionContainer()) {
|
|
210
|
+
const expr = child.get("expression");
|
|
211
|
+
if (
|
|
212
|
+
expr.isMemberExpression() ||
|
|
213
|
+
expr.isOptionalMemberExpression()
|
|
214
|
+
) {
|
|
215
|
+
return this.tracingUtils.traceGetByIdSource(child);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (child.isJSXElement()) {
|
|
220
|
+
const childOpening = child.get("openingElement");
|
|
221
|
+
return this.tryGetByIdInChildren(childOpening, depth + 1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private tryComponentRootWithCollectionProp(
|
|
228
|
+
path: NodePath<t.JSXOpeningElement>
|
|
229
|
+
): CollectionInfo | null {
|
|
230
|
+
if (!this.pathUtils.isRootReturnElement(path)) return null;
|
|
231
|
+
|
|
232
|
+
const fn = this.pathUtils.findEnclosingFunction(path);
|
|
233
|
+
if (!fn) return null;
|
|
234
|
+
|
|
235
|
+
let info: CollectionInfo | null = null;
|
|
236
|
+
|
|
237
|
+
fn.traverse({
|
|
238
|
+
CallExpression: (callPath: NodePath<t.CallExpression>) => {
|
|
239
|
+
if (info) return;
|
|
240
|
+
|
|
241
|
+
const callee = callPath.get("callee");
|
|
242
|
+
if (!callee.isMemberExpression()) return;
|
|
243
|
+
|
|
244
|
+
const prop = callee.get("property") as NodePath;
|
|
245
|
+
if (
|
|
246
|
+
!prop.isIdentifier() ||
|
|
247
|
+
(prop.node.name !== "map" && prop.node.name !== "flatMap")
|
|
248
|
+
) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!this.callbackProducesJSX(callPath)) return;
|
|
253
|
+
|
|
254
|
+
const arrayObj = callee.get("object") as NodePath<t.Expression>;
|
|
255
|
+
info = this.tracingUtils.traceCollectionSource(arrayObj);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return info;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
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 "./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);
|
|
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
|
+
): void {
|
|
225
|
+
if (this.attributeUtils.hasAttribute(path, DATA_COLLECTION_ITEM_ID)) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const idExpr = this.types.optionalMemberExpression(
|
|
230
|
+
this.types.identifier(rootIdentifier.name),
|
|
231
|
+
this.types.identifier("_id"),
|
|
232
|
+
false,
|
|
233
|
+
true
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const binding = path.scope.getBinding(rootIdentifier.name);
|
|
237
|
+
if (!binding) {
|
|
238
|
+
this.tryAddItemIdFromParentComponent(path, rootIdentifier);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (binding.path.isVariableDeclarator()) {
|
|
243
|
+
this.attributeUtils.addExpressionAttribute(
|
|
244
|
+
path,
|
|
245
|
+
DATA_COLLECTION_ITEM_ID,
|
|
246
|
+
idExpr
|
|
247
|
+
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const fn = path.getFunctionParent();
|
|
252
|
+
if (fn) {
|
|
253
|
+
const params = fn.get("params");
|
|
254
|
+
for (const param of (Array.isArray(params) ? params : [params])) {
|
|
255
|
+
if (param.isIdentifier() && param.node.name === rootIdentifier.name) {
|
|
256
|
+
this.attributeUtils.addExpressionAttribute(
|
|
257
|
+
path,
|
|
258
|
+
DATA_COLLECTION_ITEM_ID,
|
|
259
|
+
idExpr
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private tryAddItemIdFromParentComponent(
|
|
268
|
+
path: NodePath<t.JSXOpeningElement>,
|
|
269
|
+
rootIdentifier: t.Identifier
|
|
270
|
+
): void {
|
|
271
|
+
let current: NodePath | null = path.parentPath;
|
|
272
|
+
while (current) {
|
|
273
|
+
if (current.isJSXElement()) {
|
|
274
|
+
const opening = current.get("openingElement");
|
|
275
|
+
const name = JSXUtils.getElementName(opening.node);
|
|
276
|
+
if (name && JSXUtils.isCustomComponent(name)) {
|
|
277
|
+
const keyAttr = this.findKeyWithId(opening);
|
|
278
|
+
if (keyAttr) {
|
|
279
|
+
const idExpr = this.types.optionalMemberExpression(
|
|
280
|
+
this.types.identifier(rootIdentifier.name),
|
|
281
|
+
this.types.identifier("_id"),
|
|
282
|
+
false,
|
|
283
|
+
true
|
|
284
|
+
);
|
|
285
|
+
this.attributeUtils.addExpressionAttribute(
|
|
286
|
+
path,
|
|
287
|
+
DATA_COLLECTION_ITEM_ID,
|
|
288
|
+
idExpr
|
|
289
|
+
);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
current = current.parentPath;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private findKeyWithId(
|
|
299
|
+
path: NodePath<t.JSXOpeningElement>
|
|
300
|
+
): boolean {
|
|
301
|
+
for (const attr of path.node.attributes) {
|
|
302
|
+
if (
|
|
303
|
+
this.types.isJSXAttribute(attr) &&
|
|
304
|
+
this.types.isJSXIdentifier(attr.name) &&
|
|
305
|
+
attr.name.name === "key" &&
|
|
306
|
+
attr.value &&
|
|
307
|
+
this.types.isJSXExpressionContainer(attr.value)
|
|
308
|
+
) {
|
|
309
|
+
const expr = attr.value.expression;
|
|
310
|
+
if (this.types.isExpression(expr)) {
|
|
311
|
+
return (
|
|
312
|
+
this.types.isMemberExpression(expr) &&
|
|
313
|
+
this.types.isIdentifier(expr.property) &&
|
|
314
|
+
expr.property.name === "_id"
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -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 "./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
|
+
}
|