@trojanbox-vcp-test/site-edit-engine 0.1.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 (174) hide show
  1. package/README.md +85 -0
  2. package/dist/execute-integration/execute-fixture-harness.d.ts +25 -0
  3. package/dist/execute-integration/execute-fixture-harness.js +37 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/internal/ast/diagnostics/index.d.ts +5 -0
  7. package/dist/internal/ast/diagnostics/index.js +25 -0
  8. package/dist/internal/ast/history/index.d.ts +15 -0
  9. package/dist/internal/ast/history/index.js +62 -0
  10. package/dist/internal/ast/index.d.ts +8 -0
  11. package/dist/internal/ast/index.js +5 -0
  12. package/dist/internal/ast/locators/index.d.ts +1 -0
  13. package/dist/internal/ast/locators/index.js +1 -0
  14. package/dist/internal/ast/locators/resolve-locator.d.ts +16 -0
  15. package/dist/internal/ast/locators/resolve-locator.js +920 -0
  16. package/dist/internal/ast/parser/SourceParser.d.ts +30 -0
  17. package/dist/internal/ast/parser/SourceParser.js +49 -0
  18. package/dist/internal/ast/parser/index.d.ts +21 -0
  19. package/dist/internal/ast/parser/index.js +64 -0
  20. package/dist/internal/ast/primitives/conditional/conditional-primitives.d.ts +18 -0
  21. package/dist/internal/ast/primitives/conditional/conditional-primitives.js +237 -0
  22. package/dist/internal/ast/primitives/conditional/index.d.ts +1 -0
  23. package/dist/internal/ast/primitives/conditional/index.js +1 -0
  24. package/dist/internal/ast/primitives/imports/add-import.d.ts +18 -0
  25. package/dist/internal/ast/primitives/imports/add-import.js +111 -0
  26. package/dist/internal/ast/primitives/imports/index.d.ts +2 -0
  27. package/dist/internal/ast/primitives/imports/index.js +2 -0
  28. package/dist/internal/ast/primitives/imports/remove-import.d.ts +15 -0
  29. package/dist/internal/ast/primitives/imports/remove-import.js +72 -0
  30. package/dist/internal/ast/primitives/index.d.ts +10 -0
  31. package/dist/internal/ast/primitives/index.js +10 -0
  32. package/dist/internal/ast/primitives/jsx/index.d.ts +4 -0
  33. package/dist/internal/ast/primitives/jsx/index.js +4 -0
  34. package/dist/internal/ast/primitives/jsx/insert-child.d.ts +11 -0
  35. package/dist/internal/ast/primitives/jsx/insert-child.js +69 -0
  36. package/dist/internal/ast/primitives/jsx/move-node.d.ts +9 -0
  37. package/dist/internal/ast/primitives/jsx/move-node.js +76 -0
  38. package/dist/internal/ast/primitives/jsx/remove-node.d.ts +7 -0
  39. package/dist/internal/ast/primitives/jsx/remove-node.js +36 -0
  40. package/dist/internal/ast/primitives/jsx/update-text.d.ts +8 -0
  41. package/dist/internal/ast/primitives/jsx/update-text.js +81 -0
  42. package/dist/internal/ast/primitives/next/index.d.ts +1 -0
  43. package/dist/internal/ast/primitives/next/index.js +1 -0
  44. package/dist/internal/ast/primitives/next/next-primitives.d.ts +43 -0
  45. package/dist/internal/ast/primitives/next/next-primitives.js +211 -0
  46. package/dist/internal/ast/primitives/shared.d.ts +60 -0
  47. package/dist/internal/ast/primitives/shared.js +176 -0
  48. package/dist/internal/ast/primitives/style/class-expression.d.ts +23 -0
  49. package/dist/internal/ast/primitives/style/class-expression.js +174 -0
  50. package/dist/internal/ast/primitives/style/index.d.ts +1 -0
  51. package/dist/internal/ast/primitives/style/index.js +1 -0
  52. package/dist/internal/ast/primitives/style/style-primitives.d.ts +49 -0
  53. package/dist/internal/ast/primitives/style/style-primitives.js +555 -0
  54. package/dist/internal/ast/primitives/values/index.d.ts +1 -0
  55. package/dist/internal/ast/primitives/values/index.js +1 -0
  56. package/dist/internal/ast/primitives/values/value-primitives.d.ts +42 -0
  57. package/dist/internal/ast/primitives/values/value-primitives.js +158 -0
  58. package/dist/internal/ast/printer/SourcePrinter.d.ts +21 -0
  59. package/dist/internal/ast/printer/SourcePrinter.js +76 -0
  60. package/dist/internal/ast/printer/index.d.ts +6 -0
  61. package/dist/internal/ast/printer/index.js +126 -0
  62. package/dist/internal/ast/types.d.ts +190 -0
  63. package/dist/internal/ast/types.js +1 -0
  64. package/dist/internal/capability/capability-resolver.d.ts +16 -0
  65. package/dist/internal/capability/capability-resolver.js +127 -0
  66. package/dist/internal/classname-source.d.ts +24 -0
  67. package/dist/internal/classname-source.js +220 -0
  68. package/dist/internal/contracts/IEditEngineRuntime.d.ts +18 -0
  69. package/dist/internal/contracts/IEditEngineRuntime.js +1 -0
  70. package/dist/internal/domain/EditDiagnostic.d.ts +38 -0
  71. package/dist/internal/domain/EditDiagnostic.js +43 -0
  72. package/dist/internal/events/event-bus.d.ts +14 -0
  73. package/dist/internal/events/event-bus.js +21 -0
  74. package/dist/internal/graph/graph-builder.d.ts +12 -0
  75. package/dist/internal/graph/graph-builder.js +1371 -0
  76. package/dist/internal/graph/import-resolver.d.ts +31 -0
  77. package/dist/internal/graph/import-resolver.js +109 -0
  78. package/dist/internal/graph/project-graph-builder.d.ts +32 -0
  79. package/dist/internal/graph/project-graph-builder.js +133 -0
  80. package/dist/internal/graph/types.d.ts +114 -0
  81. package/dist/internal/graph/types.js +6 -0
  82. package/dist/internal/history/undo-redo.d.ts +28 -0
  83. package/dist/internal/history/undo-redo.js +42 -0
  84. package/dist/internal/index.d.ts +2 -0
  85. package/dist/internal/index.js +1 -0
  86. package/dist/internal/planner/planner.d.ts +104 -0
  87. package/dist/internal/planner/planner.js +2533 -0
  88. package/dist/internal/planner/types.d.ts +275 -0
  89. package/dist/internal/planner/types.js +6 -0
  90. package/dist/internal/protocol/boundary.d.ts +10 -0
  91. package/dist/internal/protocol/boundary.js +3 -0
  92. package/dist/internal/protocol/capability.d.ts +47 -0
  93. package/dist/internal/protocol/capability.js +8 -0
  94. package/dist/internal/protocol/error.d.ts +43 -0
  95. package/dist/internal/protocol/error.js +38 -0
  96. package/dist/internal/protocol/event.d.ts +39 -0
  97. package/dist/internal/protocol/event.js +3 -0
  98. package/dist/internal/protocol/identity.d.ts +26 -0
  99. package/dist/internal/protocol/identity.js +30 -0
  100. package/dist/internal/protocol/operation.d.ts +224 -0
  101. package/dist/internal/protocol/operation.js +8 -0
  102. package/dist/internal/protocol/render.d.ts +212 -0
  103. package/dist/internal/protocol/render.js +3 -0
  104. package/dist/internal/protocol.d.ts +9 -0
  105. package/dist/internal/protocol.js +2 -0
  106. package/dist/internal/provenance/binding-graph.d.ts +39 -0
  107. package/dist/internal/provenance/binding-graph.js +184 -0
  108. package/dist/internal/provenance/capability-policy.d.ts +15 -0
  109. package/dist/internal/provenance/capability-policy.js +96 -0
  110. package/dist/internal/provenance/data-source-classifier.d.ts +14 -0
  111. package/dist/internal/provenance/data-source-classifier.js +281 -0
  112. package/dist/internal/provenance/resolve-text-provenance.d.ts +45 -0
  113. package/dist/internal/provenance/resolve-text-provenance.js +3090 -0
  114. package/dist/internal/provenance/types.d.ts +89 -0
  115. package/dist/internal/provenance/types.js +1 -0
  116. package/dist/internal/render/component-semantic.d.ts +11 -0
  117. package/dist/internal/render/component-semantic.js +141 -0
  118. package/dist/internal/render/content-model.d.ts +3 -0
  119. package/dist/internal/render/content-model.js +89 -0
  120. package/dist/internal/render/media-model.d.ts +3 -0
  121. package/dist/internal/render/media-model.js +45 -0
  122. package/dist/internal/render/provenance-types.d.ts +33 -0
  123. package/dist/internal/render/provenance-types.js +1 -0
  124. package/dist/internal/render/render-projection.d.ts +24 -0
  125. package/dist/internal/render/render-projection.js +281 -0
  126. package/dist/internal/render/tailwind-style-model.d.ts +19 -0
  127. package/dist/internal/render/tailwind-style-model.js +1187 -0
  128. package/dist/internal/runtime/EditEngineRuntime.d.ts +25 -0
  129. package/dist/internal/runtime/EditEngineRuntime.js +89 -0
  130. package/dist/internal/runtime/EditEngineRuntimeSnapshot.d.ts +31 -0
  131. package/dist/internal/runtime/EditEngineRuntimeSnapshot.js +15 -0
  132. package/dist/internal/runtime/InternalEditEngine.d.ts +44 -0
  133. package/dist/internal/runtime/InternalEditEngine.js +1391 -0
  134. package/dist/internal/runtime.d.ts +3 -0
  135. package/dist/internal/runtime.js +1 -0
  136. package/dist/internal/topology/topology.d.ts +6 -0
  137. package/dist/internal/topology/topology.js +98 -0
  138. package/dist/internal/topology/types.d.ts +35 -0
  139. package/dist/internal/topology/types.js +5 -0
  140. package/dist/internal/types.d.ts +1 -0
  141. package/dist/internal/types.js +1 -0
  142. package/dist/internal/writeback/in-memory-fs.d.ts +7 -0
  143. package/dist/internal/writeback/in-memory-fs.js +44 -0
  144. package/dist/internal/writeback/types.d.ts +45 -0
  145. package/dist/internal/writeback/types.js +7 -0
  146. package/dist/internal/writeback/writeback-service.d.ts +7 -0
  147. package/dist/internal/writeback/writeback-service.js +568 -0
  148. package/dist/internal-adapter.d.ts +18 -0
  149. package/dist/internal-adapter.js +350 -0
  150. package/dist/next-app-router-fs.d.ts +2 -0
  151. package/dist/next-app-router-fs.js +64 -0
  152. package/dist/next-app-router.d.ts +11 -0
  153. package/dist/next-app-router.js +140 -0
  154. package/dist/preview-runtime.d.ts +394 -0
  155. package/dist/preview-runtime.js +102 -0
  156. package/dist/public-file-system.d.ts +7 -0
  157. package/dist/public-file-system.js +1 -0
  158. package/dist/runtime-sync.d.ts +95 -0
  159. package/dist/runtime-sync.js +321 -0
  160. package/dist/runtime.d.ts +340 -0
  161. package/dist/runtime.js +134 -0
  162. package/dist/site-edit-instrumentation.d.ts +19 -0
  163. package/dist/site-edit-instrumentation.js +322 -0
  164. package/dist/snapshot-file-system.d.ts +19 -0
  165. package/dist/snapshot-file-system.js +49 -0
  166. package/dist/source-watcher.d.ts +20 -0
  167. package/dist/source-watcher.js +150 -0
  168. package/dist/source-writeback-test-harness.d.ts +244 -0
  169. package/dist/source-writeback-test-harness.js +119 -0
  170. package/dist/types.d.ts +68 -0
  171. package/dist/types.js +1 -0
  172. package/dist/webpack-loader.cjs +592 -0
  173. package/dist/webpack-loader.d.ts +27 -0
  174. package/package.json +66 -0
@@ -0,0 +1,1371 @@
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
+ }