@trojanbox-vcp-test/site-edit-engine 0.1.0 → 0.2.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.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +127 -2
- package/dist/internal/protocol/operation.d.ts +2 -0
- package/dist/internal/protocol/render.d.ts +3 -30
- package/dist/internal/protocol.d.ts +1 -1
- package/dist/next-app-router.js +1 -140
- package/dist/preview-runtime.d.ts +249 -303
- package/dist/runtime-sync.d.ts +8 -8
- package/dist/runtime.d.ts +1 -127
- package/dist/site-edit-instrumentation.d.ts +1 -1
- package/dist/source-watcher.js +1 -150
- package/dist/types.d.ts +12 -14
- package/dist/webpack-loader.cjs +50 -588
- package/package.json +2 -2
- package/dist/execute-integration/execute-fixture-harness.d.ts +0 -25
- package/dist/execute-integration/execute-fixture-harness.js +0 -37
- package/dist/internal/ast/diagnostics/index.d.ts +0 -5
- package/dist/internal/ast/diagnostics/index.js +0 -25
- package/dist/internal/ast/history/index.d.ts +0 -15
- package/dist/internal/ast/history/index.js +0 -62
- package/dist/internal/ast/index.d.ts +0 -8
- package/dist/internal/ast/index.js +0 -5
- package/dist/internal/ast/locators/index.d.ts +0 -1
- package/dist/internal/ast/locators/index.js +0 -1
- package/dist/internal/ast/locators/resolve-locator.d.ts +0 -16
- package/dist/internal/ast/locators/resolve-locator.js +0 -920
- package/dist/internal/ast/parser/SourceParser.d.ts +0 -30
- package/dist/internal/ast/parser/SourceParser.js +0 -49
- package/dist/internal/ast/parser/index.d.ts +0 -21
- package/dist/internal/ast/parser/index.js +0 -64
- package/dist/internal/ast/primitives/conditional/conditional-primitives.d.ts +0 -18
- package/dist/internal/ast/primitives/conditional/conditional-primitives.js +0 -237
- package/dist/internal/ast/primitives/conditional/index.d.ts +0 -1
- package/dist/internal/ast/primitives/conditional/index.js +0 -1
- package/dist/internal/ast/primitives/imports/add-import.d.ts +0 -18
- package/dist/internal/ast/primitives/imports/add-import.js +0 -111
- package/dist/internal/ast/primitives/imports/index.d.ts +0 -2
- package/dist/internal/ast/primitives/imports/index.js +0 -2
- package/dist/internal/ast/primitives/imports/remove-import.d.ts +0 -15
- package/dist/internal/ast/primitives/imports/remove-import.js +0 -72
- package/dist/internal/ast/primitives/index.d.ts +0 -10
- package/dist/internal/ast/primitives/index.js +0 -10
- package/dist/internal/ast/primitives/jsx/index.d.ts +0 -4
- package/dist/internal/ast/primitives/jsx/index.js +0 -4
- package/dist/internal/ast/primitives/jsx/insert-child.d.ts +0 -11
- package/dist/internal/ast/primitives/jsx/insert-child.js +0 -69
- package/dist/internal/ast/primitives/jsx/move-node.d.ts +0 -9
- package/dist/internal/ast/primitives/jsx/move-node.js +0 -76
- package/dist/internal/ast/primitives/jsx/remove-node.d.ts +0 -7
- package/dist/internal/ast/primitives/jsx/remove-node.js +0 -36
- package/dist/internal/ast/primitives/jsx/update-text.d.ts +0 -8
- package/dist/internal/ast/primitives/jsx/update-text.js +0 -81
- package/dist/internal/ast/primitives/next/index.d.ts +0 -1
- package/dist/internal/ast/primitives/next/index.js +0 -1
- package/dist/internal/ast/primitives/next/next-primitives.d.ts +0 -43
- package/dist/internal/ast/primitives/next/next-primitives.js +0 -211
- package/dist/internal/ast/primitives/shared.d.ts +0 -60
- package/dist/internal/ast/primitives/shared.js +0 -176
- package/dist/internal/ast/primitives/style/class-expression.d.ts +0 -23
- package/dist/internal/ast/primitives/style/class-expression.js +0 -174
- package/dist/internal/ast/primitives/style/index.d.ts +0 -1
- package/dist/internal/ast/primitives/style/index.js +0 -1
- package/dist/internal/ast/primitives/style/style-primitives.d.ts +0 -49
- package/dist/internal/ast/primitives/style/style-primitives.js +0 -555
- package/dist/internal/ast/primitives/values/index.d.ts +0 -1
- package/dist/internal/ast/primitives/values/index.js +0 -1
- package/dist/internal/ast/primitives/values/value-primitives.d.ts +0 -42
- package/dist/internal/ast/primitives/values/value-primitives.js +0 -158
- package/dist/internal/ast/printer/SourcePrinter.d.ts +0 -21
- package/dist/internal/ast/printer/SourcePrinter.js +0 -76
- package/dist/internal/ast/printer/index.d.ts +0 -6
- package/dist/internal/ast/printer/index.js +0 -126
- package/dist/internal/ast/types.d.ts +0 -190
- package/dist/internal/ast/types.js +0 -1
- package/dist/internal/capability/capability-resolver.d.ts +0 -16
- package/dist/internal/capability/capability-resolver.js +0 -127
- package/dist/internal/classname-source.d.ts +0 -24
- package/dist/internal/classname-source.js +0 -220
- package/dist/internal/contracts/IEditEngineRuntime.d.ts +0 -18
- package/dist/internal/contracts/IEditEngineRuntime.js +0 -1
- package/dist/internal/domain/EditDiagnostic.d.ts +0 -38
- package/dist/internal/domain/EditDiagnostic.js +0 -43
- package/dist/internal/events/event-bus.d.ts +0 -14
- package/dist/internal/events/event-bus.js +0 -21
- package/dist/internal/graph/graph-builder.d.ts +0 -12
- package/dist/internal/graph/graph-builder.js +0 -1371
- package/dist/internal/graph/import-resolver.d.ts +0 -31
- package/dist/internal/graph/import-resolver.js +0 -109
- package/dist/internal/graph/project-graph-builder.d.ts +0 -32
- package/dist/internal/graph/project-graph-builder.js +0 -133
- package/dist/internal/graph/types.d.ts +0 -114
- package/dist/internal/graph/types.js +0 -6
- package/dist/internal/history/undo-redo.d.ts +0 -28
- package/dist/internal/history/undo-redo.js +0 -42
- package/dist/internal/index.d.ts +0 -2
- package/dist/internal/index.js +0 -1
- package/dist/internal/planner/planner.d.ts +0 -104
- package/dist/internal/planner/planner.js +0 -2533
- package/dist/internal/planner/types.d.ts +0 -275
- package/dist/internal/planner/types.js +0 -6
- package/dist/internal/protocol/boundary.js +0 -3
- package/dist/internal/protocol/capability.js +0 -8
- package/dist/internal/protocol/error.js +0 -38
- package/dist/internal/protocol/event.js +0 -3
- package/dist/internal/protocol/identity.js +0 -30
- package/dist/internal/protocol/operation.js +0 -8
- package/dist/internal/protocol/render.js +0 -3
- package/dist/internal/protocol.js +0 -2
- package/dist/internal/provenance/binding-graph.d.ts +0 -39
- package/dist/internal/provenance/binding-graph.js +0 -184
- package/dist/internal/provenance/capability-policy.d.ts +0 -15
- package/dist/internal/provenance/capability-policy.js +0 -96
- package/dist/internal/provenance/data-source-classifier.d.ts +0 -14
- package/dist/internal/provenance/data-source-classifier.js +0 -281
- package/dist/internal/provenance/resolve-text-provenance.d.ts +0 -45
- package/dist/internal/provenance/resolve-text-provenance.js +0 -3090
- package/dist/internal/provenance/types.d.ts +0 -89
- package/dist/internal/provenance/types.js +0 -1
- package/dist/internal/render/component-semantic.d.ts +0 -11
- package/dist/internal/render/component-semantic.js +0 -141
- package/dist/internal/render/content-model.d.ts +0 -3
- package/dist/internal/render/content-model.js +0 -89
- package/dist/internal/render/media-model.d.ts +0 -3
- package/dist/internal/render/media-model.js +0 -45
- package/dist/internal/render/provenance-types.d.ts +0 -33
- package/dist/internal/render/provenance-types.js +0 -1
- package/dist/internal/render/render-projection.d.ts +0 -24
- package/dist/internal/render/render-projection.js +0 -281
- package/dist/internal/render/tailwind-style-model.d.ts +0 -19
- package/dist/internal/render/tailwind-style-model.js +0 -1187
- package/dist/internal/runtime/EditEngineRuntime.d.ts +0 -25
- package/dist/internal/runtime/EditEngineRuntime.js +0 -89
- package/dist/internal/runtime/EditEngineRuntimeSnapshot.d.ts +0 -31
- package/dist/internal/runtime/EditEngineRuntimeSnapshot.js +0 -15
- package/dist/internal/runtime/InternalEditEngine.d.ts +0 -44
- package/dist/internal/runtime/InternalEditEngine.js +0 -1391
- package/dist/internal/runtime.d.ts +0 -3
- package/dist/internal/runtime.js +0 -1
- package/dist/internal/topology/topology.d.ts +0 -6
- package/dist/internal/topology/topology.js +0 -98
- package/dist/internal/topology/types.d.ts +0 -35
- package/dist/internal/topology/types.js +0 -5
- package/dist/internal/types.d.ts +0 -1
- package/dist/internal/types.js +0 -1
- package/dist/internal/writeback/in-memory-fs.d.ts +0 -7
- package/dist/internal/writeback/in-memory-fs.js +0 -44
- package/dist/internal/writeback/types.d.ts +0 -45
- package/dist/internal/writeback/types.js +0 -7
- package/dist/internal/writeback/writeback-service.d.ts +0 -7
- package/dist/internal/writeback/writeback-service.js +0 -568
- package/dist/internal-adapter.d.ts +0 -18
- package/dist/internal-adapter.js +0 -350
- package/dist/next-app-router-fs.js +0 -64
- package/dist/preview-runtime.js +0 -102
- package/dist/public-file-system.js +0 -1
- package/dist/runtime-sync.js +0 -321
- package/dist/runtime.js +0 -134
- package/dist/site-edit-instrumentation.js +0 -322
- package/dist/snapshot-file-system.d.ts +0 -19
- package/dist/snapshot-file-system.js +0 -49
- package/dist/source-writeback-test-harness.d.ts +0 -244
- package/dist/source-writeback-test-harness.js +0 -119
- package/dist/types.js +0 -1
|
@@ -1,1371 +0,0 @@
|
|
|
1
|
-
// ─── Graph Builder ───────────────────────────────────
|
|
2
|
-
// 来源: docs/06-development-checklist.md §4
|
|
3
|
-
//
|
|
4
|
-
// 从单个源文件 AST 构建 RenderSourceGraph。
|
|
5
|
-
// 职责:
|
|
6
|
-
// - 解析文件 AST(Babel)
|
|
7
|
-
// - 遍历 JSX 树
|
|
8
|
-
// - 构建 ElementNode / RegionNode / TextSegment
|
|
9
|
-
// - 计算 SourceIdentity(使用 identity 模块)
|
|
10
|
-
// - 维护文件索引
|
|
11
|
-
import * as parser from "@babel/parser";
|
|
12
|
-
import _traverse from "@babel/traverse";
|
|
13
|
-
import * as t from "@babel/types";
|
|
14
|
-
import { extractLocalImportsFromAst } from "./import-resolver.js";
|
|
15
|
-
import { computeSourceIdentityKey } from "../protocol.js";
|
|
16
|
-
// Handle ESM default import for @babel/traverse
|
|
17
|
-
const traverse = (typeof _traverse === "function" ? _traverse : _traverse.default);
|
|
18
|
-
// ── Helper: parse source ──
|
|
19
|
-
function parseSource(source) {
|
|
20
|
-
try {
|
|
21
|
-
return parser.parse(source, {
|
|
22
|
-
sourceType: "module",
|
|
23
|
-
plugins: ["jsx", "typescript"],
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// ── Helper: extract source range ──
|
|
31
|
-
function toSourceRange(node) {
|
|
32
|
-
return {
|
|
33
|
-
startLine: node.loc?.start.line ?? 0,
|
|
34
|
-
startColumn: node.loc?.start.column ?? 0,
|
|
35
|
-
endLine: node.loc?.end.line ?? 0,
|
|
36
|
-
endColumn: node.loc?.end.column ?? 0,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
// ── Helper: check if a tag is a component (PascalCase) ──
|
|
40
|
-
function isComponentTag(tag) {
|
|
41
|
-
return /^[A-Z]/.test(tag);
|
|
42
|
-
}
|
|
43
|
-
// ── Helper: check if JSXElement is a Fragment ──
|
|
44
|
-
function isFragment(node) {
|
|
45
|
-
const opening = node.openingElement;
|
|
46
|
-
if (t.isJSXIdentifier(opening.name)) {
|
|
47
|
-
return opening.name.name === "Fragment";
|
|
48
|
-
}
|
|
49
|
-
if (t.isJSXMemberExpression(opening.name)) {
|
|
50
|
-
return opening.name.property.name === "Fragment";
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
function isJSXFragment(node) {
|
|
55
|
-
return t.isJSXFragment(node);
|
|
56
|
-
}
|
|
57
|
-
// ── Helper: get tag name from JSX opening element ──
|
|
58
|
-
function getTagName(opening) {
|
|
59
|
-
if (t.isJSXIdentifier(opening.name)) {
|
|
60
|
-
return opening.name.name;
|
|
61
|
-
}
|
|
62
|
-
if (t.isJSXMemberExpression(opening.name)) {
|
|
63
|
-
return getMemberExpressionName(opening.name);
|
|
64
|
-
}
|
|
65
|
-
if (t.isJSXNamespacedName(opening.name)) {
|
|
66
|
-
return `${opening.name.namespace.name}:${opening.name.name.name}`;
|
|
67
|
-
}
|
|
68
|
-
return "unknown";
|
|
69
|
-
}
|
|
70
|
-
function getMemberExpressionName(expr) {
|
|
71
|
-
if (t.isJSXIdentifier(expr.object)) {
|
|
72
|
-
return `${expr.object.name}.${expr.property.name}`;
|
|
73
|
-
}
|
|
74
|
-
if (t.isJSXMemberExpression(expr.object)) {
|
|
75
|
-
return `${getMemberExpressionName(expr.object)}.${expr.property.name}`;
|
|
76
|
-
}
|
|
77
|
-
return expr.property.name;
|
|
78
|
-
}
|
|
79
|
-
// ── Helper: find component name from function ──
|
|
80
|
-
function findComponentName(path) {
|
|
81
|
-
// Check if we're inside a function declaration/expression
|
|
82
|
-
let current = path;
|
|
83
|
-
while (current) {
|
|
84
|
-
const node = current.node;
|
|
85
|
-
// export default function Name() {}
|
|
86
|
-
if (t.isFunctionDeclaration(node) && node.id) {
|
|
87
|
-
return node.id.name;
|
|
88
|
-
}
|
|
89
|
-
// const Name = () => {} or const Name = function() {}
|
|
90
|
-
if (t.isVariableDeclarator(node) && t.isIdentifier(node.id)) {
|
|
91
|
-
return node.id.name;
|
|
92
|
-
}
|
|
93
|
-
// export default function() {} — use 'default'
|
|
94
|
-
if (t.isExportDefaultDeclaration(node)) {
|
|
95
|
-
const decl = node.declaration;
|
|
96
|
-
if (t.isFunctionDeclaration(decl) && decl.id) {
|
|
97
|
-
return decl.id.name;
|
|
98
|
-
}
|
|
99
|
-
if (t.isFunctionDeclaration(decl) && !decl.id) {
|
|
100
|
-
return "default";
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
current = current.parentPath;
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
function createOrdinalTracker() {
|
|
108
|
-
const counters = new Map();
|
|
109
|
-
return {
|
|
110
|
-
getOrdinal(parentKey, tag) {
|
|
111
|
-
const pk = parentKey ?? "__root__";
|
|
112
|
-
if (!counters.has(pk)) {
|
|
113
|
-
counters.set(pk, new Map());
|
|
114
|
-
}
|
|
115
|
-
const tagMap = counters.get(pk);
|
|
116
|
-
const current = tagMap.get(tag) ?? 0;
|
|
117
|
-
tagMap.set(tag, current + 1);
|
|
118
|
-
return current;
|
|
119
|
-
},
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* 从已解析的 AST 构建 ImportMap。
|
|
124
|
-
* 只处理 ES import 声明,不处理 require()。
|
|
125
|
-
*/
|
|
126
|
-
function buildImportMap(ast) {
|
|
127
|
-
const map = new Map();
|
|
128
|
-
for (const node of ast.program.body) {
|
|
129
|
-
if (!t.isImportDeclaration(node))
|
|
130
|
-
continue;
|
|
131
|
-
const specifier = node.source.value;
|
|
132
|
-
for (const spec of node.specifiers) {
|
|
133
|
-
if (t.isImportDefaultSpecifier(spec)) {
|
|
134
|
-
map.set(spec.local.name, { specifier, exportName: "default" });
|
|
135
|
-
}
|
|
136
|
-
else if (t.isImportNamespaceSpecifier(spec)) {
|
|
137
|
-
map.set(spec.local.name, { specifier, exportName: "*" });
|
|
138
|
-
}
|
|
139
|
-
else if (t.isImportSpecifier(spec)) {
|
|
140
|
-
const imported = t.isIdentifier(spec.imported)
|
|
141
|
-
? spec.imported.name
|
|
142
|
-
: spec.imported.value;
|
|
143
|
-
map.set(spec.local.name, { specifier, exportName: imported });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return map;
|
|
148
|
-
}
|
|
149
|
-
function collectBoundIdentifiers(pattern, names = []) {
|
|
150
|
-
if (t.isIdentifier(pattern)) {
|
|
151
|
-
names.push(pattern.name);
|
|
152
|
-
return names;
|
|
153
|
-
}
|
|
154
|
-
if (t.isObjectPattern(pattern)) {
|
|
155
|
-
for (const property of pattern.properties) {
|
|
156
|
-
if (t.isObjectProperty(property)) {
|
|
157
|
-
collectBoundIdentifiers(property.value, names);
|
|
158
|
-
}
|
|
159
|
-
else if (t.isRestElement(property)) {
|
|
160
|
-
collectBoundIdentifiers(property.argument, names);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return names;
|
|
164
|
-
}
|
|
165
|
-
if (t.isArrayPattern(pattern)) {
|
|
166
|
-
for (const element of pattern.elements) {
|
|
167
|
-
if (!element)
|
|
168
|
-
continue;
|
|
169
|
-
if (t.isRestElement(element)) {
|
|
170
|
-
collectBoundIdentifiers(element.argument, names);
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
collectBoundIdentifiers(element, names);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return names;
|
|
177
|
-
}
|
|
178
|
-
if (t.isAssignmentPattern(pattern)) {
|
|
179
|
-
collectBoundIdentifiers(pattern.left, names);
|
|
180
|
-
return names;
|
|
181
|
-
}
|
|
182
|
-
if (t.isRestElement(pattern)) {
|
|
183
|
-
collectBoundIdentifiers(pattern.argument, names);
|
|
184
|
-
return names;
|
|
185
|
-
}
|
|
186
|
-
return names;
|
|
187
|
-
}
|
|
188
|
-
function getExpressionText(node, source) {
|
|
189
|
-
if (node.start == null || node.end == null) {
|
|
190
|
-
return "";
|
|
191
|
-
}
|
|
192
|
-
return source.slice(node.start, node.end);
|
|
193
|
-
}
|
|
194
|
-
function resolveStaticBinding(name, scopes) {
|
|
195
|
-
for (let index = scopes.length - 1; index >= 0; index -= 1) {
|
|
196
|
-
const slot = scopes[index].get(name);
|
|
197
|
-
if (slot) {
|
|
198
|
-
if (!slot.resolved) {
|
|
199
|
-
return { kind: "unresolved" };
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
kind: "resolved",
|
|
203
|
-
value: slot.value,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return { kind: "unresolved" };
|
|
208
|
-
}
|
|
209
|
-
function setResolvedBinding(scope, name, value) {
|
|
210
|
-
scope.set(name, { resolved: true, value });
|
|
211
|
-
}
|
|
212
|
-
function setUnresolvedBinding(scope, name) {
|
|
213
|
-
scope.set(name, { resolved: false });
|
|
214
|
-
}
|
|
215
|
-
function readStaticValueAtPath(value, path) {
|
|
216
|
-
let current = value;
|
|
217
|
-
for (const segment of path) {
|
|
218
|
-
if (segment.kind === "property") {
|
|
219
|
-
if (current == null ||
|
|
220
|
-
Array.isArray(current) ||
|
|
221
|
-
typeof current !== "object" ||
|
|
222
|
-
!(segment.name in current)) {
|
|
223
|
-
return { kind: "known-empty" };
|
|
224
|
-
}
|
|
225
|
-
current = current[segment.name];
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (!Array.isArray(current) ||
|
|
229
|
-
segment.index < 0 ||
|
|
230
|
-
segment.index >= current.length) {
|
|
231
|
-
return { kind: "known-empty" };
|
|
232
|
-
}
|
|
233
|
-
current = current[segment.index];
|
|
234
|
-
}
|
|
235
|
-
return { kind: "resolved", value: current };
|
|
236
|
-
}
|
|
237
|
-
function getExpressionSourceInfo(expr, source, ctx) {
|
|
238
|
-
if (t.isIdentifier(expr)) {
|
|
239
|
-
return {
|
|
240
|
-
kind: "identifier",
|
|
241
|
-
expression: expr.name,
|
|
242
|
-
base: expr.name,
|
|
243
|
-
path: [],
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
if (!t.isMemberExpression(expr)) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
const path = [];
|
|
250
|
-
let current = expr;
|
|
251
|
-
while (t.isMemberExpression(current)) {
|
|
252
|
-
if (current.computed) {
|
|
253
|
-
if (t.isNumericLiteral(current.property)) {
|
|
254
|
-
path.unshift({ kind: "index", index: current.property.value });
|
|
255
|
-
}
|
|
256
|
-
else if (t.isStringLiteral(current.property)) {
|
|
257
|
-
path.unshift({ kind: "property", name: current.property.value });
|
|
258
|
-
}
|
|
259
|
-
else if (ctx && t.isExpression(current.property)) {
|
|
260
|
-
const computedValue = evaluateStaticValue(current.property, ctx);
|
|
261
|
-
if (typeof computedValue === "string") {
|
|
262
|
-
path.unshift({ kind: "property", name: computedValue });
|
|
263
|
-
}
|
|
264
|
-
else if (Number.isInteger(computedValue)) {
|
|
265
|
-
path.unshift({ kind: "index", index: computedValue });
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
return null;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
if (!t.isIdentifier(current.property)) {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
path.unshift({ kind: "property", name: current.property.name });
|
|
280
|
-
}
|
|
281
|
-
current = current.object;
|
|
282
|
-
}
|
|
283
|
-
if (!t.isIdentifier(current)) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
kind: path.some((segment) => segment.kind === "index")
|
|
288
|
-
? "index-expression"
|
|
289
|
-
: "member-expression",
|
|
290
|
-
expression: getExpressionText(expr, source),
|
|
291
|
-
base: current.name,
|
|
292
|
-
path,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
function evaluateStaticValue(expr, ctx) {
|
|
296
|
-
if (t.isStringLiteral(expr) ||
|
|
297
|
-
t.isNumericLiteral(expr) ||
|
|
298
|
-
t.isBooleanLiteral(expr)) {
|
|
299
|
-
return expr.value;
|
|
300
|
-
}
|
|
301
|
-
if (t.isNullLiteral(expr)) {
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
if (t.isTemplateLiteral(expr)) {
|
|
305
|
-
if (expr.expressions.length === 0 && expr.quasis.length === 1) {
|
|
306
|
-
return expr.quasis[0].value.cooked ?? expr.quasis[0].value.raw;
|
|
307
|
-
}
|
|
308
|
-
return undefined;
|
|
309
|
-
}
|
|
310
|
-
if (t.isIdentifier(expr)) {
|
|
311
|
-
const result = resolveStaticBinding(expr.name, ctx.valueScopes);
|
|
312
|
-
return result.kind === "resolved" ? result.value : undefined;
|
|
313
|
-
}
|
|
314
|
-
if (t.isArrayExpression(expr)) {
|
|
315
|
-
const values = [];
|
|
316
|
-
for (const element of expr.elements) {
|
|
317
|
-
if (!element || t.isSpreadElement(element)) {
|
|
318
|
-
return undefined;
|
|
319
|
-
}
|
|
320
|
-
const elementValue = evaluateStaticValue(element, ctx);
|
|
321
|
-
if (elementValue === undefined) {
|
|
322
|
-
return undefined;
|
|
323
|
-
}
|
|
324
|
-
values.push(elementValue);
|
|
325
|
-
}
|
|
326
|
-
return values;
|
|
327
|
-
}
|
|
328
|
-
if (t.isObjectExpression(expr)) {
|
|
329
|
-
const value = {};
|
|
330
|
-
for (const property of expr.properties) {
|
|
331
|
-
if (!t.isObjectProperty(property) ||
|
|
332
|
-
property.computed ||
|
|
333
|
-
!t.isExpression(property.value)) {
|
|
334
|
-
return undefined;
|
|
335
|
-
}
|
|
336
|
-
let keyName = null;
|
|
337
|
-
if (t.isIdentifier(property.key)) {
|
|
338
|
-
keyName = property.key.name;
|
|
339
|
-
}
|
|
340
|
-
else if (t.isStringLiteral(property.key)) {
|
|
341
|
-
keyName = property.key.value;
|
|
342
|
-
}
|
|
343
|
-
if (!keyName) {
|
|
344
|
-
return undefined;
|
|
345
|
-
}
|
|
346
|
-
let propertyValue;
|
|
347
|
-
if (property.shorthand) {
|
|
348
|
-
if (!t.isIdentifier(property.value)) {
|
|
349
|
-
return undefined;
|
|
350
|
-
}
|
|
351
|
-
const shorthandResult = resolveStaticBinding(property.value.name, ctx.valueScopes);
|
|
352
|
-
if (shorthandResult.kind !== "resolved") {
|
|
353
|
-
return undefined;
|
|
354
|
-
}
|
|
355
|
-
propertyValue = shorthandResult.value;
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
propertyValue = evaluateStaticValue(property.value, ctx);
|
|
359
|
-
}
|
|
360
|
-
if (propertyValue === undefined) {
|
|
361
|
-
return undefined;
|
|
362
|
-
}
|
|
363
|
-
value[keyName] = propertyValue;
|
|
364
|
-
}
|
|
365
|
-
return value;
|
|
366
|
-
}
|
|
367
|
-
if (t.isMemberExpression(expr)) {
|
|
368
|
-
const sourceInfo = getExpressionSourceInfo(expr, "", ctx);
|
|
369
|
-
if (!sourceInfo) {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
const bindingResult = resolveStaticBinding(sourceInfo.base, ctx.valueScopes);
|
|
373
|
-
if (bindingResult.kind !== "resolved") {
|
|
374
|
-
return undefined;
|
|
375
|
-
}
|
|
376
|
-
const pathResult = readStaticValueAtPath(bindingResult.value, sourceInfo.path);
|
|
377
|
-
return pathResult.kind === "resolved" ? pathResult.value : undefined;
|
|
378
|
-
}
|
|
379
|
-
if (t.isCallExpression(expr)) {
|
|
380
|
-
const classNameValue = evaluateClassNameExpression(expr, ctx);
|
|
381
|
-
return classNameValue ?? undefined;
|
|
382
|
-
}
|
|
383
|
-
return undefined;
|
|
384
|
-
}
|
|
385
|
-
function evaluateTextExpression(expr, ctx, source) {
|
|
386
|
-
const sourceInfo = getExpressionSourceInfo(expr, source, ctx);
|
|
387
|
-
if (!sourceInfo) {
|
|
388
|
-
if (t.isMemberExpression(expr) ||
|
|
389
|
-
t.isBinaryExpression(expr) ||
|
|
390
|
-
t.isLogicalExpression(expr) ||
|
|
391
|
-
t.isConditionalExpression(expr) ||
|
|
392
|
-
t.isCallExpression(expr)) {
|
|
393
|
-
return {
|
|
394
|
-
value: getExpressionText(expr, source),
|
|
395
|
-
source: null,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
const bindingResult = resolveStaticBinding(sourceInfo.base, ctx.valueScopes);
|
|
401
|
-
const resolvedResult = sourceInfo.kind === "identifier"
|
|
402
|
-
? bindingResult
|
|
403
|
-
: bindingResult.kind === "resolved"
|
|
404
|
-
? readStaticValueAtPath(bindingResult.value, sourceInfo.path)
|
|
405
|
-
: bindingResult;
|
|
406
|
-
if (resolvedResult.kind === "resolved" &&
|
|
407
|
-
(typeof resolvedResult.value === "string" ||
|
|
408
|
-
typeof resolvedResult.value === "number")) {
|
|
409
|
-
return {
|
|
410
|
-
value: String(resolvedResult.value),
|
|
411
|
-
source: sourceInfo,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
if (resolvedResult.kind === "resolved" &&
|
|
415
|
-
(typeof resolvedResult.value === "boolean" || resolvedResult.value === null)) {
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
if (resolvedResult.kind === "known-empty") {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
return {
|
|
422
|
-
value: sourceInfo.expression,
|
|
423
|
-
source: sourceInfo,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
function createEvaluationContext(file, componentName, elements, regions, importMap, valueScopes) {
|
|
427
|
-
return {
|
|
428
|
-
file,
|
|
429
|
-
componentName,
|
|
430
|
-
elements,
|
|
431
|
-
regions,
|
|
432
|
-
ordinalTracker: createOrdinalTracker(),
|
|
433
|
-
parentStack: [],
|
|
434
|
-
valueScopes,
|
|
435
|
-
importMap,
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
function collectStaticBindingsFromDeclarations(declarations, ctx, targetScope) {
|
|
439
|
-
for (const declaration of declarations) {
|
|
440
|
-
for (const declarator of declaration.declarations) {
|
|
441
|
-
for (const name of collectBoundIdentifiers(declarator.id)) {
|
|
442
|
-
setUnresolvedBinding(targetScope, name);
|
|
443
|
-
}
|
|
444
|
-
if (!t.isIdentifier(declarator.id) ||
|
|
445
|
-
!declarator.init ||
|
|
446
|
-
!t.isExpression(declarator.init)) {
|
|
447
|
-
continue;
|
|
448
|
-
}
|
|
449
|
-
const value = evaluateStaticValue(declarator.init, ctx);
|
|
450
|
-
if (value !== undefined) {
|
|
451
|
-
setResolvedBinding(targetScope, declarator.id.name, value);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
function getFileScopeVariableDeclarations(programBody) {
|
|
457
|
-
const declarations = [];
|
|
458
|
-
for (const statement of programBody) {
|
|
459
|
-
if (t.isVariableDeclaration(statement)) {
|
|
460
|
-
declarations.push(statement);
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
if (t.isExportNamedDeclaration(statement) &&
|
|
464
|
-
statement.declaration &&
|
|
465
|
-
t.isVariableDeclaration(statement.declaration)) {
|
|
466
|
-
declarations.push(statement.declaration);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
return declarations;
|
|
470
|
-
}
|
|
471
|
-
function extractTextSegments(children, ctx, source) {
|
|
472
|
-
const segments = [];
|
|
473
|
-
let index = 0;
|
|
474
|
-
for (const child of children) {
|
|
475
|
-
if (t.isJSXText(child)) {
|
|
476
|
-
const text = child.value.trim();
|
|
477
|
-
if (text) {
|
|
478
|
-
segments.push({
|
|
479
|
-
index: index++,
|
|
480
|
-
value: text,
|
|
481
|
-
sourceRange: toSourceRange(child),
|
|
482
|
-
editable: true,
|
|
483
|
-
source: null,
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
else if (t.isJSXExpressionContainer(child) &&
|
|
488
|
-
t.isStringLiteral(child.expression)) {
|
|
489
|
-
segments.push({
|
|
490
|
-
index: index++,
|
|
491
|
-
value: child.expression.value,
|
|
492
|
-
sourceRange: toSourceRange(child),
|
|
493
|
-
editable: true,
|
|
494
|
-
source: null,
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
else if (t.isJSXExpressionContainer(child) &&
|
|
498
|
-
t.isTemplateLiteral(child.expression)) {
|
|
499
|
-
// Template literal with no expressions = editable string
|
|
500
|
-
const expr = child.expression;
|
|
501
|
-
if (expr.expressions.length === 0 && expr.quasis.length === 1) {
|
|
502
|
-
segments.push({
|
|
503
|
-
index: index++,
|
|
504
|
-
value: expr.quasis[0].value.cooked ?? expr.quasis[0].value.raw,
|
|
505
|
-
sourceRange: toSourceRange(child),
|
|
506
|
-
editable: true,
|
|
507
|
-
source: null,
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
else {
|
|
511
|
-
segments.push({
|
|
512
|
-
index: index++,
|
|
513
|
-
value: getExpressionText(expr, source),
|
|
514
|
-
sourceRange: toSourceRange(child),
|
|
515
|
-
editable: true,
|
|
516
|
-
source: null,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
else if (t.isJSXExpressionContainer(child) &&
|
|
521
|
-
t.isExpression(child.expression)) {
|
|
522
|
-
const result = evaluateTextExpression(child.expression, ctx, source);
|
|
523
|
-
if (result) {
|
|
524
|
-
segments.push({
|
|
525
|
-
index: index++,
|
|
526
|
-
value: result.value,
|
|
527
|
-
sourceRange: toSourceRange(child),
|
|
528
|
-
editable: true,
|
|
529
|
-
source: result.source,
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return segments;
|
|
535
|
-
}
|
|
536
|
-
function extractAttributeSources(opening, ctx, source) {
|
|
537
|
-
const attributes = [];
|
|
538
|
-
for (const attribute of opening.attributes) {
|
|
539
|
-
if (!t.isJSXAttribute(attribute) || !t.isJSXIdentifier(attribute.name)) {
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
if (t.isStringLiteral(attribute.value)) {
|
|
543
|
-
attributes.push({
|
|
544
|
-
name: attribute.name.name,
|
|
545
|
-
value: attribute.value.value,
|
|
546
|
-
sourceRange: toSourceRange(attribute),
|
|
547
|
-
source: null,
|
|
548
|
-
});
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
if (t.isJSXExpressionContainer(attribute.value) &&
|
|
552
|
-
t.isExpression(attribute.value.expression)) {
|
|
553
|
-
const staticValue = evaluateStaticValue(attribute.value.expression, ctx);
|
|
554
|
-
if (attribute.name.name === "className") {
|
|
555
|
-
const classNameValue = typeof staticValue === "string"
|
|
556
|
-
? staticValue
|
|
557
|
-
: staticValue === undefined
|
|
558
|
-
? evaluateClassNameExpression(attribute.value.expression, ctx)
|
|
559
|
-
: null;
|
|
560
|
-
if (classNameValue) {
|
|
561
|
-
attributes.push({
|
|
562
|
-
name: attribute.name.name,
|
|
563
|
-
value: classNameValue,
|
|
564
|
-
sourceRange: toSourceRange(attribute),
|
|
565
|
-
source: null,
|
|
566
|
-
resolvedValue: classNameValue,
|
|
567
|
-
});
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
const result = evaluateTextExpression(attribute.value.expression, ctx, source);
|
|
572
|
-
if (staticValue !== undefined &&
|
|
573
|
-
typeof staticValue !== "string" &&
|
|
574
|
-
typeof staticValue !== "number") {
|
|
575
|
-
attributes.push({
|
|
576
|
-
name: attribute.name.name,
|
|
577
|
-
value: formatStaticAttributeValue(staticValue),
|
|
578
|
-
sourceRange: toSourceRange(attribute),
|
|
579
|
-
source: getExpressionSourceInfo(attribute.value.expression, source, ctx),
|
|
580
|
-
resolvedValue: staticValue,
|
|
581
|
-
});
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
if (!result) {
|
|
585
|
-
if (staticValue !== undefined) {
|
|
586
|
-
attributes.push({
|
|
587
|
-
name: attribute.name.name,
|
|
588
|
-
value: formatStaticAttributeValue(staticValue),
|
|
589
|
-
sourceRange: toSourceRange(attribute),
|
|
590
|
-
source: getExpressionSourceInfo(attribute.value.expression, source, ctx),
|
|
591
|
-
resolvedValue: staticValue,
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
attributes.push({
|
|
597
|
-
name: attribute.name.name,
|
|
598
|
-
value: result.value,
|
|
599
|
-
sourceRange: toSourceRange(attribute),
|
|
600
|
-
source: result.source,
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
return attributes;
|
|
605
|
-
}
|
|
606
|
-
function evaluateClassNameExpression(expr, ctx) {
|
|
607
|
-
const segments = collectClassNameSegments(expr, ctx);
|
|
608
|
-
if (!segments) {
|
|
609
|
-
return null;
|
|
610
|
-
}
|
|
611
|
-
const value = segments
|
|
612
|
-
.map((segment) => segment.trim())
|
|
613
|
-
.filter(Boolean)
|
|
614
|
-
.join(" ");
|
|
615
|
-
return value || null;
|
|
616
|
-
}
|
|
617
|
-
function collectClassNameSegments(expr, ctx) {
|
|
618
|
-
if (t.isStringLiteral(expr)) {
|
|
619
|
-
return [expr.value];
|
|
620
|
-
}
|
|
621
|
-
if (t.isTemplateLiteral(expr)) {
|
|
622
|
-
if (expr.expressions.length > 0) {
|
|
623
|
-
return null;
|
|
624
|
-
}
|
|
625
|
-
return [expr.quasis.map((quasi) => quasi.value.cooked ?? "").join("")];
|
|
626
|
-
}
|
|
627
|
-
if (t.isNullLiteral(expr)) {
|
|
628
|
-
return [];
|
|
629
|
-
}
|
|
630
|
-
if (t.isBooleanLiteral(expr)) {
|
|
631
|
-
return expr.value ? null : [];
|
|
632
|
-
}
|
|
633
|
-
if (t.isIdentifier(expr) || t.isMemberExpression(expr)) {
|
|
634
|
-
if (t.isIdentifier(expr) && expr.name === "undefined") {
|
|
635
|
-
return [];
|
|
636
|
-
}
|
|
637
|
-
const staticValue = evaluateStaticValue(expr, ctx);
|
|
638
|
-
return typeof staticValue === "string" ? [staticValue] : null;
|
|
639
|
-
}
|
|
640
|
-
if (t.isConditionalExpression(expr)) {
|
|
641
|
-
const consequent = collectClassNameSegments(expr.consequent, ctx);
|
|
642
|
-
const alternate = collectClassNameSegments(expr.alternate, ctx);
|
|
643
|
-
if (!consequent || !alternate) {
|
|
644
|
-
return null;
|
|
645
|
-
}
|
|
646
|
-
return [...consequent, ...alternate];
|
|
647
|
-
}
|
|
648
|
-
if (t.isLogicalExpression(expr)) {
|
|
649
|
-
if (expr.operator !== "&&") {
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
const leftValue = evaluateStaticValue(expr.left, ctx);
|
|
653
|
-
if (leftValue === false || leftValue === null) {
|
|
654
|
-
return [];
|
|
655
|
-
}
|
|
656
|
-
return collectClassNameSegments(expr.right, ctx);
|
|
657
|
-
}
|
|
658
|
-
if (t.isArrayExpression(expr)) {
|
|
659
|
-
const segments = [];
|
|
660
|
-
for (const element of expr.elements) {
|
|
661
|
-
if (!element || t.isSpreadElement(element)) {
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
const next = collectClassNameSegments(element, ctx);
|
|
665
|
-
if (!next) {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
segments.push(...next);
|
|
669
|
-
}
|
|
670
|
-
return segments;
|
|
671
|
-
}
|
|
672
|
-
if (t.isObjectExpression(expr)) {
|
|
673
|
-
const segments = [];
|
|
674
|
-
for (const property of expr.properties) {
|
|
675
|
-
if (!t.isObjectProperty(property)) {
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
const key = getStaticClassObjectKey(property.key);
|
|
679
|
-
if (!key) {
|
|
680
|
-
return null;
|
|
681
|
-
}
|
|
682
|
-
segments.push(key);
|
|
683
|
-
}
|
|
684
|
-
return segments;
|
|
685
|
-
}
|
|
686
|
-
if (t.isCallExpression(expr)) {
|
|
687
|
-
const callee = getClassCallName(expr.callee);
|
|
688
|
-
if (!callee || !["cn", "clsx", "twMerge"].includes(callee)) {
|
|
689
|
-
return null;
|
|
690
|
-
}
|
|
691
|
-
const segments = [];
|
|
692
|
-
for (const argument of expr.arguments) {
|
|
693
|
-
if (!t.isExpression(argument)) {
|
|
694
|
-
return null;
|
|
695
|
-
}
|
|
696
|
-
const next = collectClassNameSegments(argument, ctx);
|
|
697
|
-
if (!next) {
|
|
698
|
-
return null;
|
|
699
|
-
}
|
|
700
|
-
segments.push(...next);
|
|
701
|
-
}
|
|
702
|
-
return segments;
|
|
703
|
-
}
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
function getClassCallName(callee) {
|
|
707
|
-
if (t.isIdentifier(callee)) {
|
|
708
|
-
return callee.name;
|
|
709
|
-
}
|
|
710
|
-
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
711
|
-
return callee.property.name;
|
|
712
|
-
}
|
|
713
|
-
return null;
|
|
714
|
-
}
|
|
715
|
-
function getStaticClassObjectKey(key) {
|
|
716
|
-
if (t.isIdentifier(key)) {
|
|
717
|
-
return key.name;
|
|
718
|
-
}
|
|
719
|
-
if (t.isStringLiteral(key)) {
|
|
720
|
-
return key.value;
|
|
721
|
-
}
|
|
722
|
-
return null;
|
|
723
|
-
}
|
|
724
|
-
function getAttributeExpression(opening, name) {
|
|
725
|
-
const attribute = opening.attributes.find((item) => t.isJSXAttribute(item) &&
|
|
726
|
-
t.isJSXIdentifier(item.name) &&
|
|
727
|
-
item.name.name === name);
|
|
728
|
-
if (!attribute || !t.isJSXAttribute(attribute)) {
|
|
729
|
-
return null;
|
|
730
|
-
}
|
|
731
|
-
if (!t.isJSXExpressionContainer(attribute.value)) {
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
return attribute.value.expression;
|
|
735
|
-
}
|
|
736
|
-
function getFirstAttributeExpression(opening, names) {
|
|
737
|
-
for (const name of names) {
|
|
738
|
-
const expression = getAttributeExpression(opening, name);
|
|
739
|
-
if (expression) {
|
|
740
|
-
return expression;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
function getAttributeName(attribute) {
|
|
746
|
-
return t.isJSXIdentifier(attribute.name) ? attribute.name.name : null;
|
|
747
|
-
}
|
|
748
|
-
function formatStaticAttributeValue(value) {
|
|
749
|
-
if (value === null) {
|
|
750
|
-
return "null";
|
|
751
|
-
}
|
|
752
|
-
if (typeof value === "boolean") {
|
|
753
|
-
return value ? "true" : "false";
|
|
754
|
-
}
|
|
755
|
-
return JSON.stringify(value);
|
|
756
|
-
}
|
|
757
|
-
// ── Check for .map() call expression (repeat region detection) ──
|
|
758
|
-
function isMapCallExpression(node) {
|
|
759
|
-
return (t.isCallExpression(node) &&
|
|
760
|
-
t.isMemberExpression(node.callee) &&
|
|
761
|
-
t.isIdentifier(node.callee.property) &&
|
|
762
|
-
node.callee.property.name === "map");
|
|
763
|
-
}
|
|
764
|
-
// ── Check for ternary / && conditional expression ──
|
|
765
|
-
function isConditionalExpression(node) {
|
|
766
|
-
return t.isConditionalExpression(node) || t.isLogicalExpression(node);
|
|
767
|
-
}
|
|
768
|
-
function isFunctionLikeExpression(node) {
|
|
769
|
-
return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node);
|
|
770
|
-
}
|
|
771
|
-
function isListRenderPropName(name) {
|
|
772
|
-
return (name === "render" ||
|
|
773
|
-
name === "itemRender" ||
|
|
774
|
-
name === "rowRender" ||
|
|
775
|
-
name === "children");
|
|
776
|
-
}
|
|
777
|
-
function isStateRenderPropName(name) {
|
|
778
|
-
return (name === "empty" ||
|
|
779
|
-
name === "emptyRender" ||
|
|
780
|
-
name === "loading" ||
|
|
781
|
-
name === "loadingRender" ||
|
|
782
|
-
name === "error" ||
|
|
783
|
-
name === "errorRender" ||
|
|
784
|
-
name === "fallback" ||
|
|
785
|
-
name === "headerRender" ||
|
|
786
|
-
name === "footerRender");
|
|
787
|
-
}
|
|
788
|
-
function createRepeatCandidate(collectionExpression, callback, source, ctx) {
|
|
789
|
-
if (!collectionExpression || !t.isExpression(collectionExpression)) {
|
|
790
|
-
return null;
|
|
791
|
-
}
|
|
792
|
-
const candidate = {
|
|
793
|
-
kind: "repeat-template",
|
|
794
|
-
collectionExpression: getExpressionText(collectionExpression, source),
|
|
795
|
-
collectionSource: getExpressionSourceInfo(collectionExpression, source, ctx),
|
|
796
|
-
};
|
|
797
|
-
if (callback) {
|
|
798
|
-
const itemParam = callback.params[0];
|
|
799
|
-
const indexParam = callback.params[1];
|
|
800
|
-
if (t.isIdentifier(itemParam)) {
|
|
801
|
-
candidate.itemParamName = itemParam.name;
|
|
802
|
-
}
|
|
803
|
-
if (t.isIdentifier(indexParam)) {
|
|
804
|
-
candidate.indexParamName = indexParam.name;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return candidate;
|
|
808
|
-
}
|
|
809
|
-
function processRenderableNode(node, ctx, source) {
|
|
810
|
-
if (!node) {
|
|
811
|
-
return [];
|
|
812
|
-
}
|
|
813
|
-
if (t.isJSXElement(node)) {
|
|
814
|
-
const key = processJSXElement(node, ctx, source);
|
|
815
|
-
return key ? [key] : [];
|
|
816
|
-
}
|
|
817
|
-
if (isJSXFragment(node)) {
|
|
818
|
-
return processJSXFragment(node, ctx, source);
|
|
819
|
-
}
|
|
820
|
-
if (t.isParenthesizedExpression(node)) {
|
|
821
|
-
return processRenderableNode(node.expression, ctx, source);
|
|
822
|
-
}
|
|
823
|
-
if (t.isExpression(node)) {
|
|
824
|
-
const regionCountBefore = ctx.regions.length;
|
|
825
|
-
processExpression(node, ctx, source);
|
|
826
|
-
return ctx.regions
|
|
827
|
-
.slice(regionCountBefore)
|
|
828
|
-
.flatMap((region) => region.childKeys);
|
|
829
|
-
}
|
|
830
|
-
return [];
|
|
831
|
-
}
|
|
832
|
-
function processFunctionReturn(callback, ctx, source) {
|
|
833
|
-
const callbackScope = new Map();
|
|
834
|
-
for (const param of callback.params) {
|
|
835
|
-
for (const name of collectBoundIdentifiers(param)) {
|
|
836
|
-
setUnresolvedBinding(callbackScope, name);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
ctx.valueScopes.push(callbackScope);
|
|
840
|
-
try {
|
|
841
|
-
if (!t.isBlockStatement(callback.body)) {
|
|
842
|
-
return processRenderableNode(callback.body, ctx, source);
|
|
843
|
-
}
|
|
844
|
-
const callbackLocalScope = new Map();
|
|
845
|
-
ctx.valueScopes.push(callbackLocalScope);
|
|
846
|
-
try {
|
|
847
|
-
for (const statement of callback.body.body) {
|
|
848
|
-
if (t.isVariableDeclaration(statement)) {
|
|
849
|
-
collectStaticBindingsFromDeclarations([statement], ctx, callbackLocalScope);
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
if (t.isReturnStatement(statement) && statement.argument) {
|
|
853
|
-
return processRenderableNode(statement.argument, ctx, source);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
finally {
|
|
858
|
-
ctx.valueScopes.pop();
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
finally {
|
|
862
|
-
ctx.valueScopes.pop();
|
|
863
|
-
}
|
|
864
|
-
return [];
|
|
865
|
-
}
|
|
866
|
-
function pushRegion(ctx, kind, ownerKey, sourceRangeNode, childKeys, sourceCandidates) {
|
|
867
|
-
if (childKeys.length === 0) {
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
ctx.regions.push({
|
|
871
|
-
kind,
|
|
872
|
-
ownerKey,
|
|
873
|
-
childKeys,
|
|
874
|
-
sourceRange: toSourceRange(sourceRangeNode),
|
|
875
|
-
sourceCandidates: sourceCandidates && sourceCandidates.length > 0
|
|
876
|
-
? sourceCandidates
|
|
877
|
-
: undefined,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
function processJSXAttributeRenderProtocols(node, elementKey, ctx, source) {
|
|
881
|
-
const collectionExpression = getFirstAttributeExpression(node.openingElement, ["items", "dataSource", "data"]);
|
|
882
|
-
const parentEntry = { key: elementKey, tag: getTagName(node.openingElement) };
|
|
883
|
-
ctx.parentStack.push(parentEntry);
|
|
884
|
-
try {
|
|
885
|
-
for (const attribute of node.openingElement.attributes) {
|
|
886
|
-
if (!t.isJSXAttribute(attribute)) {
|
|
887
|
-
continue;
|
|
888
|
-
}
|
|
889
|
-
const name = getAttributeName(attribute);
|
|
890
|
-
if (!name || !t.isJSXExpressionContainer(attribute.value)) {
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
const expression = attribute.value.expression;
|
|
894
|
-
if (t.isJSXEmptyExpression(expression)) {
|
|
895
|
-
continue;
|
|
896
|
-
}
|
|
897
|
-
if (isListRenderPropName(name) && isFunctionLikeExpression(expression)) {
|
|
898
|
-
const childKeys = processFunctionReturn(expression, ctx, source);
|
|
899
|
-
const repeatCandidate = createRepeatCandidate(collectionExpression, expression, source, ctx);
|
|
900
|
-
pushRegion(ctx, "repeat-region", elementKey, attribute, childKeys, repeatCandidate ? [repeatCandidate] : undefined);
|
|
901
|
-
continue;
|
|
902
|
-
}
|
|
903
|
-
if (isStateRenderPropName(name)) {
|
|
904
|
-
const childKeys = isFunctionLikeExpression(expression)
|
|
905
|
-
? processFunctionReturn(expression, ctx, source)
|
|
906
|
-
: processRenderableNode(expression, ctx, source);
|
|
907
|
-
pushRegion(ctx, "conditional-region", elementKey, attribute, childKeys);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
if (collectionExpression) {
|
|
911
|
-
for (const child of node.children) {
|
|
912
|
-
if (!t.isJSXExpressionContainer(child) ||
|
|
913
|
-
!isFunctionLikeExpression(child.expression)) {
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
const childKeys = processFunctionReturn(child.expression, ctx, source);
|
|
917
|
-
const repeatCandidate = createRepeatCandidate(collectionExpression, child.expression, source, ctx);
|
|
918
|
-
pushRegion(ctx, "repeat-region", elementKey, child, childKeys, repeatCandidate ? [repeatCandidate] : undefined);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
finally {
|
|
923
|
-
ctx.parentStack.pop();
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
// ── Process JSX element ──
|
|
927
|
-
function processJSXElement(node, ctx, source) {
|
|
928
|
-
const tag = getTagName(node.openingElement);
|
|
929
|
-
// Fragment → transparent, process children directly
|
|
930
|
-
if (tag === "Fragment" || isFragment(node)) {
|
|
931
|
-
processChildren(node.children, ctx, source);
|
|
932
|
-
return null;
|
|
933
|
-
}
|
|
934
|
-
const parentInfo = ctx.parentStack[ctx.parentStack.length - 1] ?? null;
|
|
935
|
-
const parentKey = parentInfo?.key ?? null;
|
|
936
|
-
// Build structural path segment
|
|
937
|
-
const isComp = isComponentTag(tag);
|
|
938
|
-
let pathSegment;
|
|
939
|
-
if (isComp) {
|
|
940
|
-
const ordinal = ctx.ordinalTracker.getOrdinal(parentKey, tag);
|
|
941
|
-
pathSegment = `component:${tag}#${ordinal}`;
|
|
942
|
-
}
|
|
943
|
-
else {
|
|
944
|
-
const ordinal = ctx.ordinalTracker.getOrdinal(parentKey, tag);
|
|
945
|
-
pathSegment = `${tag}#${ordinal}`;
|
|
946
|
-
}
|
|
947
|
-
// Compute full structural path
|
|
948
|
-
const parentPath = parentInfo
|
|
949
|
-
? ctx.elements.get(parentInfo.key)?.identity.structuralPath
|
|
950
|
-
: undefined;
|
|
951
|
-
const structuralPath = parentPath
|
|
952
|
-
? `${parentPath}/${pathSegment}`
|
|
953
|
-
: pathSegment;
|
|
954
|
-
// Generate key
|
|
955
|
-
const key = computeSourceIdentityKey(ctx.file, ctx.componentName, structuralPath);
|
|
956
|
-
const identity = {
|
|
957
|
-
file: ctx.file,
|
|
958
|
-
component: ctx.componentName,
|
|
959
|
-
structuralPath,
|
|
960
|
-
key,
|
|
961
|
-
};
|
|
962
|
-
const boundaryKind = isComp
|
|
963
|
-
? "component-boundary"
|
|
964
|
-
: null;
|
|
965
|
-
const textSegments = extractTextSegments(node.children, ctx, source);
|
|
966
|
-
const attributes = extractAttributeSources(node.openingElement, ctx, source);
|
|
967
|
-
// For component elements, look up import source info
|
|
968
|
-
let importedFrom;
|
|
969
|
-
let exportName;
|
|
970
|
-
if (isComp) {
|
|
971
|
-
const importInfo = ctx.importMap.get(tag);
|
|
972
|
-
if (importInfo) {
|
|
973
|
-
importedFrom = importInfo.specifier;
|
|
974
|
-
exportName = importInfo.exportName;
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
const elementNode = {
|
|
978
|
-
identity,
|
|
979
|
-
tag,
|
|
980
|
-
componentName: ctx.componentName,
|
|
981
|
-
sourceFile: ctx.file,
|
|
982
|
-
sourceRange: toSourceRange(node),
|
|
983
|
-
parentKey,
|
|
984
|
-
childKeys: [],
|
|
985
|
-
textSegments,
|
|
986
|
-
attributes: attributes.length > 0 ? attributes : undefined,
|
|
987
|
-
boundaryKind,
|
|
988
|
-
isOpaque: isComp, // external components are opaque by default
|
|
989
|
-
importedFrom,
|
|
990
|
-
exportName,
|
|
991
|
-
};
|
|
992
|
-
ctx.elements.set(key, elementNode);
|
|
993
|
-
// Register as child of parent
|
|
994
|
-
if (parentKey && ctx.elements.has(parentKey)) {
|
|
995
|
-
ctx.elements.get(parentKey).childKeys.push(key);
|
|
996
|
-
}
|
|
997
|
-
processJSXAttributeRenderProtocols(node, key, ctx, source);
|
|
998
|
-
// Preserve explicit component children as call-site slot content.
|
|
999
|
-
if (!isComp || node.children.length > 0) {
|
|
1000
|
-
ctx.parentStack.push({ key, tag });
|
|
1001
|
-
processChildren(node.children, ctx, source);
|
|
1002
|
-
ctx.parentStack.pop();
|
|
1003
|
-
}
|
|
1004
|
-
return key;
|
|
1005
|
-
}
|
|
1006
|
-
// ── Process JSX Fragment ──
|
|
1007
|
-
function processJSXFragment(node, ctx, source) {
|
|
1008
|
-
const topLevelKeys = [];
|
|
1009
|
-
for (const child of node.children) {
|
|
1010
|
-
if (t.isJSXElement(child)) {
|
|
1011
|
-
const childKey = processJSXElement(child, ctx, source);
|
|
1012
|
-
if (childKey) {
|
|
1013
|
-
topLevelKeys.push(childKey);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
else if (isJSXFragment(child)) {
|
|
1017
|
-
topLevelKeys.push(...processJSXFragment(child, ctx, source));
|
|
1018
|
-
}
|
|
1019
|
-
else if (t.isJSXExpressionContainer(child)) {
|
|
1020
|
-
processExpression(child.expression, ctx, source);
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
return topLevelKeys;
|
|
1024
|
-
}
|
|
1025
|
-
// ── Process children array ──
|
|
1026
|
-
function processChildren(children, ctx, source) {
|
|
1027
|
-
for (const child of children) {
|
|
1028
|
-
if (t.isJSXElement(child)) {
|
|
1029
|
-
processJSXElement(child, ctx, source);
|
|
1030
|
-
}
|
|
1031
|
-
else if (isJSXFragment(child)) {
|
|
1032
|
-
processJSXFragment(child, ctx, source);
|
|
1033
|
-
}
|
|
1034
|
-
else if (t.isJSXExpressionContainer(child)) {
|
|
1035
|
-
processExpression(child.expression, ctx, source);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
// ── Process expression (detects repeat/conditional) ──
|
|
1040
|
-
function processExpression(expr, ctx, source) {
|
|
1041
|
-
if (t.isJSXEmptyExpression(expr))
|
|
1042
|
-
return;
|
|
1043
|
-
// .map() → repeat region
|
|
1044
|
-
if (isMapCallExpression(expr)) {
|
|
1045
|
-
const callExpr = expr;
|
|
1046
|
-
const parentInfo = ctx.parentStack[ctx.parentStack.length - 1] ?? null;
|
|
1047
|
-
const sourceCandidates = [];
|
|
1048
|
-
if (t.isMemberExpression(callExpr.callee) &&
|
|
1049
|
-
t.isExpression(callExpr.callee.object)) {
|
|
1050
|
-
const callback = callExpr.arguments[0];
|
|
1051
|
-
const repeatCandidate = {
|
|
1052
|
-
kind: "repeat-template",
|
|
1053
|
-
collectionExpression: getExpressionText(callExpr.callee.object, source),
|
|
1054
|
-
collectionSource: getExpressionSourceInfo(callExpr.callee.object, source, ctx),
|
|
1055
|
-
};
|
|
1056
|
-
if (t.isArrowFunctionExpression(callback) ||
|
|
1057
|
-
t.isFunctionExpression(callback)) {
|
|
1058
|
-
const itemParam = callback.params[0];
|
|
1059
|
-
const indexParam = callback.params[1];
|
|
1060
|
-
if (t.isIdentifier(itemParam)) {
|
|
1061
|
-
repeatCandidate.itemParamName = itemParam.name;
|
|
1062
|
-
}
|
|
1063
|
-
if (t.isIdentifier(indexParam)) {
|
|
1064
|
-
repeatCandidate.indexParamName = indexParam.name;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
sourceCandidates.push(repeatCandidate);
|
|
1068
|
-
}
|
|
1069
|
-
const region = {
|
|
1070
|
-
kind: "repeat-region",
|
|
1071
|
-
ownerKey: parentInfo?.key ?? "",
|
|
1072
|
-
childKeys: [],
|
|
1073
|
-
sourceRange: toSourceRange(callExpr),
|
|
1074
|
-
sourceCandidates: sourceCandidates.length > 0 ? sourceCandidates : undefined,
|
|
1075
|
-
};
|
|
1076
|
-
ctx.regions.push(region);
|
|
1077
|
-
// Process the callback body for JSX
|
|
1078
|
-
const callback = callExpr.arguments[0];
|
|
1079
|
-
if (t.isArrowFunctionExpression(callback) ||
|
|
1080
|
-
t.isFunctionExpression(callback)) {
|
|
1081
|
-
const callbackScope = new Map();
|
|
1082
|
-
for (const param of callback.params) {
|
|
1083
|
-
for (const name of collectBoundIdentifiers(param)) {
|
|
1084
|
-
setUnresolvedBinding(callbackScope, name);
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
ctx.valueScopes.push(callbackScope);
|
|
1088
|
-
const body = callback.body;
|
|
1089
|
-
if (t.isJSXElement(body)) {
|
|
1090
|
-
const childKey = processJSXElement(body, ctx, source);
|
|
1091
|
-
if (childKey)
|
|
1092
|
-
region.childKeys.push(childKey);
|
|
1093
|
-
}
|
|
1094
|
-
else if (isJSXFragment(body)) {
|
|
1095
|
-
region.childKeys.push(...processJSXFragment(body, ctx, source));
|
|
1096
|
-
}
|
|
1097
|
-
else if (t.isBlockStatement(body)) {
|
|
1098
|
-
const callbackLocalScope = new Map();
|
|
1099
|
-
ctx.valueScopes.push(callbackLocalScope);
|
|
1100
|
-
// Look for return statement
|
|
1101
|
-
for (const stmt of body.body) {
|
|
1102
|
-
if (t.isVariableDeclaration(stmt)) {
|
|
1103
|
-
collectStaticBindingsFromDeclarations([stmt], ctx, callbackLocalScope);
|
|
1104
|
-
}
|
|
1105
|
-
if (t.isReturnStatement(stmt) && stmt.argument) {
|
|
1106
|
-
if (t.isJSXElement(stmt.argument)) {
|
|
1107
|
-
const childKey = processJSXElement(stmt.argument, ctx, source);
|
|
1108
|
-
if (childKey)
|
|
1109
|
-
region.childKeys.push(childKey);
|
|
1110
|
-
}
|
|
1111
|
-
else if (isJSXFragment(stmt.argument)) {
|
|
1112
|
-
region.childKeys.push(...processJSXFragment(stmt.argument, ctx, source));
|
|
1113
|
-
}
|
|
1114
|
-
else if (t.isParenthesizedExpression(stmt.argument) &&
|
|
1115
|
-
t.isJSXElement(stmt.argument.expression)) {
|
|
1116
|
-
const childKey = processJSXElement(stmt.argument.expression, ctx, source);
|
|
1117
|
-
if (childKey)
|
|
1118
|
-
region.childKeys.push(childKey);
|
|
1119
|
-
}
|
|
1120
|
-
else if (t.isParenthesizedExpression(stmt.argument) &&
|
|
1121
|
-
isJSXFragment(stmt.argument.expression)) {
|
|
1122
|
-
region.childKeys.push(...processJSXFragment(stmt.argument.expression, ctx, source));
|
|
1123
|
-
}
|
|
1124
|
-
break;
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
ctx.valueScopes.pop();
|
|
1128
|
-
}
|
|
1129
|
-
else if (t.isParenthesizedExpression(body) &&
|
|
1130
|
-
t.isJSXElement(body.expression)) {
|
|
1131
|
-
const childKey = processJSXElement(body.expression, ctx, source);
|
|
1132
|
-
if (childKey)
|
|
1133
|
-
region.childKeys.push(childKey);
|
|
1134
|
-
}
|
|
1135
|
-
ctx.valueScopes.pop();
|
|
1136
|
-
}
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
// Ternary → conditional region
|
|
1140
|
-
if (t.isConditionalExpression(expr)) {
|
|
1141
|
-
const parentInfo = ctx.parentStack[ctx.parentStack.length - 1] ?? null;
|
|
1142
|
-
const conditionCandidate = {
|
|
1143
|
-
kind: "conditional-branch",
|
|
1144
|
-
conditionExpression: getExpressionText(expr.test, source),
|
|
1145
|
-
conditionSource: getExpressionSourceInfo(expr.test, source, ctx),
|
|
1146
|
-
};
|
|
1147
|
-
const region = {
|
|
1148
|
-
kind: "conditional-region",
|
|
1149
|
-
ownerKey: parentInfo?.key ?? "",
|
|
1150
|
-
childKeys: [],
|
|
1151
|
-
sourceRange: toSourceRange(expr),
|
|
1152
|
-
sourceCandidates: [conditionCandidate],
|
|
1153
|
-
};
|
|
1154
|
-
ctx.regions.push(region);
|
|
1155
|
-
// Process consequent (then)
|
|
1156
|
-
if (t.isJSXElement(expr.consequent)) {
|
|
1157
|
-
const childKey = processJSXElement(expr.consequent, ctx, source);
|
|
1158
|
-
if (childKey)
|
|
1159
|
-
region.childKeys.push(childKey);
|
|
1160
|
-
}
|
|
1161
|
-
// Process alternate (else)
|
|
1162
|
-
if (t.isJSXElement(expr.alternate)) {
|
|
1163
|
-
const childKey = processJSXElement(expr.alternate, ctx, source);
|
|
1164
|
-
if (childKey)
|
|
1165
|
-
region.childKeys.push(childKey);
|
|
1166
|
-
}
|
|
1167
|
-
return;
|
|
1168
|
-
}
|
|
1169
|
-
// && logical expression → conditional region (single branch)
|
|
1170
|
-
if (t.isLogicalExpression(expr) && expr.operator === "&&") {
|
|
1171
|
-
const parentInfo = ctx.parentStack[ctx.parentStack.length - 1] ?? null;
|
|
1172
|
-
const conditionCandidate = {
|
|
1173
|
-
kind: "conditional-branch",
|
|
1174
|
-
conditionExpression: getExpressionText(expr.left, source),
|
|
1175
|
-
conditionSource: getExpressionSourceInfo(expr.left, source, ctx),
|
|
1176
|
-
};
|
|
1177
|
-
const region = {
|
|
1178
|
-
kind: "conditional-region",
|
|
1179
|
-
ownerKey: parentInfo?.key ?? "",
|
|
1180
|
-
childKeys: [],
|
|
1181
|
-
sourceRange: toSourceRange(expr),
|
|
1182
|
-
sourceCandidates: [conditionCandidate],
|
|
1183
|
-
};
|
|
1184
|
-
ctx.regions.push(region);
|
|
1185
|
-
if (t.isJSXElement(expr.right)) {
|
|
1186
|
-
const childKey = processJSXElement(expr.right, ctx, source);
|
|
1187
|
-
if (childKey)
|
|
1188
|
-
region.childKeys.push(childKey);
|
|
1189
|
-
}
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
// Parenthesized expression
|
|
1193
|
-
if (t.isParenthesizedExpression(expr)) {
|
|
1194
|
-
processExpression(expr.expression, ctx, source);
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1197
|
-
// Direct JSX in expression
|
|
1198
|
-
if (t.isJSXElement(expr)) {
|
|
1199
|
-
processJSXElement(expr, ctx, source);
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
if (isJSXFragment(expr)) {
|
|
1203
|
-
processJSXFragment(expr, ctx, source);
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
function getJSXReturnArgument(statement) {
|
|
1208
|
-
const argument = statement.argument;
|
|
1209
|
-
if (!argument) {
|
|
1210
|
-
return null;
|
|
1211
|
-
}
|
|
1212
|
-
if (t.isJSXElement(argument) || isJSXFragment(argument)) {
|
|
1213
|
-
return argument;
|
|
1214
|
-
}
|
|
1215
|
-
if (t.isParenthesizedExpression(argument) &&
|
|
1216
|
-
(t.isJSXElement(argument.expression) || isJSXFragment(argument.expression))) {
|
|
1217
|
-
return argument.expression;
|
|
1218
|
-
}
|
|
1219
|
-
return null;
|
|
1220
|
-
}
|
|
1221
|
-
function collectReturnRecordsFromStatements(statements, inheritedCondition) {
|
|
1222
|
-
const records = [];
|
|
1223
|
-
for (const statement of statements) {
|
|
1224
|
-
if (t.isReturnStatement(statement)) {
|
|
1225
|
-
const argument = getJSXReturnArgument(statement);
|
|
1226
|
-
if (argument) {
|
|
1227
|
-
records.push({ argument, statement, condition: inheritedCondition });
|
|
1228
|
-
}
|
|
1229
|
-
continue;
|
|
1230
|
-
}
|
|
1231
|
-
if (t.isIfStatement(statement)) {
|
|
1232
|
-
const consequentStatements = t.isBlockStatement(statement.consequent)
|
|
1233
|
-
? statement.consequent.body
|
|
1234
|
-
: [statement.consequent];
|
|
1235
|
-
records.push(...collectReturnRecordsFromStatements(consequentStatements, statement.test));
|
|
1236
|
-
if (statement.alternate) {
|
|
1237
|
-
const alternateStatements = t.isBlockStatement(statement.alternate)
|
|
1238
|
-
? statement.alternate.body
|
|
1239
|
-
: [statement.alternate];
|
|
1240
|
-
records.push(...collectReturnRecordsFromStatements(alternateStatements, statement.test));
|
|
1241
|
-
}
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1244
|
-
if (t.isSwitchStatement(statement)) {
|
|
1245
|
-
for (const switchCase of statement.cases) {
|
|
1246
|
-
const condition = switchCase.test
|
|
1247
|
-
? t.binaryExpression("===", statement.discriminant, switchCase.test)
|
|
1248
|
-
: statement.discriminant;
|
|
1249
|
-
records.push(...collectReturnRecordsFromStatements(switchCase.consequent, condition));
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
return records;
|
|
1254
|
-
}
|
|
1255
|
-
// ── Main: build graph from single source file ──
|
|
1256
|
-
export function buildGraphFromSource(file, source) {
|
|
1257
|
-
return buildGraphWithLocalImportsFromSource(file, source).graph;
|
|
1258
|
-
}
|
|
1259
|
-
export function buildGraphWithLocalImportsFromSource(file, source) {
|
|
1260
|
-
const emptyGraph = {
|
|
1261
|
-
routeId: file,
|
|
1262
|
-
rootKeys: [],
|
|
1263
|
-
elements: new Map(),
|
|
1264
|
-
regions: [],
|
|
1265
|
-
fileIndex: new Map(),
|
|
1266
|
-
version: 1,
|
|
1267
|
-
};
|
|
1268
|
-
const ast = parseSource(source);
|
|
1269
|
-
if (!ast)
|
|
1270
|
-
return { graph: emptyGraph, localImports: [] };
|
|
1271
|
-
const localImports = extractLocalImportsFromAst(ast);
|
|
1272
|
-
const elements = new Map();
|
|
1273
|
-
const regions = [];
|
|
1274
|
-
const rootKeys = [];
|
|
1275
|
-
const fileIndex = new Map();
|
|
1276
|
-
// Build import map once for the whole file
|
|
1277
|
-
const importMap = buildImportMap(ast);
|
|
1278
|
-
// Find all React component functions and their JSX return values
|
|
1279
|
-
traverse(ast, {
|
|
1280
|
-
"FunctionDeclaration|FunctionExpression|ArrowFunctionExpression"(path) {
|
|
1281
|
-
const componentName = findComponentName(path);
|
|
1282
|
-
if (!componentName)
|
|
1283
|
-
return;
|
|
1284
|
-
const node = path.node;
|
|
1285
|
-
let returnRecords = [];
|
|
1286
|
-
if (t.isArrowFunctionExpression(node)) {
|
|
1287
|
-
if (t.isJSXElement(node.body) || isJSXFragment(node.body)) {
|
|
1288
|
-
returnRecords = [
|
|
1289
|
-
{
|
|
1290
|
-
argument: node.body,
|
|
1291
|
-
statement: t.returnStatement(node.body),
|
|
1292
|
-
},
|
|
1293
|
-
];
|
|
1294
|
-
}
|
|
1295
|
-
else if (t.isBlockStatement(node.body)) {
|
|
1296
|
-
returnRecords = collectReturnRecordsFromStatements(node.body.body);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
else {
|
|
1300
|
-
const body = node.body;
|
|
1301
|
-
if (t.isBlockStatement(body)) {
|
|
1302
|
-
returnRecords = collectReturnRecordsFromStatements(body.body);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
if (returnRecords.length === 0)
|
|
1306
|
-
return;
|
|
1307
|
-
const fileScope = new Map();
|
|
1308
|
-
collectStaticBindingsFromDeclarations(getFileScopeVariableDeclarations(ast.program.body), createEvaluationContext(file, componentName, elements, regions, importMap, [fileScope]), fileScope);
|
|
1309
|
-
const componentScope = new Map();
|
|
1310
|
-
for (const param of node.params) {
|
|
1311
|
-
for (const name of collectBoundIdentifiers(param)) {
|
|
1312
|
-
setUnresolvedBinding(componentScope, name);
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
const componentBody = t.isBlockStatement(node.body) ? node.body.body : [];
|
|
1316
|
-
const firstReturnStatement = returnRecords[0]?.statement ?? null;
|
|
1317
|
-
for (const statement of componentBody) {
|
|
1318
|
-
if (firstReturnStatement && statement === firstReturnStatement) {
|
|
1319
|
-
break;
|
|
1320
|
-
}
|
|
1321
|
-
if (t.isVariableDeclaration(statement)) {
|
|
1322
|
-
collectStaticBindingsFromDeclarations([statement], createEvaluationContext(file, componentName, elements, regions, importMap, [fileScope, componentScope]), componentScope);
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
const ctx = createEvaluationContext(file, componentName, elements, regions, importMap, [fileScope, componentScope]);
|
|
1326
|
-
for (const record of returnRecords) {
|
|
1327
|
-
const childKeys = processRenderableNode(record.argument, ctx, source);
|
|
1328
|
-
for (const key of childKeys) {
|
|
1329
|
-
if (!rootKeys.includes(key)) {
|
|
1330
|
-
rootKeys.push(key);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
if (record.condition) {
|
|
1334
|
-
const conditionCandidate = {
|
|
1335
|
-
kind: "conditional-branch",
|
|
1336
|
-
conditionExpression: getExpressionText(record.condition, source),
|
|
1337
|
-
conditionSource: getExpressionSourceInfo(record.condition, source, ctx),
|
|
1338
|
-
};
|
|
1339
|
-
pushRegion(ctx, "conditional-region", "", record.statement, childKeys, [conditionCandidate]);
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
// Don't traverse into nested functions (they might be other components)
|
|
1343
|
-
path.skip();
|
|
1344
|
-
},
|
|
1345
|
-
});
|
|
1346
|
-
// Build file index
|
|
1347
|
-
const keys = [...elements.keys()];
|
|
1348
|
-
if (keys.length > 0) {
|
|
1349
|
-
fileIndex.set(file, keys);
|
|
1350
|
-
}
|
|
1351
|
-
return {
|
|
1352
|
-
graph: {
|
|
1353
|
-
routeId: file,
|
|
1354
|
-
rootKeys,
|
|
1355
|
-
elements,
|
|
1356
|
-
regions,
|
|
1357
|
-
fileIndex,
|
|
1358
|
-
version: 1,
|
|
1359
|
-
},
|
|
1360
|
-
localImports,
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
// ── Re-export for backward compatibility ──
|
|
1364
|
-
export { buildGraphFromSource as buildProjectSourceGraph };
|
|
1365
|
-
/**
|
|
1366
|
-
* 从项目源图中提取单路由的渲染源图。
|
|
1367
|
-
* 当前阶段直接返回单文件构建结果。
|
|
1368
|
-
*/
|
|
1369
|
-
export function extractRenderSourceGraph(graph, _routeId, _entryFile) {
|
|
1370
|
-
return graph;
|
|
1371
|
-
}
|