@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,3090 @@
1
+ import path from "node:path";
2
+ import * as parser from "@babel/parser";
3
+ import _traverse from "@babel/traverse";
4
+ import * as t from "@babel/types";
5
+ import { resolveLocalImport } from "../graph/import-resolver.js";
6
+ import { findAliasInitializer, findPatternBindingPath, findPropDefaultBinding, getParamPropReference as getBindingParamPropReference, } from "./binding-graph.js";
7
+ import { classifyBindingDataSource, classifyExpressionDataSource, isApiSource, } from "./data-source-classifier.js";
8
+ const traverse = (typeof _traverse === "function" ? _traverse : _traverse.default);
9
+ function buildApiExternalProvenance(file, sourceId, displayPath) {
10
+ return {
11
+ kind: "external",
12
+ file,
13
+ sourceType: "api-field",
14
+ sourceId,
15
+ displayPath,
16
+ editMode: "readonly",
17
+ diagnostics: [
18
+ {
19
+ code: "external-source-readonly",
20
+ message: "Dynamic API data is read-only for content edits",
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ export async function resolveTextProvenance(params) {
26
+ const { file, source, segment, readFile, exists } = params;
27
+ const fileCache = params.fileCache ?? new Map();
28
+ const ast = parseSourceCached(fileCache, file, source);
29
+ if (!ast) {
30
+ return null;
31
+ }
32
+ const expressionPath = findExpressionPathByRange(ast, segment.sourceRange);
33
+ if (!expressionPath) {
34
+ return null;
35
+ }
36
+ const sourceInfo = segment.source ??
37
+ (await buildExpressionSourceInfo({
38
+ expression: expressionPath.node,
39
+ scope: expressionPath.scope,
40
+ source,
41
+ currentFile: file,
42
+ readFile,
43
+ exists,
44
+ fileCache,
45
+ visited: new Set(),
46
+ }));
47
+ if (!sourceInfo) {
48
+ const directProvenance = await resolveDirectExpressionProvenance({
49
+ currentFile: file,
50
+ expressionPath,
51
+ readFile,
52
+ exists,
53
+ graph: params.graph,
54
+ externalSourceResolvers: params.externalSourceResolvers,
55
+ externalAdapters: params.externalAdapters,
56
+ functionCallContracts: params.functionCallContracts,
57
+ fileCache,
58
+ visited: new Set(),
59
+ });
60
+ if (!directProvenance) {
61
+ return null;
62
+ }
63
+ if (directProvenance.kind === "external") {
64
+ return {
65
+ kind: "external-entry",
66
+ file: directProvenance.file,
67
+ sourceType: directProvenance.sourceType,
68
+ sourceId: directProvenance.sourceId,
69
+ adapterId: directProvenance.adapterId,
70
+ displayExpression: directProvenance.displayPath,
71
+ };
72
+ }
73
+ const directLocator = {
74
+ kind: "value",
75
+ root: directProvenance.root,
76
+ path: directProvenance.path.length > 0 ? directProvenance.path : [],
77
+ };
78
+ return {
79
+ kind: "value-binding",
80
+ file: directProvenance.file,
81
+ locator: directLocator,
82
+ root: directProvenance.root,
83
+ path: directProvenance.path,
84
+ displayExpression: directProvenance.displayExpression ??
85
+ getExpressionText(expressionPath.node, source),
86
+ sourceKind: directProvenance.sourceKind,
87
+ ...(directProvenance.collectionIndexMap
88
+ ? { collectionIndexMap: directProvenance.collectionIndexMap }
89
+ : {}),
90
+ ...(directProvenance.collectionPathMap
91
+ ? { collectionPathMap: directProvenance.collectionPathMap }
92
+ : {}),
93
+ };
94
+ }
95
+ const visited = new Set();
96
+ const provenance = await resolveSegmentProvenance({
97
+ currentFile: file,
98
+ expressionPath,
99
+ segment: {
100
+ ...segment,
101
+ source: sourceInfo,
102
+ },
103
+ readFile,
104
+ exists,
105
+ graph: params.graph,
106
+ externalSourceResolvers: params.externalSourceResolvers,
107
+ externalAdapters: params.externalAdapters,
108
+ functionCallContracts: params.functionCallContracts,
109
+ fileCache,
110
+ visited,
111
+ });
112
+ if (!provenance) {
113
+ return null;
114
+ }
115
+ if (provenance.kind === "external") {
116
+ return {
117
+ kind: "external-entry",
118
+ file: provenance.file,
119
+ sourceType: provenance.sourceType,
120
+ sourceId: provenance.sourceId,
121
+ adapterId: provenance.adapterId,
122
+ displayExpression: provenance.displayPath,
123
+ };
124
+ }
125
+ const locator = {
126
+ kind: "value",
127
+ root: provenance.root,
128
+ path: provenance.path.length > 0 ? provenance.path : [],
129
+ };
130
+ return {
131
+ kind: "value-binding",
132
+ file: provenance.file,
133
+ locator,
134
+ root: provenance.root,
135
+ path: provenance.path,
136
+ displayExpression: provenance.displayExpression ?? sourceInfo.expression,
137
+ sourceKind: provenance.sourceKind,
138
+ ...(provenance.collectionIndexMap
139
+ ? { collectionIndexMap: provenance.collectionIndexMap }
140
+ : {}),
141
+ ...(provenance.collectionPathMap
142
+ ? { collectionPathMap: provenance.collectionPathMap }
143
+ : {}),
144
+ };
145
+ }
146
+ export async function resolveProvenance(params) {
147
+ const { file, source, readFile, exists } = params;
148
+ const fileCache = params.fileCache ?? new Map();
149
+ const ast = parseSourceCached(fileCache, file, source);
150
+ if (!ast) {
151
+ return unsupportedResolution("unsupported-expression", "Unable to parse source for provenance resolution");
152
+ }
153
+ const visited = new Set();
154
+ if (params.target.kind === "text-segment") {
155
+ return resolveTextSegmentProvenance({
156
+ file,
157
+ source,
158
+ ast,
159
+ segment: params.target.segment,
160
+ readFile,
161
+ exists,
162
+ graph: params.graph,
163
+ externalSourceResolvers: params.externalSourceResolvers,
164
+ externalAdapters: params.externalAdapters,
165
+ functionCallContracts: params.functionCallContracts,
166
+ fileCache,
167
+ visited,
168
+ });
169
+ }
170
+ return resolveConditionalBranchProvenance({
171
+ file,
172
+ source,
173
+ ast,
174
+ candidate: params.target.candidate,
175
+ readFile,
176
+ exists,
177
+ graph: params.graph,
178
+ fileCache,
179
+ visited,
180
+ });
181
+ }
182
+ function parseSource(source) {
183
+ try {
184
+ return parser.parse(source, {
185
+ sourceType: "module",
186
+ plugins: ["jsx", "typescript"],
187
+ });
188
+ }
189
+ catch {
190
+ return null;
191
+ }
192
+ }
193
+ function parseSourceCached(fileCache, file, source) {
194
+ const cached = fileCache.get(file);
195
+ if (cached?.source === source) {
196
+ return cached.ast;
197
+ }
198
+ const ast = parseSource(source);
199
+ fileCache.set(file, { source, ast });
200
+ return ast;
201
+ }
202
+ function findExpressionPathByRange(ast, range) {
203
+ let match = null;
204
+ let matchSpan = -1;
205
+ traverse(ast, {
206
+ Expression(path) {
207
+ const loc = path.node.loc;
208
+ if (!loc || !rangeContains(loc, range)) {
209
+ return;
210
+ }
211
+ const span = (path.node.end ?? 0) - (path.node.start ?? 0);
212
+ if (span >= matchSpan) {
213
+ match = path;
214
+ matchSpan = span;
215
+ }
216
+ },
217
+ });
218
+ return match;
219
+ }
220
+ function findExpressionPathByExactRange(ast, range) {
221
+ let match = null;
222
+ let matchSpan = Number.POSITIVE_INFINITY;
223
+ traverse(ast, {
224
+ Expression(path) {
225
+ if (!matchesRange(path.node.loc, range)) {
226
+ return;
227
+ }
228
+ const span = (path.node.end ?? 0) - (path.node.start ?? 0);
229
+ if (span <= matchSpan) {
230
+ match = path;
231
+ matchSpan = span;
232
+ }
233
+ },
234
+ });
235
+ return match;
236
+ }
237
+ function findJSXElementPathByRange(ast, range) {
238
+ let match = null;
239
+ let matchSpan = Number.POSITIVE_INFINITY;
240
+ traverse(ast, {
241
+ JSXElement(path) {
242
+ const loc = path.node.loc;
243
+ if (!loc || !rangeContains(loc, range)) {
244
+ return;
245
+ }
246
+ const span = (path.node.end ?? 0) - (path.node.start ?? 0);
247
+ if (span <= matchSpan) {
248
+ match = path;
249
+ matchSpan = span;
250
+ }
251
+ },
252
+ });
253
+ return match;
254
+ }
255
+ function matchesRange(loc, range) {
256
+ if (!loc) {
257
+ return false;
258
+ }
259
+ return (loc.start.line === range.startLine &&
260
+ loc.start.column === range.startColumn &&
261
+ loc.end.line === range.endLine &&
262
+ loc.end.column === range.endColumn);
263
+ }
264
+ function rangeContains(loc, range) {
265
+ const start = comparePosition({ line: loc.start.line, column: loc.start.column }, { line: range.startLine, column: range.startColumn });
266
+ const end = comparePosition({ line: loc.end.line, column: loc.end.column }, { line: range.endLine, column: range.endColumn });
267
+ return start >= 0 && end <= 0;
268
+ }
269
+ function toValueSourceRange(node) {
270
+ if (!node.loc) {
271
+ return null;
272
+ }
273
+ return {
274
+ startLine: node.loc.start.line,
275
+ startColumn: node.loc.start.column,
276
+ endLine: node.loc.end.line,
277
+ endColumn: node.loc.end.column,
278
+ };
279
+ }
280
+ function comparePosition(left, right) {
281
+ if (left.line !== right.line) {
282
+ return left.line < right.line ? -1 : 1;
283
+ }
284
+ if (left.column !== right.column) {
285
+ return left.column < right.column ? -1 : 1;
286
+ }
287
+ return 0;
288
+ }
289
+ function expressionSourceInfo(expr, source) {
290
+ if (t.isIdentifier(expr)) {
291
+ return {
292
+ kind: "identifier",
293
+ expression: expr.name,
294
+ base: expr.name,
295
+ path: [],
296
+ };
297
+ }
298
+ if (!t.isMemberExpression(expr)) {
299
+ return null;
300
+ }
301
+ const path = [];
302
+ let current = expr;
303
+ while (t.isMemberExpression(current)) {
304
+ if (current.computed) {
305
+ if (t.isNumericLiteral(current.property)) {
306
+ path.unshift({ kind: "index", index: current.property.value });
307
+ }
308
+ else if (t.isStringLiteral(current.property)) {
309
+ path.unshift({ kind: "property", name: current.property.value });
310
+ }
311
+ else {
312
+ return null;
313
+ }
314
+ }
315
+ else {
316
+ if (!t.isIdentifier(current.property)) {
317
+ return null;
318
+ }
319
+ path.unshift({ kind: "property", name: current.property.name });
320
+ }
321
+ current = current.object;
322
+ }
323
+ if (!t.isIdentifier(current)) {
324
+ return null;
325
+ }
326
+ return {
327
+ kind: path.some((segment) => segment.kind === "index")
328
+ ? "index-expression"
329
+ : "member-expression",
330
+ expression: getExpressionText(expr, source),
331
+ base: current.name,
332
+ path,
333
+ };
334
+ }
335
+ async function buildExpressionSourceInfo(params) {
336
+ if (t.isIdentifier(params.expression)) {
337
+ return {
338
+ kind: "identifier",
339
+ expression: params.expression.name,
340
+ base: params.expression.name,
341
+ path: [],
342
+ };
343
+ }
344
+ if (!t.isMemberExpression(params.expression)) {
345
+ return null;
346
+ }
347
+ const normalized = await normalizeExpressionWithScope(params.expression, params.scope, params.currentFile, params.readFile, params.exists, params.fileCache, params.visited);
348
+ if (!normalized) {
349
+ return null;
350
+ }
351
+ return {
352
+ kind: normalized.path.some((segment) => segment.kind === "index")
353
+ ? "index-expression"
354
+ : "member-expression",
355
+ expression: getExpressionText(params.expression, params.source),
356
+ base: normalized.base,
357
+ path: normalized.path,
358
+ };
359
+ }
360
+ function getExpressionText(node, source) {
361
+ if (source) {
362
+ return getNodeText(node, source);
363
+ }
364
+ const normalized = normalizeExpression(node);
365
+ if (!normalized) {
366
+ return "";
367
+ }
368
+ return normalized.path.reduce((expression, segment) => segment.kind === "property"
369
+ ? `${expression}.${segment.name}`
370
+ : `${expression}[${segment.index}]`, normalized.base);
371
+ }
372
+ function getNodeText(node, source) {
373
+ if (node.start == null || node.end == null) {
374
+ return "";
375
+ }
376
+ return source.slice(node.start, node.end);
377
+ }
378
+ function findExpressionPathByText(ast, source, expressionText) {
379
+ let match = null;
380
+ let matchSpan = Number.POSITIVE_INFINITY;
381
+ traverse(ast, {
382
+ Expression(path) {
383
+ if (getNodeText(path.node, source) !== expressionText) {
384
+ return;
385
+ }
386
+ const span = (path.node.end ?? 0) - (path.node.start ?? 0);
387
+ if (span <= matchSpan) {
388
+ match = path;
389
+ matchSpan = span;
390
+ }
391
+ },
392
+ });
393
+ return match;
394
+ }
395
+ function findExpressionPathByTextInRange(ast, source, expressionText, range) {
396
+ let match = null;
397
+ let matchSpan = Number.POSITIVE_INFINITY;
398
+ traverse(ast, {
399
+ Expression(path) {
400
+ if (getNodeText(path.node, source) !== expressionText) {
401
+ return;
402
+ }
403
+ const loc = path.node.loc;
404
+ if (!loc || !rangeContains(loc, range)) {
405
+ return;
406
+ }
407
+ const span = (path.node.end ?? 0) - (path.node.start ?? 0);
408
+ if (span <= matchSpan) {
409
+ match = path;
410
+ matchSpan = span;
411
+ }
412
+ },
413
+ });
414
+ return match;
415
+ }
416
+ async function resolveTextSegmentProvenance(ctx) {
417
+ const { file, source, ast, segment, readFile, exists, fileCache, visited } = ctx;
418
+ const expressionPath = findExpressionPathByRange(ast, segment.sourceRange);
419
+ const sourceInfo = segment.source ??
420
+ (expressionPath
421
+ ? await buildExpressionSourceInfo({
422
+ expression: expressionPath.node,
423
+ scope: expressionPath.scope,
424
+ source,
425
+ currentFile: file,
426
+ readFile,
427
+ exists,
428
+ fileCache,
429
+ visited,
430
+ })
431
+ : null);
432
+ if (!sourceInfo) {
433
+ if (!expressionPath) {
434
+ return literalResolution(file, segment.value);
435
+ }
436
+ const directProvenance = await resolveDirectExpressionProvenance({
437
+ currentFile: file,
438
+ expressionPath,
439
+ readFile,
440
+ exists,
441
+ graph: ctx.graph,
442
+ externalSourceResolvers: ctx.externalSourceResolvers,
443
+ externalAdapters: ctx.externalAdapters,
444
+ functionCallContracts: ctx.functionCallContracts,
445
+ fileCache,
446
+ visited,
447
+ });
448
+ if (directProvenance) {
449
+ return directProvenance.kind === "external"
450
+ ? buildExternalResolution(file, directProvenance)
451
+ : buildValueResolution(file, getExpressionText(expressionPath.node, source), directProvenance);
452
+ }
453
+ return unsupportedResolutionForExpression({
454
+ expression: expressionPath.node,
455
+ scope: expressionPath.scope,
456
+ currentFile: file,
457
+ source,
458
+ readFile,
459
+ exists,
460
+ fileCache,
461
+ visited,
462
+ functionCallContracts: ctx.functionCallContracts,
463
+ });
464
+ }
465
+ if (!expressionPath) {
466
+ return unsupportedResolution("unsupported-expression", "Unable to locate text expression for provenance resolution");
467
+ }
468
+ const provenance = await resolveSourceInfoProvenance({
469
+ currentFile: file,
470
+ expressionPath,
471
+ sourceInfo,
472
+ readFile,
473
+ exists,
474
+ graph: ctx.graph,
475
+ externalSourceResolvers: ctx.externalSourceResolvers,
476
+ externalAdapters: ctx.externalAdapters,
477
+ functionCallContracts: ctx.functionCallContracts,
478
+ fileCache,
479
+ visited,
480
+ });
481
+ if (!provenance) {
482
+ const directExternalDiagnostic = getInvalidExternalHelperDiagnostic(expressionPath.node);
483
+ if (directExternalDiagnostic) {
484
+ return unsupportedResolution(directExternalDiagnostic.code, directExternalDiagnostic.message);
485
+ }
486
+ const binding = expressionPath.scope.getBinding(sourceInfo.base);
487
+ if (binding?.path.isVariableDeclarator()) {
488
+ const bindingExternalDiagnostic = getInvalidExternalHelperDiagnostic(binding.path.node.init);
489
+ if (bindingExternalDiagnostic) {
490
+ return unsupportedResolution(bindingExternalDiagnostic.code, bindingExternalDiagnostic.message);
491
+ }
492
+ }
493
+ if (binding?.path.isVariableDeclarator() &&
494
+ t.isCallExpression(binding.path.node.init)) {
495
+ return readonlyResolution("unsupported-expression", getFunctionCallReadonlyMessage(binding.path.node.init, ctx.functionCallContracts));
496
+ }
497
+ return unsupportedResolution("unsupported-expression", `Unable to resolve provenance for expression "${sourceInfo.expression}"`);
498
+ }
499
+ return provenance.kind === "external"
500
+ ? buildExternalResolution(file, provenance)
501
+ : buildValueResolution(file, sourceInfo.expression, provenance);
502
+ }
503
+ async function resolveConditionalBranchProvenance(ctx) {
504
+ const { file, source, ast, candidate, readFile, exists, fileCache, visited } = ctx;
505
+ if (!candidate.conditionSource) {
506
+ return unsupportedResolution("conditional-branch-unresolved", `Unable to resolve conditional branch source for "${candidate.conditionExpression}"`);
507
+ }
508
+ const expressionPath = findExpressionPathByText(ast, source, candidate.conditionExpression);
509
+ if (!expressionPath) {
510
+ return unsupportedResolution("conditional-branch-unresolved", `Unable to locate conditional expression "${candidate.conditionExpression}"`);
511
+ }
512
+ const provenance = await resolveSourceInfoProvenance({
513
+ currentFile: file,
514
+ expressionPath,
515
+ sourceInfo: candidate.conditionSource,
516
+ readFile,
517
+ exists,
518
+ graph: ctx.graph,
519
+ externalSourceResolvers: ctx.externalSourceResolvers,
520
+ externalAdapters: ctx.externalAdapters,
521
+ functionCallContracts: ctx.functionCallContracts,
522
+ fileCache,
523
+ visited,
524
+ });
525
+ if (!provenance) {
526
+ return unsupportedResolution("conditional-branch-unresolved", `Unable to resolve conditional branch source for "${candidate.conditionExpression}"`);
527
+ }
528
+ if (provenance.kind === "external") {
529
+ return unsupportedResolution("conditional-branch-unresolved", `Unable to resolve conditional branch source for "${candidate.conditionExpression}"`);
530
+ }
531
+ const resolution = buildValueResolution(file, candidate.conditionExpression, provenance);
532
+ if (!resolution.finalSource) {
533
+ return resolution;
534
+ }
535
+ return {
536
+ ...resolution,
537
+ finalSource: {
538
+ ...resolution.finalSource,
539
+ kind: "conditional-branch",
540
+ displayPath: candidate.conditionExpression,
541
+ },
542
+ chain: [
543
+ ...resolution.chain,
544
+ {
545
+ kind: "conditional-branch",
546
+ file: provenance.file,
547
+ displayName: candidate.conditionExpression,
548
+ canEditHere: true,
549
+ },
550
+ ],
551
+ };
552
+ }
553
+ function literalResolution(file, value) {
554
+ return {
555
+ finalSource: {
556
+ kind: "jsx-literal",
557
+ file,
558
+ locator: null,
559
+ displayPath: value,
560
+ },
561
+ chain: [
562
+ {
563
+ kind: "jsx-literal",
564
+ file,
565
+ displayName: value,
566
+ canEditHere: true,
567
+ },
568
+ ],
569
+ confidence: "exact",
570
+ editMode: "direct",
571
+ diagnostics: [],
572
+ };
573
+ }
574
+ async function unsupportedResolutionForExpression(params) {
575
+ const diagnostics = await collectExpressionDiagnostics(params);
576
+ if (diagnostics.length === 0) {
577
+ return unsupportedResolution("unsupported-expression", `Unsupported expression type: ${params.expression.type}`);
578
+ }
579
+ if (isReadonlyExpression(params.expression)) {
580
+ return readonlyResolutionWithDiagnostics(diagnostics);
581
+ }
582
+ if (isInvalidI18nHelperCall(params.expression)) {
583
+ return unsupportedResolution("unsupported-expression", "i18n helpers require a static message id string");
584
+ }
585
+ if (t.isMemberExpression(params.expression) &&
586
+ params.expression.computed &&
587
+ !t.isNumericLiteral(params.expression.property) &&
588
+ !t.isStringLiteral(params.expression.property)) {
589
+ return unsupportedResolution("dynamic-key", "Computed member expressions are not supported for provenance resolution");
590
+ }
591
+ const [primary] = diagnostics;
592
+ return unsupportedResolution(primary.code, primary.message);
593
+ }
594
+ function unsupportedResolution(code, message) {
595
+ return {
596
+ finalSource: null,
597
+ chain: [],
598
+ confidence: "unknown",
599
+ editMode: "unsupported",
600
+ diagnostics: [{ code, message }],
601
+ };
602
+ }
603
+ function readonlyResolution(code, message) {
604
+ return readonlyResolutionWithDiagnostics([{ code, message }]);
605
+ }
606
+ function readonlyResolutionWithDiagnostics(diagnostics) {
607
+ return {
608
+ finalSource: null,
609
+ chain: [],
610
+ confidence: "partial",
611
+ editMode: "readonly",
612
+ diagnostics,
613
+ };
614
+ }
615
+ function isReadonlyExpression(expr) {
616
+ return (t.isCallExpression(expr) ||
617
+ t.isConditionalExpression(expr) ||
618
+ t.isLogicalExpression(expr) ||
619
+ t.isBinaryExpression(expr));
620
+ }
621
+ function uniqueDiagnostics(diagnostics) {
622
+ const seen = new Set();
623
+ return diagnostics.filter((diagnostic) => {
624
+ const key = `${diagnostic.code}:${diagnostic.message}`;
625
+ if (seen.has(key)) {
626
+ return false;
627
+ }
628
+ seen.add(key);
629
+ return true;
630
+ });
631
+ }
632
+ async function collectExpressionDiagnostics(params) {
633
+ const { expression, scope, currentFile, source, readFile, exists, fileCache, visited, functionCallContracts, } = params;
634
+ const invalidExternalDiagnostic = getInvalidExternalHelperDiagnostic(expression);
635
+ if (invalidExternalDiagnostic) {
636
+ return [invalidExternalDiagnostic];
637
+ }
638
+ if (t.isCallExpression(expression)) {
639
+ return [
640
+ {
641
+ code: "unsupported-expression",
642
+ message: getFunctionCallReadonlyMessage(expression, functionCallContracts),
643
+ },
644
+ ];
645
+ }
646
+ if (t.isMemberExpression(expression) && expression.computed) {
647
+ const property = expression.property;
648
+ if (!t.isNumericLiteral(property) && !t.isStringLiteral(property)) {
649
+ if (!t.isExpression(property)) {
650
+ return [
651
+ {
652
+ code: "dynamic-key",
653
+ message: "Computed member expressions are not supported for provenance resolution",
654
+ },
655
+ ];
656
+ }
657
+ const computedProperty = property;
658
+ const computedValue = await evaluateStaticExpression(computedProperty, scope, currentFile, readFile, exists, fileCache, visited);
659
+ if (typeof computedValue !== "string" &&
660
+ !Number.isInteger(computedValue)) {
661
+ return [
662
+ {
663
+ code: "dynamic-key",
664
+ message: "Computed member expressions are not supported for provenance resolution",
665
+ },
666
+ ];
667
+ }
668
+ }
669
+ }
670
+ if (t.isBinaryExpression(expression)) {
671
+ return uniqueDiagnostics([
672
+ ...(await collectNestedExpressionDiagnostics(expression.left, expression.right, params)),
673
+ {
674
+ code: "unsupported-expression",
675
+ message: "Mixed expressions are recognized but remain readonly until a safer composition editing strategy is available",
676
+ },
677
+ ]);
678
+ }
679
+ if (t.isLogicalExpression(expression)) {
680
+ return uniqueDiagnostics([
681
+ ...(await collectNestedExpressionDiagnostics(expression.left, expression.right, params)),
682
+ {
683
+ code: "unsupported-expression",
684
+ message: expression.operator === "??" || expression.operator === "||"
685
+ ? "Fallback expressions are recognized but remain readonly until a safer composition editing strategy is available"
686
+ : "Complex conditional expressions are recognized but remain readonly until a safer editing strategy is available",
687
+ },
688
+ ]);
689
+ }
690
+ if (t.isConditionalExpression(expression)) {
691
+ return uniqueDiagnostics([
692
+ ...(await collectNestedExpressionDiagnostics(expression.test, expression.consequent, expression.alternate, params)),
693
+ {
694
+ code: "unsupported-expression",
695
+ message: "Complex conditional expressions are recognized but remain readonly until a safer editing strategy is available",
696
+ },
697
+ ]);
698
+ }
699
+ return [];
700
+ }
701
+ function getInvalidExternalHelperDiagnostic(node) {
702
+ if (isInvalidI18nHelperCall(node)) {
703
+ return createExternalUnsupportedDiagnostic("i18n helpers require a static message id string");
704
+ }
705
+ if (isInvalidCmsHelperCall(node)) {
706
+ return createExternalUnsupportedDiagnostic("CMS helpers require static entry id and field path string arguments");
707
+ }
708
+ return null;
709
+ }
710
+ async function collectNestedExpressionDiagnostics(...args) {
711
+ const params = args[args.length - 1];
712
+ const expressions = args.slice(0, -1);
713
+ const diagnostics = [];
714
+ for (const expression of expressions) {
715
+ diagnostics.push(...(await collectExpressionDiagnostics({
716
+ ...params,
717
+ expression,
718
+ })));
719
+ }
720
+ return diagnostics;
721
+ }
722
+ function selectExternalAdapter(match, registrations) {
723
+ const candidates = (registrations ?? []).filter((registration) => registration.sourceType === match.sourceType &&
724
+ (registration.capabilities === undefined ||
725
+ registration.capabilities.includes("write-text")));
726
+ if (match.adapterId) {
727
+ const explicit = candidates.find((candidate) => candidate.adapterId === match.adapterId);
728
+ if (explicit) {
729
+ return {
730
+ adapterId: explicit.adapterId,
731
+ editMode: "proxy",
732
+ diagnostics: [],
733
+ };
734
+ }
735
+ }
736
+ else if (candidates.length > 0) {
737
+ return {
738
+ adapterId: candidates[0]?.adapterId,
739
+ editMode: "proxy",
740
+ diagnostics: [],
741
+ };
742
+ }
743
+ return {
744
+ adapterId: undefined,
745
+ editMode: "readonly",
746
+ diagnostics: [createMissingAdapterDiagnostic(match.sourceType)],
747
+ };
748
+ }
749
+ async function resolveExternalExpression(params) {
750
+ for (const resolver of BUILT_IN_EXTERNAL_SOURCE_RESOLVERS) {
751
+ if (!shouldTryExternalSourceResolver(params.expressionPath, resolver.helperNames)) {
752
+ continue;
753
+ }
754
+ const result = await resolver.resolve(params);
755
+ if (result) {
756
+ return buildResolvedExternalProvenance(normalizeExternalSourceResolution(result), params.externalAdapters);
757
+ }
758
+ }
759
+ const resolverContext = {
760
+ currentFile: params.currentFile,
761
+ expressionPath: params.expressionPath,
762
+ readFile: params.readFile,
763
+ exists: params.exists,
764
+ graph: params.graph,
765
+ };
766
+ for (const resolver of params.externalSourceResolvers ?? []) {
767
+ if (!shouldTryExternalSourceResolver(params.expressionPath, resolver.helperNames)) {
768
+ continue;
769
+ }
770
+ const result = await resolver.resolve(resolverContext);
771
+ if (result) {
772
+ return buildResolvedExternalProvenance(normalizeExternalSourceResolution(result), params.externalAdapters);
773
+ }
774
+ }
775
+ return null;
776
+ }
777
+ function createMissingAdapterDiagnostic(sourceType) {
778
+ return {
779
+ code: "missing-adapter",
780
+ message: `No external adapter is registered for ${sourceType}`,
781
+ };
782
+ }
783
+ function createExternalReadonlyDiagnostic(message) {
784
+ return {
785
+ code: "external-source-readonly",
786
+ message,
787
+ };
788
+ }
789
+ function createExternalUnsupportedDiagnostic(message) {
790
+ return {
791
+ code: "unsupported-expression",
792
+ message,
793
+ };
794
+ }
795
+ function getCallExpressionHelperName(node) {
796
+ if (!node || !t.isCallExpression(node)) {
797
+ return null;
798
+ }
799
+ if (t.isIdentifier(node.callee)) {
800
+ return node.callee.name;
801
+ }
802
+ if (t.isMemberExpression(node.callee) &&
803
+ !node.callee.computed &&
804
+ t.isIdentifier(node.callee.object) &&
805
+ t.isIdentifier(node.callee.property)) {
806
+ return `${node.callee.object.name}.${node.callee.property.name}`;
807
+ }
808
+ return null;
809
+ }
810
+ function shouldTryExternalSourceResolver(expressionPath, helperNames) {
811
+ if (!helperNames || helperNames.length === 0) {
812
+ return true;
813
+ }
814
+ const helperName = getCallExpressionHelperName(expressionPath.node);
815
+ return helperName ? helperNames.includes(helperName) : false;
816
+ }
817
+ function normalizeExternalSourceResolution(result) {
818
+ if ("match" in result) {
819
+ return {
820
+ match: result.match,
821
+ editMode: result.editMode,
822
+ diagnostics: uniqueDiagnostics(result.diagnostics ?? []),
823
+ };
824
+ }
825
+ return {
826
+ match: result,
827
+ diagnostics: [],
828
+ };
829
+ }
830
+ function buildResolvedExternalProvenance(result, registrations) {
831
+ const selected = selectExternalAdapter(result.match, registrations);
832
+ return {
833
+ kind: "external",
834
+ file: result.match.file,
835
+ sourceType: result.match.sourceType,
836
+ sourceId: result.match.sourceId,
837
+ adapterId: result.match.adapterId ?? selected.adapterId,
838
+ displayPath: result.match.displayPath,
839
+ editMode: result.editMode === "readonly" ? "readonly" : selected.editMode,
840
+ diagnostics: uniqueDiagnostics([
841
+ ...result.diagnostics,
842
+ ...(result.editMode === "readonly" || selected.editMode === "readonly"
843
+ ? selected.diagnostics
844
+ : []),
845
+ ]),
846
+ };
847
+ }
848
+ function resolveCmsFieldExpression(expressionPath) {
849
+ if (!expressionPath.isCallExpression()) {
850
+ return null;
851
+ }
852
+ const callee = expressionPath.node.callee;
853
+ if (!t.isIdentifier(callee) || callee.name !== "getCmsField") {
854
+ return null;
855
+ }
856
+ const [entryArgument, fieldArgument] = expressionPath.node.arguments;
857
+ if (!entryArgument ||
858
+ !fieldArgument ||
859
+ !t.isStringLiteral(entryArgument) ||
860
+ !t.isStringLiteral(fieldArgument)) {
861
+ return null;
862
+ }
863
+ const entryId = entryArgument.value;
864
+ const fieldPath = fieldArgument.value;
865
+ return {
866
+ sourceType: "cms-field",
867
+ sourceId: `${entryId}:${fieldPath}`,
868
+ file: `cms://${entryId}/${fieldPath.replace(/\./g, "/")}`,
869
+ displayPath: `${entryId}.${fieldPath}`,
870
+ };
871
+ }
872
+ function isInvalidCmsHelperCall(node) {
873
+ return (!!node &&
874
+ t.isCallExpression(node) &&
875
+ t.isIdentifier(node.callee) &&
876
+ node.callee.name === "getCmsField" &&
877
+ (node.arguments.length < 2 ||
878
+ !t.isStringLiteral(node.arguments[0]) ||
879
+ !t.isStringLiteral(node.arguments[1])));
880
+ }
881
+ function getI18nMessageIdFromCall(node) {
882
+ if (!node || !t.isCallExpression(node)) {
883
+ return undefined;
884
+ }
885
+ if (t.isIdentifier(node.callee) && node.callee.name === "t") {
886
+ const [firstArg] = node.arguments;
887
+ return firstArg && t.isStringLiteral(firstArg) ? firstArg.value : null;
888
+ }
889
+ if (t.isMemberExpression(node.callee) &&
890
+ !node.callee.computed &&
891
+ t.isIdentifier(node.callee.object) &&
892
+ node.callee.object.name === "intl" &&
893
+ t.isIdentifier(node.callee.property) &&
894
+ node.callee.property.name === "formatMessage") {
895
+ const [descriptorArg] = node.arguments;
896
+ if (!descriptorArg || !t.isObjectExpression(descriptorArg)) {
897
+ return null;
898
+ }
899
+ for (const property of descriptorArg.properties) {
900
+ if (t.isObjectProperty(property) &&
901
+ !property.computed &&
902
+ ((t.isIdentifier(property.key) && property.key.name === "id") ||
903
+ (t.isStringLiteral(property.key) && property.key.value === "id")) &&
904
+ t.isStringLiteral(property.value)) {
905
+ return property.value.value;
906
+ }
907
+ }
908
+ return null;
909
+ }
910
+ return undefined;
911
+ }
912
+ function isInvalidI18nHelperCall(node) {
913
+ return getI18nMessageIdFromCall(node) === null;
914
+ }
915
+ async function locateLocaleFile(currentFile, locale, exists) {
916
+ const directories = new Set();
917
+ let cursor = path.posix.dirname(currentFile);
918
+ while (true) {
919
+ directories.add(cursor);
920
+ if (cursor === "." || cursor === "/") {
921
+ break;
922
+ }
923
+ const parent = path.posix.dirname(cursor);
924
+ if (parent === cursor) {
925
+ directories.add(".");
926
+ break;
927
+ }
928
+ cursor = parent;
929
+ }
930
+ directories.add("src");
931
+ directories.add(".");
932
+ for (const directory of directories) {
933
+ const prefixes = directory === "."
934
+ ? ["locales", "messages"]
935
+ : [
936
+ path.posix.join(directory, "locales"),
937
+ path.posix.join(directory, "messages"),
938
+ ];
939
+ for (const prefix of prefixes) {
940
+ const candidate = path.posix.join(prefix, `${locale}.json`);
941
+ if (await exists(candidate)) {
942
+ return candidate;
943
+ }
944
+ }
945
+ }
946
+ return null;
947
+ }
948
+ async function resolveI18nExpression(params) {
949
+ const messageId = getI18nMessageIdFromCall(params.expressionPath.node);
950
+ if (typeof messageId !== "string" || messageId.length === 0) {
951
+ return null;
952
+ }
953
+ const locale = "en";
954
+ const localeFile = await locateLocaleFile(params.currentFile, locale, params.exists);
955
+ if (!localeFile) {
956
+ return null;
957
+ }
958
+ return {
959
+ sourceType: "i18n-message",
960
+ sourceId: `${locale}:${messageId}`,
961
+ file: localeFile,
962
+ displayPath: messageId,
963
+ };
964
+ }
965
+ async function resolveApiFieldExpression(params) {
966
+ if (!params.expressionPath.isCallExpression()) {
967
+ return null;
968
+ }
969
+ const callee = params.expressionPath.node.callee;
970
+ if (!t.isIdentifier(callee) || callee.name !== "apiField") {
971
+ return null;
972
+ }
973
+ const [mappingArgument, transformArgument] = params.expressionPath.node.arguments;
974
+ if (!mappingArgument || !t.isExpression(mappingArgument)) {
975
+ return null;
976
+ }
977
+ const sourceId = await evaluateStaticExpression(mappingArgument, params.expressionPath.scope, params.currentFile, params.readFile, params.exists, params.fileCache, params.visited);
978
+ if (typeof sourceId !== "string" || sourceId.length === 0) {
979
+ return null;
980
+ }
981
+ const match = {
982
+ sourceType: "api-field",
983
+ sourceId,
984
+ file: `api://${sourceId}`,
985
+ displayPath: sourceId,
986
+ };
987
+ if (transformArgument) {
988
+ return {
989
+ match,
990
+ editMode: "readonly",
991
+ diagnostics: [
992
+ createExternalReadonlyDiagnostic("API mapping transforms must stay readonly until a reversible mapping strategy is registered"),
993
+ ],
994
+ };
995
+ }
996
+ return match;
997
+ }
998
+ const BUILT_IN_EXTERNAL_SOURCE_RESOLVERS = [
999
+ {
1000
+ helperNames: ["getCmsField"],
1001
+ resolve: ({ expressionPath }) => resolveCmsFieldExpression(expressionPath),
1002
+ },
1003
+ {
1004
+ helperNames: ["apiField"],
1005
+ resolve: (params) => resolveApiFieldExpression(params),
1006
+ },
1007
+ {
1008
+ helperNames: ["t", "intl.formatMessage"],
1009
+ resolve: ({ currentFile, expressionPath, exists }) => resolveI18nExpression({
1010
+ currentFile,
1011
+ expressionPath,
1012
+ exists,
1013
+ }),
1014
+ },
1015
+ ];
1016
+ function buildExternalResolution(currentFile, provenance) {
1017
+ return {
1018
+ finalSource: {
1019
+ kind: provenance.sourceType,
1020
+ file: provenance.file,
1021
+ locator: null,
1022
+ displayPath: provenance.displayPath,
1023
+ externalSource: {
1024
+ sourceType: provenance.sourceType,
1025
+ sourceId: provenance.sourceId,
1026
+ adapterId: provenance.adapterId,
1027
+ },
1028
+ },
1029
+ chain: [
1030
+ ...(provenance.chainPrefix ?? []),
1031
+ {
1032
+ kind: provenance.sourceType,
1033
+ file: provenance.file,
1034
+ displayName: provenance.displayPath,
1035
+ canEditHere: provenance.editMode !== "readonly",
1036
+ },
1037
+ ],
1038
+ confidence: "exact",
1039
+ editMode: provenance.editMode === "direct"
1040
+ ? provenance.file === currentFile
1041
+ ? "direct"
1042
+ : "proxy"
1043
+ : provenance.editMode,
1044
+ diagnostics: provenance.diagnostics,
1045
+ };
1046
+ }
1047
+ function buildValueResolution(currentFile, displayExpression, provenance) {
1048
+ const locator = {
1049
+ kind: "value",
1050
+ root: provenance.root,
1051
+ path: provenance.path.length > 0 ? provenance.path : [],
1052
+ };
1053
+ const hasIndex = provenance.path.some((segment) => segment.kind === "index");
1054
+ const configEntry = describeConfigEntry(provenance);
1055
+ let finalKind;
1056
+ if (provenance.semanticKind) {
1057
+ finalKind = provenance.semanticKind;
1058
+ }
1059
+ else if (configEntry) {
1060
+ finalKind = "config-entry";
1061
+ }
1062
+ else if (provenance.sourceKind === "repeat-template") {
1063
+ finalKind = "repeat-template";
1064
+ }
1065
+ else if (provenance.path.length === 0) {
1066
+ finalKind =
1067
+ provenance.sourceKind === "imported-binding"
1068
+ ? "imported-binding"
1069
+ : "binding";
1070
+ }
1071
+ else {
1072
+ finalKind = hasIndex ? "array-item" : "object-field";
1073
+ }
1074
+ const resolvedDisplayExpression = provenance.displayExpression ?? displayExpression;
1075
+ const chain = [...(provenance.chainPrefix ?? [])];
1076
+ if (provenance.sourceKind === "binding") {
1077
+ chain.push({
1078
+ kind: "binding",
1079
+ file: provenance.file,
1080
+ displayName: provenance.root.kind === "binding"
1081
+ ? provenance.root.name
1082
+ : resolvedDisplayExpression,
1083
+ canEditHere: true,
1084
+ });
1085
+ }
1086
+ else if (provenance.sourceKind === "imported-binding") {
1087
+ chain.push({
1088
+ kind: "imported-binding",
1089
+ file: provenance.file,
1090
+ displayName: provenance.root.kind === "named-export"
1091
+ ? provenance.root.name
1092
+ : "default",
1093
+ canEditHere: true,
1094
+ });
1095
+ }
1096
+ else if (provenance.sourceKind === "repeat-template") {
1097
+ chain.push({
1098
+ kind: "repeat-template",
1099
+ file: provenance.file,
1100
+ displayName: displayExpression,
1101
+ canEditHere: true,
1102
+ });
1103
+ }
1104
+ if (provenance.path.length > 0 &&
1105
+ provenance.sourceKind !== "repeat-template") {
1106
+ chain.push({
1107
+ kind: hasIndex ? "array-item" : "object-field",
1108
+ file: provenance.file,
1109
+ displayName: resolvedDisplayExpression,
1110
+ canEditHere: true,
1111
+ });
1112
+ }
1113
+ if (configEntry) {
1114
+ chain.push({
1115
+ kind: "config-entry",
1116
+ file: provenance.file,
1117
+ displayName: configEntry.displayPath,
1118
+ canEditHere: true,
1119
+ });
1120
+ }
1121
+ return {
1122
+ finalSource: {
1123
+ kind: finalKind,
1124
+ file: provenance.file,
1125
+ locator,
1126
+ root: provenance.root,
1127
+ path: provenance.path,
1128
+ displayPath: resolvedDisplayExpression,
1129
+ externalSource: configEntry
1130
+ ? {
1131
+ sourceType: "config-entry",
1132
+ sourceId: configEntry.sourceId,
1133
+ }
1134
+ : undefined,
1135
+ },
1136
+ chain,
1137
+ confidence: provenance.sourceKind === "repeat-template" ? "safe-derived" : "exact",
1138
+ editMode: provenance.editMode ??
1139
+ (provenance.file === currentFile ? "direct" : "proxy"),
1140
+ diagnostics: provenance.diagnostics ?? [],
1141
+ };
1142
+ }
1143
+ const DEFAULT_FUNCTION_CALL_CONTRACTS = [
1144
+ { helperName: "formatPrice", purity: "pure", sourceArgIndex: 0 },
1145
+ { helperName: "formatDate", purity: "pure", sourceArgIndex: 0 },
1146
+ { helperName: "pickLabel", purity: "pure", sourceArgIndex: 0 },
1147
+ ];
1148
+ function getFunctionCallContract(helperName, contracts) {
1149
+ const allContracts = [
1150
+ ...DEFAULT_FUNCTION_CALL_CONTRACTS,
1151
+ ...(contracts ?? []),
1152
+ ];
1153
+ for (let index = allContracts.length - 1; index >= 0; index -= 1) {
1154
+ const contract = allContracts[index];
1155
+ if (contract?.helperName === helperName) {
1156
+ return contract;
1157
+ }
1158
+ }
1159
+ return null;
1160
+ }
1161
+ function getFunctionCallReadonlyMessage(expression, contracts) {
1162
+ const callee = expression.callee;
1163
+ if (!t.isIdentifier(callee)) {
1164
+ return "Function calls are recognized but remain readonly until a pure-call strategy is registered";
1165
+ }
1166
+ const contract = getFunctionCallContract(callee.name, contracts);
1167
+ if (contract?.purity === "side-effect") {
1168
+ return `Helper "${callee.name}" is registered as side-effectful and remains readonly`;
1169
+ }
1170
+ return "Function calls are recognized but remain readonly until a pure-call strategy is registered";
1171
+ }
1172
+ async function resolveDirectExpressionProvenance(params) {
1173
+ const directExternal = await resolveExternalExpression(params);
1174
+ if (directExternal) {
1175
+ return directExternal;
1176
+ }
1177
+ if (params.expressionPath.isTemplateLiteral()) {
1178
+ const expression = params.expressionPath.node.expressions[0];
1179
+ const expressionPath = params.expressionPath.get("expressions.0");
1180
+ if (params.expressionPath.node.expressions.length === 1 &&
1181
+ expression &&
1182
+ expressionPath &&
1183
+ !Array.isArray(expressionPath) &&
1184
+ expressionPath.isExpression()) {
1185
+ const sourceInfo = await buildExpressionSourceInfo({
1186
+ expression: expressionPath.node,
1187
+ scope: expressionPath.scope,
1188
+ source: params.fileCache.get(params.currentFile)?.source,
1189
+ currentFile: params.currentFile,
1190
+ readFile: params.readFile,
1191
+ exists: params.exists,
1192
+ fileCache: params.fileCache,
1193
+ visited: params.visited,
1194
+ });
1195
+ if (sourceInfo) {
1196
+ const upstream = await resolveSourceInfoProvenance({
1197
+ ...params,
1198
+ expressionPath,
1199
+ sourceInfo,
1200
+ });
1201
+ if (upstream && upstream.kind === "value") {
1202
+ return {
1203
+ ...upstream,
1204
+ displayExpression: sourceInfo.expression,
1205
+ editMode: "readonly",
1206
+ diagnostics: [
1207
+ {
1208
+ code: "unsupported-expression",
1209
+ message: "Mixed expressions are recognized but remain readonly until a safer composition editing strategy is available",
1210
+ },
1211
+ ],
1212
+ };
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ return await resolvePureHelperExpression(params);
1218
+ }
1219
+ async function resolvePureHelperExpression(params) {
1220
+ if (!params.expressionPath.isCallExpression()) {
1221
+ return null;
1222
+ }
1223
+ const helperName = getCallExpressionHelperName(params.expressionPath.node);
1224
+ if (!helperName) {
1225
+ return null;
1226
+ }
1227
+ const contract = getFunctionCallContract(helperName, params.functionCallContracts);
1228
+ if (!contract || contract.purity !== "pure") {
1229
+ return null;
1230
+ }
1231
+ const sourceArgIndex = contract.sourceArgIndex ?? 0;
1232
+ const argumentPath = params.expressionPath.get(`arguments.${sourceArgIndex}`);
1233
+ if (!argumentPath ||
1234
+ Array.isArray(argumentPath) ||
1235
+ !argumentPath.isExpression()) {
1236
+ return null;
1237
+ }
1238
+ return await resolveHelperSourceArgumentProvenance({
1239
+ currentFile: params.currentFile,
1240
+ argumentPath,
1241
+ readFile: params.readFile,
1242
+ exists: params.exists,
1243
+ graph: params.graph,
1244
+ externalSourceResolvers: params.externalSourceResolvers,
1245
+ externalAdapters: params.externalAdapters,
1246
+ functionCallContracts: params.functionCallContracts,
1247
+ fileCache: params.fileCache,
1248
+ visited: params.visited,
1249
+ });
1250
+ }
1251
+ async function resolveHelperSourceArgumentProvenance(params) {
1252
+ const directExternal = await resolveExternalExpression({
1253
+ currentFile: params.currentFile,
1254
+ expressionPath: params.argumentPath,
1255
+ readFile: params.readFile,
1256
+ exists: params.exists,
1257
+ graph: params.graph,
1258
+ externalSourceResolvers: params.externalSourceResolvers,
1259
+ externalAdapters: params.externalAdapters,
1260
+ functionCallContracts: params.functionCallContracts,
1261
+ fileCache: params.fileCache,
1262
+ visited: params.visited,
1263
+ });
1264
+ if (directExternal) {
1265
+ return directExternal;
1266
+ }
1267
+ const sourceInfo = await buildExpressionSourceInfo({
1268
+ expression: params.argumentPath.node,
1269
+ scope: params.argumentPath.scope,
1270
+ source: params.fileCache.get(params.currentFile)?.source,
1271
+ currentFile: params.currentFile,
1272
+ readFile: params.readFile,
1273
+ exists: params.exists,
1274
+ fileCache: params.fileCache,
1275
+ visited: params.visited,
1276
+ });
1277
+ if (sourceInfo) {
1278
+ const upstream = await resolveSourceInfoProvenance({
1279
+ currentFile: params.currentFile,
1280
+ expressionPath: params.argumentPath,
1281
+ sourceInfo,
1282
+ readFile: params.readFile,
1283
+ exists: params.exists,
1284
+ graph: params.graph,
1285
+ externalSourceResolvers: params.externalSourceResolvers,
1286
+ externalAdapters: params.externalAdapters,
1287
+ functionCallContracts: params.functionCallContracts,
1288
+ fileCache: params.fileCache,
1289
+ visited: params.visited,
1290
+ });
1291
+ if (!upstream || upstream.kind === "external") {
1292
+ return upstream;
1293
+ }
1294
+ return {
1295
+ ...upstream,
1296
+ displayExpression: sourceInfo.expression,
1297
+ };
1298
+ }
1299
+ return await resolveDirectExpressionProvenance({
1300
+ currentFile: params.currentFile,
1301
+ expressionPath: params.argumentPath,
1302
+ readFile: params.readFile,
1303
+ exists: params.exists,
1304
+ graph: params.graph,
1305
+ externalSourceResolvers: params.externalSourceResolvers,
1306
+ externalAdapters: params.externalAdapters,
1307
+ functionCallContracts: params.functionCallContracts,
1308
+ fileCache: params.fileCache,
1309
+ visited: params.visited,
1310
+ });
1311
+ }
1312
+ async function resolvePureHelperBinding(params) {
1313
+ if (!params.binding.path.isVariableDeclarator()) {
1314
+ return null;
1315
+ }
1316
+ const init = params.binding.path.node.init;
1317
+ if (!init || !t.isCallExpression(init)) {
1318
+ return null;
1319
+ }
1320
+ const callee = init.callee;
1321
+ if (!t.isIdentifier(callee)) {
1322
+ return null;
1323
+ }
1324
+ const contract = getFunctionCallContract(callee.name, params.functionCallContracts);
1325
+ if (!contract || contract.purity !== "pure") {
1326
+ return null;
1327
+ }
1328
+ const sourceArgIndex = contract.sourceArgIndex ?? 0;
1329
+ const initPath = params.binding.path.get("init");
1330
+ if (!initPath || Array.isArray(initPath) || !initPath.isCallExpression()) {
1331
+ return null;
1332
+ }
1333
+ const argumentPath = initPath.get(`arguments.${sourceArgIndex}`);
1334
+ if (!argumentPath ||
1335
+ Array.isArray(argumentPath) ||
1336
+ !argumentPath.isExpression()) {
1337
+ return null;
1338
+ }
1339
+ const upstream = await resolveHelperSourceArgumentProvenance({
1340
+ currentFile: params.currentFile,
1341
+ readFile: params.readFile,
1342
+ exists: params.exists,
1343
+ graph: params.graph,
1344
+ externalSourceResolvers: params.externalSourceResolvers,
1345
+ externalAdapters: params.externalAdapters,
1346
+ functionCallContracts: params.functionCallContracts,
1347
+ fileCache: params.fileCache,
1348
+ visited: params.visited,
1349
+ argumentPath,
1350
+ });
1351
+ return upstream;
1352
+ }
1353
+ function describeConfigEntry(provenance) {
1354
+ if (provenance.sourceKind !== "imported-binding") {
1355
+ return null;
1356
+ }
1357
+ const rootName = provenance.root.kind === "binding" ||
1358
+ provenance.root.kind === "named-export"
1359
+ ? provenance.root.name
1360
+ : provenance.root.kind === "default-export"
1361
+ ? "default"
1362
+ : null;
1363
+ if (!rootName) {
1364
+ return null;
1365
+ }
1366
+ const isConfigFile = /(^|[\/.-])(config|settings)([\/.-]|$)/i.test(provenance.file);
1367
+ const isConfigRoot = /config|settings/i.test(rootName);
1368
+ if (!isConfigFile && !isConfigRoot) {
1369
+ return null;
1370
+ }
1371
+ const displayPath = appendExpressionPath(rootName, provenance.path);
1372
+ return {
1373
+ sourceId: displayPath,
1374
+ displayPath,
1375
+ };
1376
+ }
1377
+ async function resolveSegmentProvenance(ctx) {
1378
+ const sourceInfo = ctx.segment.source;
1379
+ if (!sourceInfo) {
1380
+ return null;
1381
+ }
1382
+ return resolveSourceInfoProvenance({
1383
+ currentFile: ctx.currentFile,
1384
+ expressionPath: ctx.expressionPath,
1385
+ sourceInfo,
1386
+ readFile: ctx.readFile,
1387
+ exists: ctx.exists,
1388
+ graph: ctx.graph,
1389
+ externalSourceResolvers: ctx.externalSourceResolvers,
1390
+ externalAdapters: ctx.externalAdapters,
1391
+ functionCallContracts: ctx.functionCallContracts,
1392
+ fileCache: ctx.fileCache,
1393
+ visited: ctx.visited,
1394
+ });
1395
+ }
1396
+ async function resolveSourceInfoProvenance(ctx) {
1397
+ const sourceInfo = ctx.sourceInfo;
1398
+ const repeatContext = findRepeatContext(ctx.expressionPath, sourceInfo.base);
1399
+ if (repeatContext &&
1400
+ (sourceInfo.path.length > 0 || repeatContext.basePath.length > 0)) {
1401
+ const collectionTarget = await resolveRepeatCollectionTarget({
1402
+ currentFile: ctx.currentFile,
1403
+ callPath: repeatContext.callPath,
1404
+ readFile: ctx.readFile,
1405
+ exists: ctx.exists,
1406
+ graph: ctx.graph,
1407
+ externalSourceResolvers: ctx.externalSourceResolvers,
1408
+ externalAdapters: ctx.externalAdapters,
1409
+ fileCache: ctx.fileCache,
1410
+ visited: ctx.visited,
1411
+ });
1412
+ if (!collectionTarget) {
1413
+ const apiSource = classifyRepeatCollectionSource(repeatContext.callPath, [
1414
+ ...repeatContext.basePath,
1415
+ ...sourceInfo.path,
1416
+ ]);
1417
+ return isApiSource(apiSource)
1418
+ ? buildApiExternalProvenance(ctx.currentFile, apiSource.sourceId, apiSource.displayPath)
1419
+ : null;
1420
+ }
1421
+ const itemPathMap = buildRepeatItemPathMap(collectionTarget, [
1422
+ ...repeatContext.basePath,
1423
+ ...sourceInfo.path,
1424
+ ]);
1425
+ const sourceIndex = collectionTarget.collectionIndexMap?.[0] ?? 0;
1426
+ const path = [
1427
+ ...(collectionTarget.pathPrefix ?? []),
1428
+ { kind: "index", index: sourceIndex },
1429
+ ...repeatContext.basePath,
1430
+ ...sourceInfo.path,
1431
+ ];
1432
+ const visibleValuePath = [
1433
+ { kind: "index", index: 0 },
1434
+ ...repeatContext.basePath,
1435
+ ...sourceInfo.path,
1436
+ ];
1437
+ const value = followStaticPath(collectionTarget.value, visibleValuePath);
1438
+ if (value === undefined) {
1439
+ return null;
1440
+ }
1441
+ return {
1442
+ kind: "value",
1443
+ file: collectionTarget.file,
1444
+ root: collectionTarget.root,
1445
+ path: itemPathMap?.[0] ?? path,
1446
+ sourceKind: "repeat-template",
1447
+ collectionIndexMap: collectionTarget.collectionIndexMap,
1448
+ ...(itemPathMap ? { collectionPathMap: itemPathMap } : {}),
1449
+ };
1450
+ }
1451
+ if (repeatContext) {
1452
+ const apiSource = classifyRepeatCollectionSource(repeatContext.callPath, []);
1453
+ return isApiSource(apiSource)
1454
+ ? buildApiExternalProvenance(ctx.currentFile, apiSource.sourceId, apiSource.displayPath)
1455
+ : null;
1456
+ }
1457
+ const binding = ctx.expressionPath.scope.getBinding(sourceInfo.base);
1458
+ if (!binding) {
1459
+ return null;
1460
+ }
1461
+ const aliasInitializer = findAliasInitializer(binding);
1462
+ if (aliasInitializer) {
1463
+ const initPath = aliasInitializer.expressionPath;
1464
+ const aliasSourceInfo = await buildExpressionSourceInfo({
1465
+ expression: initPath.node,
1466
+ scope: initPath.scope,
1467
+ currentFile: ctx.currentFile,
1468
+ readFile: ctx.readFile,
1469
+ exists: ctx.exists,
1470
+ fileCache: ctx.fileCache,
1471
+ visited: ctx.visited,
1472
+ });
1473
+ if (aliasSourceInfo) {
1474
+ const resolvedAlias = await resolveSourceInfoProvenance({
1475
+ ...ctx,
1476
+ expressionPath: initPath,
1477
+ sourceInfo: {
1478
+ ...aliasSourceInfo,
1479
+ expression: appendExpressionPath(aliasSourceInfo.expression, sourceInfo.path),
1480
+ path: [...aliasSourceInfo.path, ...sourceInfo.path],
1481
+ },
1482
+ });
1483
+ if (resolvedAlias) {
1484
+ return resolvedAlias;
1485
+ }
1486
+ }
1487
+ }
1488
+ if (binding.kind === "param") {
1489
+ return resolveComponentPropBinding({
1490
+ currentFile: ctx.currentFile,
1491
+ binding,
1492
+ sourceInfo,
1493
+ graph: ctx.graph,
1494
+ readFile: ctx.readFile,
1495
+ exists: ctx.exists,
1496
+ externalSourceResolvers: ctx.externalSourceResolvers,
1497
+ externalAdapters: ctx.externalAdapters,
1498
+ functionCallContracts: ctx.functionCallContracts,
1499
+ fileCache: ctx.fileCache,
1500
+ visited: ctx.visited,
1501
+ });
1502
+ }
1503
+ const pureHelperProvenance = await resolvePureHelperBinding({
1504
+ currentFile: ctx.currentFile,
1505
+ binding,
1506
+ readFile: ctx.readFile,
1507
+ exists: ctx.exists,
1508
+ graph: ctx.graph,
1509
+ externalSourceResolvers: ctx.externalSourceResolvers,
1510
+ externalAdapters: ctx.externalAdapters,
1511
+ functionCallContracts: ctx.functionCallContracts,
1512
+ fileCache: ctx.fileCache,
1513
+ visited: ctx.visited,
1514
+ });
1515
+ if (pureHelperProvenance) {
1516
+ return pureHelperProvenance;
1517
+ }
1518
+ const dataSource = classifyBindingDataSource(binding, sourceInfo.path);
1519
+ if (isApiSource(dataSource)) {
1520
+ return buildApiExternalProvenance(ctx.currentFile, dataSource.sourceId, dataSource.displayPath);
1521
+ }
1522
+ const provenance = await resolveBindingTarget({
1523
+ currentFile: ctx.currentFile,
1524
+ binding,
1525
+ readFile: ctx.readFile,
1526
+ exists: ctx.exists,
1527
+ graph: ctx.graph,
1528
+ externalSourceResolvers: ctx.externalSourceResolvers,
1529
+ externalAdapters: ctx.externalAdapters,
1530
+ functionCallContracts: ctx.functionCallContracts,
1531
+ fileCache: ctx.fileCache,
1532
+ visited: ctx.visited,
1533
+ });
1534
+ if (!provenance) {
1535
+ return null;
1536
+ }
1537
+ if (provenance.kind === "external") {
1538
+ return sourceInfo.path.length === 0 ? provenance : null;
1539
+ }
1540
+ const value = followStaticPath(provenance.value, sourceInfo.path);
1541
+ if (value === undefined) {
1542
+ return null;
1543
+ }
1544
+ return {
1545
+ kind: "value",
1546
+ file: provenance.file,
1547
+ root: provenance.root,
1548
+ path: sourceInfo.path,
1549
+ sourceKind: provenance.sourceKind,
1550
+ collectionIndexMap: provenance.collectionIndexMap,
1551
+ };
1552
+ }
1553
+ function findRepeatContext(expressionPath, baseName) {
1554
+ const binding = expressionPath.scope.getBinding(baseName);
1555
+ if (!binding) {
1556
+ return null;
1557
+ }
1558
+ const functionPath = binding.path.getFunctionParent();
1559
+ if (!functionPath ||
1560
+ (!functionPath.isArrowFunctionExpression() &&
1561
+ !functionPath.isFunctionExpression())) {
1562
+ return null;
1563
+ }
1564
+ const firstParam = functionPath.node.params[0];
1565
+ const basePath = findPatternBindingPath(firstParam, baseName);
1566
+ if (!basePath) {
1567
+ return null;
1568
+ }
1569
+ const callPath = functionPath.parentPath;
1570
+ if (!callPath || !callPath.isCallExpression()) {
1571
+ return null;
1572
+ }
1573
+ if (!isMapCallback(callPath, functionPath)) {
1574
+ return null;
1575
+ }
1576
+ return { callPath, basePath };
1577
+ }
1578
+ function isMapCallback(callPath, functionPath) {
1579
+ const callee = callPath.node.callee;
1580
+ if (!t.isMemberExpression(callee)) {
1581
+ return false;
1582
+ }
1583
+ if (!t.isIdentifier(callee.property) || callee.property.name !== "map") {
1584
+ return false;
1585
+ }
1586
+ return callPath.node.arguments.some((argument) => argument === functionPath.node);
1587
+ }
1588
+ async function resolveRepeatCollectionTarget(params) {
1589
+ const { currentFile, callPath, readFile, exists, fileCache, visited } = params;
1590
+ const callee = callPath.node.callee;
1591
+ if (!t.isMemberExpression(callee) ||
1592
+ !t.isIdentifier(callee.property) ||
1593
+ callee.property.name !== "map") {
1594
+ return null;
1595
+ }
1596
+ const calleePath = callPath.get("callee");
1597
+ if (!calleePath.isMemberExpression()) {
1598
+ return null;
1599
+ }
1600
+ const objectPath = calleePath.get("object");
1601
+ if (Array.isArray(objectPath) || !objectPath.isExpression()) {
1602
+ return null;
1603
+ }
1604
+ const provenance = await resolveStaticCollectionExpression({
1605
+ currentFile,
1606
+ expressionPath: objectPath,
1607
+ readFile,
1608
+ exists,
1609
+ graph: params.graph,
1610
+ externalSourceResolvers: params.externalSourceResolvers,
1611
+ externalAdapters: params.externalAdapters,
1612
+ functionCallContracts: params.functionCallContracts,
1613
+ fileCache,
1614
+ visited,
1615
+ });
1616
+ if (!provenance) {
1617
+ return null;
1618
+ }
1619
+ if (!Array.isArray(provenance.value) || provenance.value.length === 0) {
1620
+ return null;
1621
+ }
1622
+ return provenance;
1623
+ }
1624
+ function classifyRepeatCollectionSource(callPath, valuePath) {
1625
+ const calleePath = callPath.get("callee");
1626
+ if (!calleePath.isMemberExpression()) {
1627
+ return null;
1628
+ }
1629
+ const objectPath = calleePath.get("object");
1630
+ if (Array.isArray(objectPath) || !objectPath.isExpression()) {
1631
+ return null;
1632
+ }
1633
+ return classifyExpressionDataSource(objectPath, valuePath);
1634
+ }
1635
+ async function resolveStaticCollectionExpression(params) {
1636
+ const { currentFile, expressionPath, readFile, exists, fileCache, visited } = params;
1637
+ if (expressionPath.isIdentifier()) {
1638
+ const binding = expressionPath.scope.getBinding(expressionPath.node.name);
1639
+ if (!binding) {
1640
+ return null;
1641
+ }
1642
+ const provenance = await resolveBindingTarget({
1643
+ currentFile,
1644
+ binding,
1645
+ readFile,
1646
+ exists,
1647
+ graph: params.graph,
1648
+ externalSourceResolvers: params.externalSourceResolvers,
1649
+ externalAdapters: params.externalAdapters,
1650
+ functionCallContracts: params.functionCallContracts,
1651
+ fileCache,
1652
+ visited,
1653
+ });
1654
+ return provenance?.kind === "static" ? provenance : null;
1655
+ }
1656
+ if (expressionPath.isCallExpression()) {
1657
+ return resolveCollectionCallTarget({
1658
+ currentFile,
1659
+ callPath: expressionPath,
1660
+ readFile,
1661
+ exists,
1662
+ graph: params.graph,
1663
+ externalSourceResolvers: params.externalSourceResolvers,
1664
+ externalAdapters: params.externalAdapters,
1665
+ functionCallContracts: params.functionCallContracts,
1666
+ fileCache,
1667
+ visited,
1668
+ });
1669
+ }
1670
+ if (expressionPath.isTSAsExpression() ||
1671
+ expressionPath.isTSSatisfiesExpression() ||
1672
+ expressionPath.isTSNonNullExpression()) {
1673
+ const innerPath = expressionPath.get("expression");
1674
+ if (!Array.isArray(innerPath) && innerPath.isExpression()) {
1675
+ return resolveStaticCollectionExpression({
1676
+ ...params,
1677
+ expressionPath: innerPath,
1678
+ });
1679
+ }
1680
+ }
1681
+ if (expressionPath.isArrayExpression()) {
1682
+ const sourceRange = toValueSourceRange(expressionPath.node);
1683
+ if (!sourceRange) {
1684
+ return null;
1685
+ }
1686
+ const value = await evaluateStaticExpression(expressionPath.node, expressionPath.scope, currentFile, readFile, exists, fileCache, visited);
1687
+ if (!Array.isArray(value)) {
1688
+ return null;
1689
+ }
1690
+ return {
1691
+ kind: "static",
1692
+ file: currentFile,
1693
+ root: { kind: "expression", sourceRange },
1694
+ value,
1695
+ sourceKind: "binding",
1696
+ };
1697
+ }
1698
+ if (expressionPath.isMemberExpression()) {
1699
+ const sourceInfo = await buildExpressionSourceInfo({
1700
+ expression: expressionPath.node,
1701
+ scope: expressionPath.scope,
1702
+ currentFile,
1703
+ readFile,
1704
+ exists,
1705
+ fileCache,
1706
+ visited,
1707
+ });
1708
+ if (!sourceInfo) {
1709
+ return null;
1710
+ }
1711
+ const provenance = await resolveSourceInfoProvenance({
1712
+ currentFile,
1713
+ expressionPath,
1714
+ sourceInfo,
1715
+ readFile,
1716
+ exists,
1717
+ graph: params.graph,
1718
+ externalSourceResolvers: params.externalSourceResolvers,
1719
+ externalAdapters: params.externalAdapters,
1720
+ functionCallContracts: params.functionCallContracts,
1721
+ fileCache,
1722
+ visited,
1723
+ });
1724
+ if (!provenance || provenance.kind === "external") {
1725
+ return null;
1726
+ }
1727
+ const rootTarget = await resolveStaticRootTarget({
1728
+ currentFile,
1729
+ expressionPath,
1730
+ provenance,
1731
+ readFile,
1732
+ exists,
1733
+ graph: params.graph,
1734
+ externalSourceResolvers: params.externalSourceResolvers,
1735
+ externalAdapters: params.externalAdapters,
1736
+ functionCallContracts: params.functionCallContracts,
1737
+ fileCache,
1738
+ visited,
1739
+ });
1740
+ if (!rootTarget || rootTarget.kind !== "static") {
1741
+ return null;
1742
+ }
1743
+ if (provenance.collectionPathMap?.length) {
1744
+ const flattened = flattenCollectionPathMap(rootTarget.value, provenance.collectionPathMap);
1745
+ if (flattened) {
1746
+ return {
1747
+ kind: "static",
1748
+ file: provenance.file,
1749
+ root: provenance.root,
1750
+ value: flattened.value,
1751
+ sourceKind: provenance.sourceKind,
1752
+ collectionPathMap: flattened.collectionPathMap,
1753
+ };
1754
+ }
1755
+ }
1756
+ const value = followStaticPath(rootTarget.value, provenance.path);
1757
+ if (!Array.isArray(value)) {
1758
+ return null;
1759
+ }
1760
+ return {
1761
+ kind: "static",
1762
+ file: provenance.file,
1763
+ root: provenance.root,
1764
+ value,
1765
+ sourceKind: provenance.sourceKind,
1766
+ pathPrefix: provenance.path,
1767
+ collectionIndexMap: provenance.collectionIndexMap,
1768
+ collectionPathMap: provenance.collectionPathMap,
1769
+ };
1770
+ }
1771
+ return null;
1772
+ }
1773
+ async function resolveStaticRootTarget(params) {
1774
+ const { provenance } = params;
1775
+ if (provenance.root.kind === "binding") {
1776
+ const rootBinding = params.expressionPath.scope.getBinding(provenance.root.name);
1777
+ if (!rootBinding) {
1778
+ return null;
1779
+ }
1780
+ return await resolveBindingTarget({
1781
+ currentFile: params.currentFile,
1782
+ binding: rootBinding,
1783
+ readFile: params.readFile,
1784
+ exists: params.exists,
1785
+ graph: params.graph,
1786
+ externalSourceResolvers: params.externalSourceResolvers,
1787
+ externalAdapters: params.externalAdapters,
1788
+ functionCallContracts: params.functionCallContracts,
1789
+ fileCache: params.fileCache,
1790
+ visited: params.visited,
1791
+ });
1792
+ }
1793
+ if (provenance.root.kind !== "expression") {
1794
+ return null;
1795
+ }
1796
+ const loaded = await loadFile(provenance.file, params.readFile, params.fileCache);
1797
+ if (!loaded.ast) {
1798
+ return null;
1799
+ }
1800
+ const rootExpressionPath = findExpressionPathByExactRange(loaded.ast, provenance.root.sourceRange);
1801
+ if (!rootExpressionPath) {
1802
+ return null;
1803
+ }
1804
+ const value = await evaluateStaticExpression(rootExpressionPath.node, rootExpressionPath.scope, provenance.file, params.readFile, params.exists, params.fileCache, params.visited);
1805
+ if (value === undefined) {
1806
+ return null;
1807
+ }
1808
+ return {
1809
+ kind: "static",
1810
+ file: provenance.file,
1811
+ root: provenance.root,
1812
+ value,
1813
+ sourceKind: provenance.sourceKind,
1814
+ };
1815
+ }
1816
+ async function resolveCollectionCallTarget(params) {
1817
+ return ((await resolveFilterCallTarget(params)) ??
1818
+ (await resolveSliceCallTarget(params)) ??
1819
+ (await resolveSortCallTarget(params)));
1820
+ }
1821
+ async function resolveFilterCallTarget(params) {
1822
+ const { callPath } = params;
1823
+ const callee = callPath.node.callee;
1824
+ if (!t.isMemberExpression(callee) ||
1825
+ !t.isIdentifier(callee.property) ||
1826
+ callee.property.name !== "filter") {
1827
+ return null;
1828
+ }
1829
+ const calleePath = callPath.get("callee");
1830
+ if (!calleePath.isMemberExpression()) {
1831
+ return null;
1832
+ }
1833
+ const objectPath = calleePath.get("object");
1834
+ if (Array.isArray(objectPath) || !objectPath.isExpression()) {
1835
+ return null;
1836
+ }
1837
+ const upstream = await resolveStaticCollectionExpression({
1838
+ currentFile: params.currentFile,
1839
+ expressionPath: objectPath,
1840
+ readFile: params.readFile,
1841
+ exists: params.exists,
1842
+ graph: params.graph,
1843
+ externalSourceResolvers: params.externalSourceResolvers,
1844
+ externalAdapters: params.externalAdapters,
1845
+ functionCallContracts: params.functionCallContracts,
1846
+ fileCache: params.fileCache,
1847
+ visited: params.visited,
1848
+ });
1849
+ if (!upstream || !Array.isArray(upstream.value)) {
1850
+ return null;
1851
+ }
1852
+ const [callback] = callPath.node.arguments;
1853
+ if (!t.isArrowFunctionExpression(callback) &&
1854
+ !t.isFunctionExpression(callback)) {
1855
+ return null;
1856
+ }
1857
+ const firstParam = callback.params[0];
1858
+ if (!t.isIdentifier(firstParam)) {
1859
+ return null;
1860
+ }
1861
+ const predicate = getFilterPredicateExpression(callback);
1862
+ if (!predicate) {
1863
+ return null;
1864
+ }
1865
+ const filteredValue = [];
1866
+ const collectionIndexMap = [];
1867
+ const collectionPathMap = [];
1868
+ for (const [index, value] of upstream.value.entries()) {
1869
+ const keep = evaluateStaticFilterPredicate(predicate, firstParam.name, value);
1870
+ if (typeof keep !== "boolean") {
1871
+ return null;
1872
+ }
1873
+ if (keep) {
1874
+ filteredValue.push(value);
1875
+ collectionIndexMap.push(upstream.collectionIndexMap?.[index] ?? index);
1876
+ const itemPath = upstream.collectionPathMap?.[index];
1877
+ if (itemPath) {
1878
+ collectionPathMap.push(itemPath);
1879
+ }
1880
+ }
1881
+ }
1882
+ return {
1883
+ ...upstream,
1884
+ value: filteredValue,
1885
+ pathPrefix: upstream.pathPrefix,
1886
+ collectionIndexMap,
1887
+ ...(collectionPathMap.length > 0 ? { collectionPathMap } : {}),
1888
+ };
1889
+ }
1890
+ async function resolveSliceCallTarget(params) {
1891
+ const { callPath } = params;
1892
+ const callee = callPath.node.callee;
1893
+ if (!t.isMemberExpression(callee) ||
1894
+ !t.isIdentifier(callee.property) ||
1895
+ callee.property.name !== "slice") {
1896
+ return null;
1897
+ }
1898
+ const calleePath = callPath.get("callee");
1899
+ if (!calleePath.isMemberExpression()) {
1900
+ return null;
1901
+ }
1902
+ const objectPath = calleePath.get("object");
1903
+ if (Array.isArray(objectPath) || !objectPath.isExpression()) {
1904
+ return null;
1905
+ }
1906
+ const upstream = await resolveStaticCollectionExpression({
1907
+ currentFile: params.currentFile,
1908
+ expressionPath: objectPath,
1909
+ readFile: params.readFile,
1910
+ exists: params.exists,
1911
+ graph: params.graph,
1912
+ externalSourceResolvers: params.externalSourceResolvers,
1913
+ externalAdapters: params.externalAdapters,
1914
+ functionCallContracts: params.functionCallContracts,
1915
+ fileCache: params.fileCache,
1916
+ visited: params.visited,
1917
+ });
1918
+ if (!upstream || !Array.isArray(upstream.value)) {
1919
+ return null;
1920
+ }
1921
+ const [startArg, endArg] = callPath.node.arguments;
1922
+ const start = getStaticSliceIndex(startArg, 0);
1923
+ const end = getStaticSliceIndex(endArg, upstream.value.length);
1924
+ if (start === null || end === null) {
1925
+ return null;
1926
+ }
1927
+ const normalizedStart = Math.max(0, start < 0 ? upstream.value.length + start : start);
1928
+ const normalizedEnd = Math.max(0, end < 0 ? upstream.value.length + end : end);
1929
+ const collectionIndexMap = upstream.value
1930
+ .map((_, index) => upstream.collectionIndexMap?.[index] ?? index)
1931
+ .slice(normalizedStart, normalizedEnd);
1932
+ const collectionPathMap = upstream.collectionPathMap?.slice(normalizedStart, normalizedEnd);
1933
+ return {
1934
+ ...upstream,
1935
+ value: upstream.value.slice(normalizedStart, normalizedEnd),
1936
+ pathPrefix: upstream.pathPrefix,
1937
+ collectionIndexMap,
1938
+ ...(collectionPathMap ? { collectionPathMap } : {}),
1939
+ };
1940
+ }
1941
+ function getStaticSliceIndex(node, fallback) {
1942
+ if (!node) {
1943
+ return fallback;
1944
+ }
1945
+ return t.isNumericLiteral(node) && Number.isInteger(node.value)
1946
+ ? node.value
1947
+ : null;
1948
+ }
1949
+ async function resolveSortCallTarget(params) {
1950
+ const { callPath } = params;
1951
+ const callee = callPath.node.callee;
1952
+ if (!t.isMemberExpression(callee) ||
1953
+ !t.isIdentifier(callee.property) ||
1954
+ (callee.property.name !== "sort" && callee.property.name !== "toSorted")) {
1955
+ return null;
1956
+ }
1957
+ const calleePath = callPath.get("callee");
1958
+ if (!calleePath.isMemberExpression()) {
1959
+ return null;
1960
+ }
1961
+ const objectPath = calleePath.get("object");
1962
+ if (Array.isArray(objectPath) || !objectPath.isExpression()) {
1963
+ return null;
1964
+ }
1965
+ const upstream = await resolveStaticCollectionExpression({
1966
+ currentFile: params.currentFile,
1967
+ expressionPath: objectPath,
1968
+ readFile: params.readFile,
1969
+ exists: params.exists,
1970
+ graph: params.graph,
1971
+ externalSourceResolvers: params.externalSourceResolvers,
1972
+ externalAdapters: params.externalAdapters,
1973
+ functionCallContracts: params.functionCallContracts,
1974
+ fileCache: params.fileCache,
1975
+ visited: params.visited,
1976
+ });
1977
+ if (!upstream || !Array.isArray(upstream.value)) {
1978
+ return null;
1979
+ }
1980
+ const [comparator] = callPath.node.arguments;
1981
+ if (!t.isArrowFunctionExpression(comparator) &&
1982
+ !t.isFunctionExpression(comparator)) {
1983
+ return null;
1984
+ }
1985
+ const [leftParam, rightParam] = comparator.params;
1986
+ if (!t.isIdentifier(leftParam) || !t.isIdentifier(rightParam)) {
1987
+ return null;
1988
+ }
1989
+ const entries = upstream.value.map((value, index) => ({
1990
+ value,
1991
+ sourceIndex: upstream.collectionIndexMap?.[index] ?? index,
1992
+ originalIndex: index,
1993
+ }));
1994
+ const sortedEntries = [...entries];
1995
+ for (const left of entries) {
1996
+ for (const right of entries) {
1997
+ if (evaluateStaticSortComparator(comparator, leftParam.name, rightParam.name, left.value, right.value) === undefined) {
1998
+ return null;
1999
+ }
2000
+ }
2001
+ }
2002
+ sortedEntries.sort((left, right) => {
2003
+ const compared = evaluateStaticSortComparator(comparator, leftParam.name, rightParam.name, left.value, right.value) ?? 0;
2004
+ return compared === 0 ? left.originalIndex - right.originalIndex : compared;
2005
+ });
2006
+ return {
2007
+ ...upstream,
2008
+ value: sortedEntries.map((entry) => entry.value),
2009
+ pathPrefix: upstream.pathPrefix,
2010
+ collectionIndexMap: sortedEntries.map((entry) => entry.sourceIndex),
2011
+ ...(upstream.collectionPathMap
2012
+ ? {
2013
+ collectionPathMap: sortedEntries
2014
+ .map((entry) => upstream.collectionPathMap?.[entry.originalIndex])
2015
+ .filter((path) => Boolean(path)),
2016
+ }
2017
+ : {}),
2018
+ };
2019
+ }
2020
+ function evaluateStaticSortComparator(comparator, leftName, rightName, leftValue, rightValue) {
2021
+ const body = t.isExpression(comparator.body)
2022
+ ? comparator.body
2023
+ : comparator.body.body.find((statement) => t.isReturnStatement(statement))?.argument;
2024
+ if (!body || !t.isExpression(body)) {
2025
+ return undefined;
2026
+ }
2027
+ const value = evaluateStaticSortExpression(body, leftName, rightName, leftValue, rightValue);
2028
+ return typeof value === "number" ? value : undefined;
2029
+ }
2030
+ function evaluateStaticSortExpression(expression, leftName, rightName, leftValue, rightValue) {
2031
+ if (t.isNumericLiteral(expression) ||
2032
+ t.isStringLiteral(expression) ||
2033
+ t.isBooleanLiteral(expression)) {
2034
+ return expression.value;
2035
+ }
2036
+ if (t.isIdentifier(expression)) {
2037
+ if (expression.name === leftName) {
2038
+ return leftValue;
2039
+ }
2040
+ if (expression.name === rightName) {
2041
+ return rightValue;
2042
+ }
2043
+ return undefined;
2044
+ }
2045
+ if (t.isMemberExpression(expression)) {
2046
+ const leftPath = normalizeParamMemberExpression(expression, leftName);
2047
+ if (leftPath) {
2048
+ return followStaticPath(leftValue, leftPath);
2049
+ }
2050
+ const rightPath = normalizeParamMemberExpression(expression, rightName);
2051
+ return rightPath ? followStaticPath(rightValue, rightPath) : undefined;
2052
+ }
2053
+ if (t.isBinaryExpression(expression) && expression.operator === "-") {
2054
+ const left = t.isExpression(expression.left)
2055
+ ? evaluateStaticSortExpression(expression.left, leftName, rightName, leftValue, rightValue)
2056
+ : undefined;
2057
+ const right = t.isExpression(expression.right)
2058
+ ? evaluateStaticSortExpression(expression.right, leftName, rightName, leftValue, rightValue)
2059
+ : undefined;
2060
+ return typeof left === "number" && typeof right === "number"
2061
+ ? left - right
2062
+ : undefined;
2063
+ }
2064
+ return undefined;
2065
+ }
2066
+ function getFilterPredicateExpression(callback) {
2067
+ if (t.isExpression(callback.body)) {
2068
+ return callback.body;
2069
+ }
2070
+ for (const statement of callback.body.body) {
2071
+ if (t.isReturnStatement(statement) &&
2072
+ statement.argument &&
2073
+ t.isExpression(statement.argument)) {
2074
+ return statement.argument;
2075
+ }
2076
+ }
2077
+ return null;
2078
+ }
2079
+ function evaluateStaticFilterPredicate(expression, paramName, item) {
2080
+ const value = evaluateStaticFilterExpression(expression, paramName, item);
2081
+ return typeof value === "boolean" ? value : undefined;
2082
+ }
2083
+ function evaluateStaticFilterExpression(expression, paramName, item) {
2084
+ if (t.isStringLiteral(expression) ||
2085
+ t.isNumericLiteral(expression) ||
2086
+ t.isBooleanLiteral(expression)) {
2087
+ return expression.value;
2088
+ }
2089
+ if (t.isNullLiteral(expression)) {
2090
+ return null;
2091
+ }
2092
+ if (t.isIdentifier(expression)) {
2093
+ return expression.name === paramName ? item : undefined;
2094
+ }
2095
+ if (t.isMemberExpression(expression)) {
2096
+ const normalized = normalizeParamMemberExpression(expression, paramName);
2097
+ return normalized ? followStaticPath(item, normalized) : undefined;
2098
+ }
2099
+ if (t.isUnaryExpression(expression) && expression.operator === "!") {
2100
+ const argument = evaluateStaticFilterExpression(expression.argument, paramName, item);
2101
+ return argument === undefined ? undefined : !argument;
2102
+ }
2103
+ if (t.isLogicalExpression(expression)) {
2104
+ const left = evaluateStaticFilterExpression(expression.left, paramName, item);
2105
+ if (left === undefined) {
2106
+ return undefined;
2107
+ }
2108
+ if (expression.operator === "&&") {
2109
+ return left
2110
+ ? evaluateStaticFilterExpression(expression.right, paramName, item)
2111
+ : left;
2112
+ }
2113
+ if (expression.operator === "||") {
2114
+ return left
2115
+ ? left
2116
+ : evaluateStaticFilterExpression(expression.right, paramName, item);
2117
+ }
2118
+ }
2119
+ if (t.isBinaryExpression(expression)) {
2120
+ const left = t.isExpression(expression.left)
2121
+ ? evaluateStaticFilterExpression(expression.left, paramName, item)
2122
+ : undefined;
2123
+ const right = t.isExpression(expression.right)
2124
+ ? evaluateStaticFilterExpression(expression.right, paramName, item)
2125
+ : undefined;
2126
+ if (left === undefined || right === undefined) {
2127
+ return undefined;
2128
+ }
2129
+ switch (expression.operator) {
2130
+ case "===":
2131
+ return left === right;
2132
+ case "!==":
2133
+ return left !== right;
2134
+ case "==":
2135
+ return left == right;
2136
+ case "!=":
2137
+ return left != right;
2138
+ default:
2139
+ return undefined;
2140
+ }
2141
+ }
2142
+ return undefined;
2143
+ }
2144
+ function normalizeParamMemberExpression(expression, paramName) {
2145
+ const path = [];
2146
+ let current = expression;
2147
+ while (t.isMemberExpression(current)) {
2148
+ if (current.computed) {
2149
+ if (t.isNumericLiteral(current.property)) {
2150
+ path.unshift({ kind: "index", index: current.property.value });
2151
+ }
2152
+ else if (t.isStringLiteral(current.property)) {
2153
+ path.unshift({ kind: "property", name: current.property.value });
2154
+ }
2155
+ else {
2156
+ return null;
2157
+ }
2158
+ }
2159
+ else {
2160
+ if (!t.isIdentifier(current.property)) {
2161
+ return null;
2162
+ }
2163
+ path.unshift({ kind: "property", name: current.property.name });
2164
+ }
2165
+ current = current.object;
2166
+ }
2167
+ if (!t.isIdentifier(current) || current.name !== paramName) {
2168
+ return null;
2169
+ }
2170
+ return path;
2171
+ }
2172
+ async function resolveBindingTarget(params) {
2173
+ const { currentFile, binding, readFile, exists, fileCache, visited } = params;
2174
+ const key = `${currentFile}::${binding.kind}::${binding.identifier.name}`;
2175
+ if (visited.has(key)) {
2176
+ return null;
2177
+ }
2178
+ visited.add(key);
2179
+ try {
2180
+ const hookStateBinding = await resolveHookStateBinding({
2181
+ currentFile,
2182
+ binding,
2183
+ readFile,
2184
+ exists,
2185
+ fileCache,
2186
+ visited,
2187
+ });
2188
+ if (hookStateBinding) {
2189
+ return hookStateBinding;
2190
+ }
2191
+ if (binding.path.isImportSpecifier() ||
2192
+ binding.path.isImportDefaultSpecifier()) {
2193
+ return await resolveImportedBinding({
2194
+ currentFile,
2195
+ binding,
2196
+ readFile,
2197
+ exists,
2198
+ graph: params.graph,
2199
+ externalSourceResolvers: params.externalSourceResolvers,
2200
+ externalAdapters: params.externalAdapters,
2201
+ fileCache,
2202
+ visited,
2203
+ });
2204
+ }
2205
+ if (binding.kind === "param") {
2206
+ return null;
2207
+ }
2208
+ if (binding.path.isVariableDeclarator()) {
2209
+ const init = binding.path.node.init;
2210
+ if (!init || !t.isExpression(init)) {
2211
+ return null;
2212
+ }
2213
+ const initPath = binding.path.get("init");
2214
+ if (initPath && !Array.isArray(initPath) && initPath.isExpression()) {
2215
+ const external = await resolveExternalExpression({
2216
+ currentFile,
2217
+ expressionPath: initPath,
2218
+ readFile,
2219
+ exists,
2220
+ graph: params.graph,
2221
+ externalSourceResolvers: params.externalSourceResolvers,
2222
+ externalAdapters: params.externalAdapters,
2223
+ functionCallContracts: params.functionCallContracts,
2224
+ fileCache,
2225
+ visited,
2226
+ });
2227
+ if (external) {
2228
+ return external;
2229
+ }
2230
+ if (initPath.isCallExpression()) {
2231
+ const filtered = await resolveCollectionCallTarget({
2232
+ currentFile,
2233
+ callPath: initPath,
2234
+ readFile,
2235
+ exists,
2236
+ graph: params.graph,
2237
+ externalSourceResolvers: params.externalSourceResolvers,
2238
+ externalAdapters: params.externalAdapters,
2239
+ functionCallContracts: params.functionCallContracts,
2240
+ fileCache,
2241
+ visited,
2242
+ });
2243
+ if (filtered) {
2244
+ return filtered;
2245
+ }
2246
+ }
2247
+ }
2248
+ const value = await evaluateStaticExpression(init, binding.path.scope, currentFile, readFile, exists, fileCache, visited);
2249
+ if (value === undefined) {
2250
+ return null;
2251
+ }
2252
+ return {
2253
+ kind: "static",
2254
+ file: currentFile,
2255
+ root: { kind: "binding", name: binding.identifier.name },
2256
+ value,
2257
+ sourceKind: "binding",
2258
+ };
2259
+ }
2260
+ if (binding.path.isIdentifier() &&
2261
+ binding.path.parentPath?.isExportDefaultDeclaration()) {
2262
+ const declaration = binding.path.parentPath.node.declaration;
2263
+ if (!t.isExpression(declaration)) {
2264
+ return null;
2265
+ }
2266
+ const value = await evaluateStaticExpression(declaration, binding.path.scope, currentFile, readFile, exists, fileCache, visited);
2267
+ if (value === undefined) {
2268
+ return null;
2269
+ }
2270
+ return {
2271
+ kind: "static",
2272
+ file: currentFile,
2273
+ root: { kind: "binding", name: binding.identifier.name },
2274
+ value,
2275
+ sourceKind: "binding",
2276
+ };
2277
+ }
2278
+ return null;
2279
+ }
2280
+ finally {
2281
+ visited.delete(key);
2282
+ }
2283
+ }
2284
+ async function resolveHookStateBinding(params) {
2285
+ const { currentFile, binding, readFile, exists, fileCache, visited } = params;
2286
+ if (!binding.path.isVariableDeclarator() ||
2287
+ !binding.path.get("id").isArrayPattern()) {
2288
+ return null;
2289
+ }
2290
+ const declarator = binding.path;
2291
+ const init = declarator.node.init;
2292
+ if (!init || !t.isCallExpression(init)) {
2293
+ return null;
2294
+ }
2295
+ const hookName = getHookCallName(init);
2296
+ if (!hookName) {
2297
+ return null;
2298
+ }
2299
+ if (!t.isArrayPattern(declarator.node.id)) {
2300
+ return null;
2301
+ }
2302
+ const stateIndex = declarator.node.id.elements.findIndex((element) => t.isIdentifier(element) && element.name === binding.identifier.name);
2303
+ if (stateIndex !== 0) {
2304
+ return null;
2305
+ }
2306
+ const argIndex = hookName === "useReducer" ? 1 : 0;
2307
+ const initialArg = init.arguments[argIndex];
2308
+ if (!initialArg || !t.isExpression(initialArg)) {
2309
+ return null;
2310
+ }
2311
+ const value = await evaluateStaticExpression(initialArg, declarator.scope, currentFile, readFile, exists, fileCache, visited);
2312
+ if (value === undefined) {
2313
+ return null;
2314
+ }
2315
+ return {
2316
+ kind: "static",
2317
+ file: currentFile,
2318
+ root: { kind: "hook-state", name: binding.identifier.name, hook: hookName },
2319
+ value,
2320
+ sourceKind: "binding",
2321
+ };
2322
+ }
2323
+ function getHookCallName(call) {
2324
+ if (t.isIdentifier(call.callee) &&
2325
+ (call.callee.name === "useState" || call.callee.name === "useReducer")) {
2326
+ return call.callee.name;
2327
+ }
2328
+ if (t.isMemberExpression(call.callee) &&
2329
+ t.isIdentifier(call.callee.property) &&
2330
+ (call.callee.property.name === "useState" ||
2331
+ call.callee.property.name === "useReducer")) {
2332
+ return call.callee.property.name;
2333
+ }
2334
+ return null;
2335
+ }
2336
+ function findEnclosingComponentInfo(startPath) {
2337
+ const functionPath = startPath.getFunctionParent();
2338
+ if (!functionPath) {
2339
+ return null;
2340
+ }
2341
+ if (functionPath.isFunctionDeclaration()) {
2342
+ const componentName = functionPath.node.id?.name;
2343
+ if (!componentName) {
2344
+ return null;
2345
+ }
2346
+ const exportName = functionPath.parentPath?.isExportDefaultDeclaration()
2347
+ ? "default"
2348
+ : functionPath.parentPath?.isExportNamedDeclaration()
2349
+ ? componentName
2350
+ : componentName;
2351
+ return { componentName, exportName };
2352
+ }
2353
+ if (!functionPath.isArrowFunctionExpression() &&
2354
+ !functionPath.isFunctionExpression()) {
2355
+ return null;
2356
+ }
2357
+ const declaratorPath = functionPath.parentPath;
2358
+ if (!declaratorPath?.isVariableDeclarator() ||
2359
+ !t.isIdentifier(declaratorPath.node.id)) {
2360
+ return null;
2361
+ }
2362
+ const componentName = declaratorPath.node.id.name;
2363
+ const declarationPath = declaratorPath.parentPath;
2364
+ const exportName = declarationPath?.parentPath?.isExportDefaultDeclaration()
2365
+ ? "default"
2366
+ : declarationPath?.parentPath?.isExportNamedDeclaration()
2367
+ ? componentName
2368
+ : componentName;
2369
+ return { componentName, exportName };
2370
+ }
2371
+ function getParamPropReference(binding, sourceInfo) {
2372
+ const component = findEnclosingComponentInfo(binding.path);
2373
+ if (!component) {
2374
+ return null;
2375
+ }
2376
+ const reference = getBindingParamPropReference(binding, sourceInfo);
2377
+ return reference ? { ...component, ...reference } : null;
2378
+ }
2379
+ function appendExpressionPath(baseExpression, path) {
2380
+ return path.reduce((expression, segment) => segment.kind === "property"
2381
+ ? `${expression}.${segment.name}`
2382
+ : `${expression}[${segment.index}]`, baseExpression);
2383
+ }
2384
+ function matchesComponentCallSite(element, currentFile, componentName, exportName) {
2385
+ if (element.importedFrom === currentFile) {
2386
+ if (!exportName) {
2387
+ return true;
2388
+ }
2389
+ return element.exportName === exportName;
2390
+ }
2391
+ return element.sourceFile === currentFile && element.tag === componentName;
2392
+ }
2393
+ async function resolveComponentPropBinding(params) {
2394
+ const propReference = getParamPropReference(params.binding, params.sourceInfo);
2395
+ if (!propReference || !params.graph) {
2396
+ return null;
2397
+ }
2398
+ const callers = [...params.graph.elements.values()].filter((element) => matchesComponentCallSite(element, params.currentFile, propReference.componentName, propReference.exportName));
2399
+ if (callers.length !== 1) {
2400
+ return null;
2401
+ }
2402
+ const caller = callers[0];
2403
+ const attribute = caller.attributes?.find((item) => item.name === propReference.propName);
2404
+ if (attribute && !attribute.source) {
2405
+ const staticAttributeProvenance = await resolveStaticComponentAttributeBinding({
2406
+ caller,
2407
+ propName: propReference.propName,
2408
+ remainingPath: propReference.remainingPath,
2409
+ readFile: params.readFile,
2410
+ fileCache: params.fileCache,
2411
+ });
2412
+ if (staticAttributeProvenance) {
2413
+ return addComponentPropChain(staticAttributeProvenance, {
2414
+ componentName: propReference.componentName,
2415
+ propName: propReference.propName,
2416
+ file: caller.sourceFile,
2417
+ });
2418
+ }
2419
+ }
2420
+ if (!attribute?.source) {
2421
+ const spreadProvenance = await resolveSpreadComponentPropBinding({
2422
+ caller,
2423
+ propName: propReference.propName,
2424
+ remainingPath: propReference.remainingPath,
2425
+ readFile: params.readFile,
2426
+ exists: params.exists,
2427
+ graph: params.graph,
2428
+ externalSourceResolvers: params.externalSourceResolvers,
2429
+ externalAdapters: params.externalAdapters,
2430
+ functionCallContracts: params.functionCallContracts,
2431
+ fileCache: params.fileCache,
2432
+ visited: params.visited,
2433
+ });
2434
+ if (spreadProvenance) {
2435
+ return addComponentPropChain(spreadProvenance, {
2436
+ componentName: propReference.componentName,
2437
+ propName: propReference.propName,
2438
+ file: caller.sourceFile,
2439
+ });
2440
+ }
2441
+ const defaultBinding = findPropDefaultBinding(params.binding, propReference.componentName, propReference.propName);
2442
+ if (!defaultBinding || defaultBinding.propName !== propReference.propName) {
2443
+ return null;
2444
+ }
2445
+ const value = await evaluateStaticExpression(defaultBinding.expressionPath.node, defaultBinding.expressionPath.scope, params.currentFile, params.readFile, params.exists, params.fileCache, params.visited);
2446
+ if (value === undefined) {
2447
+ return null;
2448
+ }
2449
+ const fullPath = propReference.remainingPath;
2450
+ if (fullPath.length > 0 &&
2451
+ followStaticPath(value, fullPath) === undefined) {
2452
+ return null;
2453
+ }
2454
+ return {
2455
+ kind: "value",
2456
+ file: params.currentFile,
2457
+ root: { kind: "binding", name: propReference.propName },
2458
+ path: fullPath,
2459
+ sourceKind: "binding",
2460
+ semanticKind: "component-prop",
2461
+ displayExpression: defaultBinding.displayPath,
2462
+ chainPrefix: [
2463
+ {
2464
+ kind: "component-prop",
2465
+ file: params.currentFile,
2466
+ displayName: defaultBinding.displayPath,
2467
+ canEditHere: true,
2468
+ },
2469
+ ],
2470
+ editMode: "upstream",
2471
+ diagnostics: [],
2472
+ };
2473
+ }
2474
+ const loadedCaller = await loadFile(caller.sourceFile, params.readFile, params.fileCache);
2475
+ if (!loadedCaller.ast) {
2476
+ return null;
2477
+ }
2478
+ const callerExpressionPath = findExpressionPathByTextInRange(loadedCaller.ast, loadedCaller.source, attribute.source.expression, attribute.sourceRange) ??
2479
+ findExpressionPathByText(loadedCaller.ast, loadedCaller.source, attribute.source.expression);
2480
+ if (!callerExpressionPath) {
2481
+ return null;
2482
+ }
2483
+ const upstreamSourceInfo = {
2484
+ ...attribute.source,
2485
+ expression: appendExpressionPath(attribute.source.expression, propReference.remainingPath),
2486
+ path: [...attribute.source.path, ...propReference.remainingPath],
2487
+ };
2488
+ const upstream = await resolveSourceInfoProvenance({
2489
+ currentFile: caller.sourceFile,
2490
+ expressionPath: callerExpressionPath,
2491
+ sourceInfo: upstreamSourceInfo,
2492
+ readFile: params.readFile,
2493
+ exists: params.exists,
2494
+ graph: params.graph,
2495
+ externalSourceResolvers: params.externalSourceResolvers,
2496
+ externalAdapters: params.externalAdapters,
2497
+ functionCallContracts: params.functionCallContracts,
2498
+ fileCache: params.fileCache,
2499
+ visited: params.visited,
2500
+ });
2501
+ if (!upstream) {
2502
+ return null;
2503
+ }
2504
+ return addComponentPropChain(upstream.kind === "external"
2505
+ ? upstream
2506
+ : {
2507
+ ...upstream,
2508
+ displayExpression: upstreamSourceInfo.expression,
2509
+ }, {
2510
+ componentName: propReference.componentName,
2511
+ propName: propReference.propName,
2512
+ file: caller.sourceFile,
2513
+ });
2514
+ }
2515
+ async function resolveStaticComponentAttributeBinding(params) {
2516
+ const loadedCaller = await loadFile(params.caller.sourceFile, params.readFile, params.fileCache);
2517
+ if (!loadedCaller.ast) {
2518
+ return null;
2519
+ }
2520
+ const elementPath = findJSXElementPathByRange(loadedCaller.ast, params.caller.sourceRange);
2521
+ if (!elementPath) {
2522
+ return null;
2523
+ }
2524
+ const attributePath = elementPath
2525
+ .get("openingElement")
2526
+ .get("attributes")
2527
+ .find((candidate) => candidate.isJSXAttribute() &&
2528
+ candidate.node.name.type === "JSXIdentifier" &&
2529
+ candidate.node.name.name === params.propName);
2530
+ if (!attributePath?.isJSXAttribute()) {
2531
+ return null;
2532
+ }
2533
+ const valuePath = attributePath.get("value");
2534
+ const rootExpressionPath = valuePath.isStringLiteral()
2535
+ ? valuePath
2536
+ : valuePath.isJSXExpressionContainer()
2537
+ ? valuePath.get("expression")
2538
+ : null;
2539
+ if (!rootExpressionPath?.isExpression()) {
2540
+ return null;
2541
+ }
2542
+ const sourceRange = toValueSourceRange(rootExpressionPath.node);
2543
+ if (!sourceRange) {
2544
+ return null;
2545
+ }
2546
+ return {
2547
+ kind: "value",
2548
+ file: params.caller.sourceFile,
2549
+ root: { kind: "expression", sourceRange },
2550
+ path: params.remainingPath,
2551
+ sourceKind: "binding",
2552
+ semanticKind: "component-prop",
2553
+ displayExpression: params.propName,
2554
+ editMode: "upstream",
2555
+ diagnostics: [],
2556
+ };
2557
+ }
2558
+ async function resolveSpreadComponentPropBinding(params) {
2559
+ const loadedCaller = await loadFile(params.caller.sourceFile, params.readFile, params.fileCache);
2560
+ if (!loadedCaller.ast) {
2561
+ return null;
2562
+ }
2563
+ const elementPath = findJSXElementPathByRange(loadedCaller.ast, params.caller.sourceRange);
2564
+ if (!elementPath) {
2565
+ return null;
2566
+ }
2567
+ const openingPath = elementPath.get("openingElement");
2568
+ for (const attributePath of openingPath.get("attributes")) {
2569
+ if (!attributePath.isJSXSpreadAttribute()) {
2570
+ continue;
2571
+ }
2572
+ const argumentPath = attributePath.get("argument");
2573
+ if (!argumentPath.isExpression()) {
2574
+ continue;
2575
+ }
2576
+ const spreadSourceInfo = await buildExpressionSourceInfo({
2577
+ expression: argumentPath.node,
2578
+ scope: argumentPath.scope,
2579
+ source: loadedCaller.source,
2580
+ currentFile: params.caller.sourceFile,
2581
+ readFile: params.readFile,
2582
+ exists: params.exists,
2583
+ fileCache: params.fileCache,
2584
+ visited: params.visited,
2585
+ });
2586
+ if (!spreadSourceInfo) {
2587
+ continue;
2588
+ }
2589
+ const propPath = [
2590
+ { kind: "property", name: params.propName },
2591
+ ...params.remainingPath,
2592
+ ];
2593
+ const upstreamSourceInfo = {
2594
+ ...spreadSourceInfo,
2595
+ expression: appendExpressionPath(spreadSourceInfo.expression, propPath),
2596
+ path: [...spreadSourceInfo.path, ...propPath],
2597
+ };
2598
+ const upstream = await resolveSourceInfoProvenance({
2599
+ currentFile: params.caller.sourceFile,
2600
+ expressionPath: argumentPath,
2601
+ sourceInfo: upstreamSourceInfo,
2602
+ readFile: params.readFile,
2603
+ exists: params.exists,
2604
+ graph: params.graph,
2605
+ externalSourceResolvers: params.externalSourceResolvers,
2606
+ externalAdapters: params.externalAdapters,
2607
+ functionCallContracts: params.functionCallContracts,
2608
+ fileCache: params.fileCache,
2609
+ visited: params.visited,
2610
+ });
2611
+ if (!upstream) {
2612
+ continue;
2613
+ }
2614
+ return upstream.kind === "external"
2615
+ ? upstream
2616
+ : {
2617
+ ...upstream,
2618
+ displayExpression: upstreamSourceInfo.expression,
2619
+ };
2620
+ }
2621
+ return null;
2622
+ }
2623
+ function addComponentPropChain(upstream, params) {
2624
+ const chainPrefix = [
2625
+ {
2626
+ kind: "component-prop",
2627
+ file: params.file,
2628
+ displayName: `${params.componentName}.${params.propName}`,
2629
+ canEditHere: true,
2630
+ },
2631
+ ];
2632
+ return upstream.kind === "external"
2633
+ ? {
2634
+ ...upstream,
2635
+ chainPrefix: [...chainPrefix, ...(upstream.chainPrefix ?? [])],
2636
+ editMode: upstream.editMode === "readonly" ? "readonly" : "upstream",
2637
+ }
2638
+ : {
2639
+ ...upstream,
2640
+ chainPrefix: [...chainPrefix, ...(upstream.chainPrefix ?? [])],
2641
+ editMode: "upstream",
2642
+ };
2643
+ }
2644
+ async function resolveImportedBinding(params) {
2645
+ const { currentFile, binding, readFile, exists, fileCache, visited } = params;
2646
+ const importDeclaration = binding.path.parentPath;
2647
+ if (!importDeclaration || !importDeclaration.isImportDeclaration()) {
2648
+ return null;
2649
+ }
2650
+ const specifier = importDeclaration.node.source.value;
2651
+ if (typeof specifier !== "string") {
2652
+ return null;
2653
+ }
2654
+ const resolvedFile = await resolveLocalImport(currentFile, specifier, exists);
2655
+ if (!resolvedFile) {
2656
+ return null;
2657
+ }
2658
+ if (binding.path.isImportDefaultSpecifier()) {
2659
+ const resolved = await resolveExportValue({
2660
+ file: resolvedFile,
2661
+ exportName: "default",
2662
+ readFile,
2663
+ exists,
2664
+ graph: params.graph,
2665
+ externalSourceResolvers: params.externalSourceResolvers,
2666
+ externalAdapters: params.externalAdapters,
2667
+ functionCallContracts: params.functionCallContracts,
2668
+ fileCache,
2669
+ visited,
2670
+ });
2671
+ if (!resolved || resolved.kind !== "static") {
2672
+ return null;
2673
+ }
2674
+ return {
2675
+ kind: "static",
2676
+ file: resolvedFile,
2677
+ root: { kind: "default-export" },
2678
+ value: resolved.value,
2679
+ sourceKind: "imported-binding",
2680
+ };
2681
+ }
2682
+ if (!binding.path.isImportSpecifier()) {
2683
+ return null;
2684
+ }
2685
+ const imported = binding.path.node.imported;
2686
+ const importedName = t.isIdentifier(imported)
2687
+ ? imported.name
2688
+ : imported.value;
2689
+ const resolved = await resolveExportValue({
2690
+ file: resolvedFile,
2691
+ exportName: importedName,
2692
+ readFile,
2693
+ exists,
2694
+ graph: params.graph,
2695
+ externalSourceResolvers: params.externalSourceResolvers,
2696
+ externalAdapters: params.externalAdapters,
2697
+ functionCallContracts: params.functionCallContracts,
2698
+ fileCache,
2699
+ visited,
2700
+ });
2701
+ if (!resolved) {
2702
+ return null;
2703
+ }
2704
+ if (resolved.kind === "external") {
2705
+ return resolved;
2706
+ }
2707
+ return {
2708
+ kind: "static",
2709
+ file: resolvedFile,
2710
+ root: { kind: "named-export", name: importedName },
2711
+ value: resolved.value,
2712
+ sourceKind: "imported-binding",
2713
+ };
2714
+ }
2715
+ async function resolveExportValue(params) {
2716
+ const { file, exportName, readFile, exists, fileCache, visited } = params;
2717
+ const loaded = await loadFile(file, readFile, fileCache);
2718
+ if (!loaded.ast) {
2719
+ return undefined;
2720
+ }
2721
+ const exportEntry = getExportEntry(loaded.ast, exportName);
2722
+ if (!exportEntry) {
2723
+ return undefined;
2724
+ }
2725
+ if (exportEntry.kind === "default-expression") {
2726
+ return undefined;
2727
+ }
2728
+ const bindingName = exportEntry.name;
2729
+ if (!bindingName) {
2730
+ return undefined;
2731
+ }
2732
+ const binding = getProgramScope(loaded.ast).getBinding(bindingName);
2733
+ if (!binding) {
2734
+ return undefined;
2735
+ }
2736
+ const resolved = await resolveBindingTarget({
2737
+ currentFile: file,
2738
+ binding,
2739
+ readFile,
2740
+ exists,
2741
+ graph: params.graph,
2742
+ externalSourceResolvers: params.externalSourceResolvers,
2743
+ externalAdapters: params.externalAdapters,
2744
+ functionCallContracts: params.functionCallContracts,
2745
+ fileCache,
2746
+ visited,
2747
+ });
2748
+ return resolved ?? undefined;
2749
+ }
2750
+ function getProgramScope(ast) {
2751
+ let scope = null;
2752
+ traverse(ast, {
2753
+ Program(path) {
2754
+ scope = path.scope;
2755
+ path.stop();
2756
+ },
2757
+ });
2758
+ if (!scope) {
2759
+ throw new Error("Unable to determine program scope");
2760
+ }
2761
+ return scope;
2762
+ }
2763
+ function getExportEntry(ast, exportName) {
2764
+ for (const statement of ast.program.body) {
2765
+ if (exportName === "default" && t.isExportDefaultDeclaration(statement)) {
2766
+ if (t.isExpression(statement.declaration)) {
2767
+ return {
2768
+ kind: "default-expression",
2769
+ expression: statement.declaration,
2770
+ };
2771
+ }
2772
+ if (t.isFunctionDeclaration(statement.declaration) ||
2773
+ t.isClassDeclaration(statement.declaration)) {
2774
+ if (statement.declaration.id) {
2775
+ return {
2776
+ kind: "default-binding",
2777
+ name: statement.declaration.id.name,
2778
+ };
2779
+ }
2780
+ return null;
2781
+ }
2782
+ return null;
2783
+ }
2784
+ if (!t.isExportNamedDeclaration(statement)) {
2785
+ continue;
2786
+ }
2787
+ if (statement.declaration) {
2788
+ if (t.isVariableDeclaration(statement.declaration)) {
2789
+ for (const declarator of statement.declaration.declarations) {
2790
+ if (t.isIdentifier(declarator.id) &&
2791
+ declarator.id.name === exportName) {
2792
+ return { kind: "local-binding", name: declarator.id.name };
2793
+ }
2794
+ }
2795
+ }
2796
+ if ((t.isFunctionDeclaration(statement.declaration) ||
2797
+ t.isClassDeclaration(statement.declaration)) &&
2798
+ statement.declaration.id &&
2799
+ statement.declaration.id.name === exportName) {
2800
+ return { kind: "local-binding", name: exportName };
2801
+ }
2802
+ }
2803
+ for (const specifier of statement.specifiers) {
2804
+ const exportedName = t.isIdentifier(specifier.exported)
2805
+ ? specifier.exported.name
2806
+ : specifier.exported.value;
2807
+ if (exportedName === exportName && t.isExportSpecifier(specifier)) {
2808
+ return { kind: "local-binding", name: specifier.local.name };
2809
+ }
2810
+ }
2811
+ }
2812
+ return null;
2813
+ }
2814
+ async function evaluateStaticExpression(expr, scope, currentFile, readFile, exists, fileCache, visited) {
2815
+ if (t.isStringLiteral(expr) ||
2816
+ t.isNumericLiteral(expr) ||
2817
+ t.isBooleanLiteral(expr)) {
2818
+ return expr.value;
2819
+ }
2820
+ if (t.isNullLiteral(expr)) {
2821
+ return null;
2822
+ }
2823
+ if (t.isTemplateLiteral(expr)) {
2824
+ if (expr.expressions.length === 0 && expr.quasis.length === 1) {
2825
+ return expr.quasis[0].value.cooked ?? expr.quasis[0].value.raw;
2826
+ }
2827
+ return undefined;
2828
+ }
2829
+ if (t.isTSAsExpression(expr) ||
2830
+ t.isTSSatisfiesExpression(expr) ||
2831
+ t.isTSNonNullExpression(expr)) {
2832
+ return await evaluateStaticExpression(expr.expression, scope, currentFile, readFile, exists, fileCache, visited);
2833
+ }
2834
+ if (t.isIdentifier(expr)) {
2835
+ const binding = scope.getBinding(expr.name);
2836
+ if (!binding) {
2837
+ return undefined;
2838
+ }
2839
+ const resolved = await resolveBindingTarget({
2840
+ currentFile,
2841
+ binding,
2842
+ readFile,
2843
+ exists,
2844
+ fileCache,
2845
+ visited,
2846
+ });
2847
+ return resolved?.kind === "static" ? resolved.value : undefined;
2848
+ }
2849
+ if (t.isArrayExpression(expr)) {
2850
+ const values = [];
2851
+ for (const element of expr.elements) {
2852
+ if (!element || t.isSpreadElement(element)) {
2853
+ return undefined;
2854
+ }
2855
+ const value = await evaluateExpressionLike(element, scope, currentFile, readFile, exists, fileCache, visited);
2856
+ if (value === undefined) {
2857
+ return undefined;
2858
+ }
2859
+ values.push(value);
2860
+ }
2861
+ return values;
2862
+ }
2863
+ if (t.isObjectExpression(expr)) {
2864
+ const result = {};
2865
+ for (const property of expr.properties) {
2866
+ if (!t.isObjectProperty(property) || property.computed) {
2867
+ return undefined;
2868
+ }
2869
+ let keyName = null;
2870
+ if (t.isIdentifier(property.key)) {
2871
+ keyName = property.key.name;
2872
+ }
2873
+ else if (t.isStringLiteral(property.key)) {
2874
+ keyName = property.key.value;
2875
+ }
2876
+ if (!keyName) {
2877
+ return undefined;
2878
+ }
2879
+ if (property.shorthand) {
2880
+ if (!t.isIdentifier(property.value)) {
2881
+ return undefined;
2882
+ }
2883
+ const shorthand = await evaluateExpressionLike(property.value, scope, currentFile, readFile, exists, fileCache, visited);
2884
+ if (shorthand === undefined) {
2885
+ return undefined;
2886
+ }
2887
+ result[keyName] = shorthand;
2888
+ continue;
2889
+ }
2890
+ if (!t.isExpression(property.value)) {
2891
+ return undefined;
2892
+ }
2893
+ const value = await evaluateExpressionLike(property.value, scope, currentFile, readFile, exists, fileCache, visited);
2894
+ if (value === undefined) {
2895
+ continue;
2896
+ }
2897
+ result[keyName] = value;
2898
+ }
2899
+ return result;
2900
+ }
2901
+ if (t.isMemberExpression(expr)) {
2902
+ const normalized = await normalizeExpressionWithScope(expr, scope, currentFile, readFile, exists, fileCache, visited);
2903
+ if (!normalized) {
2904
+ return undefined;
2905
+ }
2906
+ const baseBinding = scope.getBinding(normalized.base);
2907
+ if (!baseBinding) {
2908
+ return undefined;
2909
+ }
2910
+ const baseValue = await resolveBindingTarget({
2911
+ currentFile,
2912
+ binding: baseBinding,
2913
+ readFile,
2914
+ exists,
2915
+ fileCache,
2916
+ visited,
2917
+ });
2918
+ if (!baseValue || baseValue.kind !== "static") {
2919
+ return undefined;
2920
+ }
2921
+ return followStaticPath(baseValue.value, normalized.path);
2922
+ }
2923
+ return undefined;
2924
+ }
2925
+ async function evaluateExpressionLike(node, scope, currentFile, readFile, exists, fileCache, visited) {
2926
+ if (t.isSpreadElement(node)) {
2927
+ return undefined;
2928
+ }
2929
+ return await evaluateStaticExpression(node, scope, currentFile, readFile, exists, fileCache, visited);
2930
+ }
2931
+ async function normalizeExpressionWithScope(expr, scope, currentFile, readFile, exists, fileCache, visited) {
2932
+ if (t.isIdentifier(expr)) {
2933
+ return {
2934
+ base: expr.name,
2935
+ path: [],
2936
+ };
2937
+ }
2938
+ if (!t.isMemberExpression(expr)) {
2939
+ return null;
2940
+ }
2941
+ const segments = [];
2942
+ let current = expr;
2943
+ while (t.isMemberExpression(current)) {
2944
+ if (current.computed) {
2945
+ const property = current.property;
2946
+ if (t.isNumericLiteral(property)) {
2947
+ segments.unshift({ kind: "index", index: property.value });
2948
+ }
2949
+ else if (t.isStringLiteral(property)) {
2950
+ segments.unshift({ kind: "property", name: property.value });
2951
+ }
2952
+ else {
2953
+ const computedValue = t.isExpression(property)
2954
+ ? await evaluateStaticExpression(property, scope, currentFile, readFile, exists, fileCache, visited)
2955
+ : undefined;
2956
+ if (typeof computedValue === "string") {
2957
+ segments.unshift({ kind: "property", name: computedValue });
2958
+ }
2959
+ else if (Number.isInteger(computedValue)) {
2960
+ segments.unshift({ kind: "index", index: computedValue });
2961
+ }
2962
+ else {
2963
+ return null;
2964
+ }
2965
+ }
2966
+ }
2967
+ else {
2968
+ if (!t.isIdentifier(current.property)) {
2969
+ return null;
2970
+ }
2971
+ segments.unshift({ kind: "property", name: current.property.name });
2972
+ }
2973
+ current = current.object;
2974
+ }
2975
+ if (!t.isIdentifier(current)) {
2976
+ return null;
2977
+ }
2978
+ return {
2979
+ base: current.name,
2980
+ path: segments,
2981
+ };
2982
+ }
2983
+ function normalizeExpression(expr) {
2984
+ if (t.isIdentifier(expr)) {
2985
+ return {
2986
+ base: expr.name,
2987
+ path: [],
2988
+ };
2989
+ }
2990
+ if (!t.isMemberExpression(expr)) {
2991
+ return null;
2992
+ }
2993
+ const segments = [];
2994
+ let current = expr;
2995
+ while (t.isMemberExpression(current)) {
2996
+ if (current.computed) {
2997
+ if (t.isNumericLiteral(current.property)) {
2998
+ segments.unshift({ kind: "index", index: current.property.value });
2999
+ }
3000
+ else if (t.isStringLiteral(current.property)) {
3001
+ segments.unshift({ kind: "property", name: current.property.value });
3002
+ }
3003
+ else {
3004
+ return null;
3005
+ }
3006
+ }
3007
+ else {
3008
+ if (!t.isIdentifier(current.property)) {
3009
+ return null;
3010
+ }
3011
+ segments.unshift({ kind: "property", name: current.property.name });
3012
+ }
3013
+ current = current.object;
3014
+ }
3015
+ if (!t.isIdentifier(current)) {
3016
+ return null;
3017
+ }
3018
+ return {
3019
+ base: current.name,
3020
+ path: segments,
3021
+ };
3022
+ }
3023
+ function followStaticPath(value, path) {
3024
+ let current = value;
3025
+ for (const segment of path) {
3026
+ if (segment.kind === "property") {
3027
+ if (current === null ||
3028
+ Array.isArray(current) ||
3029
+ typeof current !== "object" ||
3030
+ !(segment.name in current)) {
3031
+ return undefined;
3032
+ }
3033
+ current = current[segment.name];
3034
+ continue;
3035
+ }
3036
+ if (!Array.isArray(current) ||
3037
+ segment.index < 0 ||
3038
+ segment.index >= current.length) {
3039
+ return undefined;
3040
+ }
3041
+ current = current[segment.index];
3042
+ }
3043
+ return current;
3044
+ }
3045
+ function buildRepeatItemPathMap(collectionTarget, itemPath) {
3046
+ if (!Array.isArray(collectionTarget.value)) {
3047
+ return null;
3048
+ }
3049
+ const paths = collectionTarget.collectionPathMap?.length
3050
+ ? collectionTarget.collectionPathMap
3051
+ : collectionTarget.value.map((_, index) => [
3052
+ ...(collectionTarget.pathPrefix ?? []),
3053
+ {
3054
+ kind: "index",
3055
+ index: collectionTarget.collectionIndexMap?.[index] ?? index,
3056
+ },
3057
+ ]);
3058
+ return paths.length > 0
3059
+ ? paths.map((path) => [...path, ...itemPath])
3060
+ : null;
3061
+ }
3062
+ function flattenCollectionPathMap(rootValue, collectionPathMap) {
3063
+ const value = [];
3064
+ const flattenedPathMap = [];
3065
+ for (const collectionPath of collectionPathMap) {
3066
+ const collection = followStaticPath(rootValue, collectionPath);
3067
+ if (!Array.isArray(collection)) {
3068
+ return null;
3069
+ }
3070
+ for (const [index, item] of collection.entries()) {
3071
+ value.push(item);
3072
+ flattenedPathMap.push([
3073
+ ...collectionPath,
3074
+ { kind: "index", index },
3075
+ ]);
3076
+ }
3077
+ }
3078
+ return { value, collectionPathMap: flattenedPathMap };
3079
+ }
3080
+ async function loadFile(file, readFile, fileCache) {
3081
+ const cached = fileCache.get(file);
3082
+ if (cached) {
3083
+ return cached;
3084
+ }
3085
+ const source = await readFile(file);
3086
+ const ast = parseSource(source);
3087
+ const loaded = { source, ast };
3088
+ fileCache.set(file, loaded);
3089
+ return loaded;
3090
+ }