@component-compass/reference-graph 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder.d.ts +10 -4
- package/dist/builder.js +13 -9
- package/dist/builder.js.map +1 -1
- package/dist/engine/assert-never.d.ts +1 -1
- package/dist/engine/assert-never.js +1 -1
- package/dist/engine/component-shape.d.ts +18 -7
- package/dist/engine/component-shape.js +48 -18
- package/dist/engine/component-shape.js.map +1 -1
- package/dist/engine/helper-callers.d.ts +24 -14
- package/dist/engine/helper-callers.js +43 -70
- package/dist/engine/helper-callers.js.map +1 -1
- package/dist/engine/host-element.d.ts +37 -0
- package/dist/engine/host-element.js +55 -0
- package/dist/engine/host-element.js.map +1 -0
- package/dist/engine/index.d.ts +4 -4
- package/dist/engine/index.js +181 -91
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/library-stubs.js +1 -1
- package/dist/engine/member-identity.d.ts +9 -10
- package/dist/engine/member-identity.js +9 -10
- package/dist/engine/member-identity.js.map +1 -1
- package/dist/engine/re-export-chain.d.ts +1 -1
- package/dist/engine/re-export-chain.js +4 -3
- package/dist/engine/re-export-chain.js.map +1 -1
- package/dist/engine/registry.d.ts +43 -19
- package/dist/engine/registry.js +66 -50
- package/dist/engine/registry.js.map +1 -1
- package/dist/engine/resolve-reference.js +1 -1
- package/dist/engine/resolve-reference.js.map +1 -1
- package/dist/engine/resolve-type.d.ts +12 -0
- package/dist/engine/resolve-type.js +44 -52
- package/dist/engine/resolve-type.js.map +1 -1
- package/dist/engine/wrapper-folding.d.ts +22 -3
- package/dist/engine/wrapper-folding.js +146 -132
- package/dist/engine/wrapper-folding.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/file-graph.d.ts +29 -13
- package/dist/types/inferred-type.d.ts +2 -1
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveType, resolveToFunctions, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
|
|
1
|
+
import { resolveType, resolveToFunctions, resolveDynamicImportTarget, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
|
|
2
2
|
import { argumentRefKey } from "./argument-map.js";
|
|
3
3
|
import { createCycleGuard } from "./cycle-detection.js";
|
|
4
4
|
import { fileGraphForRef, resolveReference } from "./resolve-reference.js";
|
|
@@ -7,7 +7,7 @@ import { followReExportChain } from "./re-export-chain.js";
|
|
|
7
7
|
import { unparsedMemberDefinition, effectiveExportName, residualMemberChain, compoundExportName } from "./member-identity.js";
|
|
8
8
|
import { evalKind, isImportBackedLeaf } from "./component-shape.js";
|
|
9
9
|
import { assertNever } from "./assert-never.js";
|
|
10
|
-
/**
|
|
10
|
+
/** True when a `ReturnTypeOf`'s callee is a data-method render
|
|
11
11
|
* call (`items.map(render)`) rather than a wrapper taking a component as a
|
|
12
12
|
* prop — the exact structural condition `component-shape.ts`'s rule (ii)
|
|
13
13
|
* uses to answer `"jsx"` instead of `"component"`. Shared, not re-derived:
|
|
@@ -17,7 +17,7 @@ function isDataMethodRenderCall(type, graph, fileGraph, guard) {
|
|
|
17
17
|
return type.callee.kind === "MemberOf" && !isImportBackedLeaf(type.callee, graph, fileGraph, guard);
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* THE single seam for unreducible-leaf identity synthesis
|
|
20
|
+
* THE single seam for unreducible-leaf identity synthesis.
|
|
21
21
|
*
|
|
22
22
|
* Maps `resolveType` results into FoldedTerminals. When the algebra preserved
|
|
23
23
|
* an identity-bearing leaf — an import-backed TypeOf or a DynamicImport that
|
|
@@ -40,12 +40,10 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
// A fan-out branch (Union / dynamic-map) is stamped as `source`. Derive
|
|
43
|
-
// from it FIRST — a branch that has its own binding must win
|
|
43
|
+
// from it FIRST — a branch that has its own binding must win —
|
|
44
44
|
// and only fall back to the resolved binding when the branch carries no
|
|
45
45
|
// identity of its own (e.g. a bare `{kind:"JSX"}` branch from a lowered
|
|
46
|
-
// ternary/logical render body).
|
|
47
|
-
// deriving discarded the fallback entirely once any fanout occurred, even
|
|
48
|
-
// when the branch itself resolved to nothing.
|
|
46
|
+
// ternary/logical render body).
|
|
49
47
|
const identity = (source && deriveIdentity(source, fileGraph, graph)) ?? deriveIdentity(fallback, fileGraph, graph);
|
|
50
48
|
return {
|
|
51
49
|
type: t,
|
|
@@ -60,10 +58,11 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
60
58
|
* a seen-through wrapper and an opaque one attribute identically. Inner
|
|
61
59
|
* terminals pass through with the wrapper hop prepended. When the inner walk
|
|
62
60
|
* bottomed out at Unknown but carried an identity hint (imported, or
|
|
63
|
-
* local-promoted
|
|
61
|
+
* local-promoted through the re-export chain), synthesise a JSX terminal
|
|
62
|
+
* from it — without this
|
|
64
63
|
* rescue, HOC-wrapped occurrences drop silently (~128 in example-web's
|
|
65
|
-
* connect/flow/withTranslators patterns
|
|
66
|
-
* nothing → a single Unknown terminal
|
|
64
|
+
* connect/flow/withTranslators patterns). Truly
|
|
65
|
+
* nothing → a single Unknown terminal.
|
|
67
66
|
*/
|
|
68
67
|
function foldToWrappedArg(inner, via) {
|
|
69
68
|
const innerHasNonUnknown = inner.some((t) => t.type.kind !== "Unknown");
|
|
@@ -83,7 +82,7 @@ function foldToWrappedArg(inner, via) {
|
|
|
83
82
|
* resolution. THE single helper for both via kinds that describe a wrapped
|
|
84
83
|
* argument (`hoc-wrapper` when the wrapper folds to the argument,
|
|
85
84
|
* `passed-as-argument` when the holder is its own identity and the argument
|
|
86
|
-
* is seeded at its site
|
|
85
|
+
* is seeded at its site), so the two name a binding identically.
|
|
87
86
|
* Never re-derive `{ specifier, import }` for an argument elsewhere.
|
|
88
87
|
*/
|
|
89
88
|
export function argumentProvenance(type, arg, fileGraph) {
|
|
@@ -128,10 +127,8 @@ function identityKey(id) {
|
|
|
128
127
|
function isPassThroughWrapper(algResult, inner) {
|
|
129
128
|
const innerKeys = new Set(inner.filter((t) => t.type.kind !== "Unknown" && t.identity !== null).map((t) => identityKey(t.identity)));
|
|
130
129
|
const algKeys = algResult.filter((t) => t.type.kind !== "Unknown");
|
|
131
|
-
// A null identity on either side is "no information", never a match
|
|
132
|
-
//
|
|
133
|
-
// rehydrate/config argument once the callee unwrap stopped inventing an
|
|
134
|
-
// identity for the algebra side.
|
|
130
|
+
// A null identity on either side is "no information", never a match:
|
|
131
|
+
// null-equals-null folds a factory call onto its rehydrate/config argument.
|
|
135
132
|
return algKeys.length > 0 && algKeys.every((t) => t.identity !== null && innerKeys.has(identityKey(t.identity)));
|
|
136
133
|
}
|
|
137
134
|
/**
|
|
@@ -143,21 +140,18 @@ function isPassThroughWrapper(algResult, inner) {
|
|
|
143
140
|
* For nodes where no folding rule applies, delegates to `resolveType`
|
|
144
141
|
* (pure algebra) and computes identity from the input type's leaf TypeOf
|
|
145
142
|
* when one can be confidently identified.
|
|
146
|
-
*
|
|
147
|
-
* See spec: docs/superpowers/specs/2026-05-19-wrapper-folding-engine-extension-design.md §4.3
|
|
148
143
|
*/
|
|
149
144
|
export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(),
|
|
150
|
-
//
|
|
145
|
+
// The identity `foldTerminals` falls back to when a terminal
|
|
151
146
|
// carries no identity of its own (no `source`, no unreducible TypeOf/
|
|
152
|
-
// DynamicImport leaf). Defaults to `type` (the walked node itself)
|
|
153
|
-
// unchanged behaviour for every caller that doesn't pass this. The
|
|
147
|
+
// DynamicImport leaf). Defaults to `type` (the walked node itself). The
|
|
154
148
|
// `Function` arm below is the ONE place that overrides it: a component's
|
|
155
149
|
// own return (`return priceElement;`, `return contactModals;` — a bare
|
|
156
150
|
// JSX-value alias or a hook-return leaf) must keep deriving identity as
|
|
157
151
|
// "nothing", so the engine's own fallback (the rendered declaration, e.g.
|
|
158
152
|
// `OfferPrice`) wins — never a same-file local alias like `priceElement`
|
|
159
153
|
// that happens to sit in `foldTerminals`' fallback slot. Per-branch
|
|
160
|
-
// identity from a fanout `source` (
|
|
154
|
+
// identity from a fanout `source` (Union([TypeOf A, TypeOf B])) is
|
|
161
155
|
// untouched: `source` is checked BEFORE this fallback in `foldTerminals`.
|
|
162
156
|
identityFallback) {
|
|
163
157
|
const fallback = identityFallback ?? type;
|
|
@@ -170,26 +164,25 @@ identityFallback) {
|
|
|
170
164
|
}
|
|
171
165
|
return walkStaticMember(graph, fileGraph, type, argMap, guard, fallback);
|
|
172
166
|
case "Function":
|
|
173
|
-
//
|
|
167
|
+
// A component's own returns can contain a nested
|
|
174
168
|
// ReturnTypeOf/MemberOf (`List = ({items}) => items.map(render)`) that
|
|
175
169
|
// needs wrapper-folding's steps (the opaque-callee / dynamic-map
|
|
176
170
|
// handling below), not `resolveType`'s plain algebra — `resolveType`'s
|
|
177
171
|
// own "Function" case recurses via plain `resolveType`, which never
|
|
178
172
|
// re-enters this walk, so a JSX usage resolving to such a Function
|
|
179
|
-
// (e.g. `<List/>`) silently
|
|
180
|
-
// Mirrors `resolveType`'s "Function" case, one level up.
|
|
181
|
-
//
|
|
173
|
+
// (e.g. `<List/>`) would silently produce zero occurrences.
|
|
174
|
+
// Mirrors `resolveType`'s "Function" case, one level up. Pass THIS
|
|
175
|
+
// Function as the identity fallback for every return — a
|
|
182
176
|
// Function is never TypeOf/DynamicImport, so `deriveIdentity` on it
|
|
183
|
-
// is always null
|
|
177
|
+
// is always null.
|
|
184
178
|
return type.returns.flatMap((r) => walkWithFolding(graph, fileGraph, r, argMap, guard, type));
|
|
185
179
|
case "TypeOf":
|
|
186
180
|
return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback);
|
|
187
181
|
case "Union":
|
|
188
|
-
// Structural re-entry
|
|
182
|
+
// Structural re-entry: each branch walks through the walker so
|
|
189
183
|
// a TypeOf branch reaches `walkTypeOf` and decides its own identity
|
|
190
|
-
// (per-branch attribution
|
|
191
|
-
// ternary/logical render body — derives from the outer fallback
|
|
192
|
-
// as the algebra's `source ?? fallback` did.
|
|
184
|
+
// (per-branch attribution) while a bare JSX branch — a lowered
|
|
185
|
+
// ternary/logical render body — derives from the outer fallback.
|
|
193
186
|
return type.types.flatMap((branch) => walkWithFolding(graph, fileGraph, branch, argMap, guard, fallback));
|
|
194
187
|
case "ParameterOf":
|
|
195
188
|
case "DynamicImport":
|
|
@@ -214,14 +207,14 @@ function foldLeaf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
214
207
|
return foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), fallback);
|
|
215
208
|
}
|
|
216
209
|
/**
|
|
217
|
-
* THE seam for "walk a Function in its authoring file"
|
|
210
|
+
* THE seam for "walk a Function in its authoring file". A Function
|
|
218
211
|
* declared in another module must walk in THAT module's graph, because the
|
|
219
212
|
* walker's file-scoped lookups inside it — a dynamic map's binding
|
|
220
213
|
* (`findMapBinding`), an import table — only resolve where the code was
|
|
221
|
-
* written
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
214
|
+
* written: a cross-module `function getMapped(k) { return MAP[k]; }`
|
|
215
|
+
* walked in the caller's fileGraph misses `MAP` entirely and loses the
|
|
216
|
+
* `dynamic-map` hop. `walkDispatcherReturns` and `walkTypeOf` need the
|
|
217
|
+
* identical switch, so it is one function, not two.
|
|
225
218
|
* Falls back to the caller's graph when the file is absent or unindexed.
|
|
226
219
|
*/
|
|
227
220
|
function fileGraphForFunction(graph, fileGraph, fn) {
|
|
@@ -230,25 +223,58 @@ function fileGraphForFunction(graph, fileGraph, fn) {
|
|
|
230
223
|
: fileGraph;
|
|
231
224
|
}
|
|
232
225
|
/**
|
|
233
|
-
*
|
|
226
|
+
* The file a DynamicImport's specifier resolves against: the file that
|
|
227
|
+
* contains the `import()`, whichever file the walk is standing in.
|
|
228
|
+
*/
|
|
229
|
+
export function fileGraphForDynamicImport(graph, di, fallback) {
|
|
230
|
+
if (!graph || di.originFile === fallback.filePath)
|
|
231
|
+
return fallback;
|
|
232
|
+
return graph.files.get(di.originFile) ?? fallback;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The identity cascade for a lazy target.
|
|
236
|
+
*
|
|
237
|
+
* `resolvedFile` is the specifier canonicalised through the module resolver, so
|
|
238
|
+
* two spellings of one target — `./x` from one file, `../x` from another —
|
|
239
|
+
* carry the same `resolvedFile` and collapse to a single componentId
|
|
240
|
+
* downstream instead of minting a phantom each. A chain that resolves into the
|
|
241
|
+
* graph (workspace sibling, relative import, or unparsed workspace member)
|
|
242
|
+
* promotes to `local`, hashing to the same id as the local-index seed;
|
|
243
|
+
* everything else stays `imported`.
|
|
244
|
+
*/
|
|
245
|
+
export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
246
|
+
const origin = fileGraphForDynamicImport(graph, di, fileGraph);
|
|
247
|
+
const importedName = di.projection[0] ?? "default";
|
|
248
|
+
const resolvedFile = graph ? resolveImportedFile(graph, origin, di.specifier, importedName) : null;
|
|
249
|
+
return (workspaceSiblingIdentity(graph, di.specifier, importedName, resolvedFile) ??
|
|
250
|
+
relativeChainLocalIdentity(graph, origin, di.specifier, importedName) ??
|
|
251
|
+
memberLocalIdentity(graph, origin, di.specifier, importedName) ?? {
|
|
252
|
+
kind: "imported",
|
|
253
|
+
specifier: di.specifier,
|
|
254
|
+
imported: importedName,
|
|
255
|
+
resolvedFile,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Structural re-entry for a reference. Resolves ONE
|
|
234
260
|
* hop and walks the resolved value through `walkWithFolding`, so every walker
|
|
235
|
-
* rule — wrapper hops, dynamic maps,
|
|
236
|
-
* through an alias exactly as at a direct render site.
|
|
237
|
-
* default fold
|
|
238
|
-
* back, so a map-bodied `List` reached via `MAP[k]`
|
|
261
|
+
* rule — wrapper hops, dynamic maps, `.map()` bodies — applies
|
|
262
|
+
* through an alias exactly as at a direct render site. Without this arm the
|
|
263
|
+
* default fold hands the whole subtree to `resolveType`, which never comes
|
|
264
|
+
* back, so a map-bodied `List` reached via `MAP[k]` dies as Unknown.
|
|
239
265
|
*
|
|
240
|
-
* Identity
|
|
266
|
+
* Identity: a terminal that comes back with no identity of its own is
|
|
241
267
|
* stamped with THIS reference's identity only when the reference names a
|
|
242
268
|
* component-shaped value — `evalKind === "component"`, the one component-
|
|
243
269
|
* shape seam, called not re-derived. A reference to a JSX value
|
|
244
270
|
* (`return priceElement`) or to a parameter binding stays null so the outer
|
|
245
|
-
* fallback wins
|
|
271
|
+
* fallback wins, by construction rather than by special case.
|
|
246
272
|
*
|
|
247
|
-
* An import the graph cannot see (`resolved` is Unknown) is NOT re-entered
|
|
248
|
-
*
|
|
249
|
-
*
|
|
273
|
+
* An import the graph cannot see (`resolved` is Unknown) is NOT re-entered:
|
|
274
|
+
* it is the identity-bearing leaf `foldLeaf` synthesises from, and
|
|
275
|
+
* `foldToWrappedArg`'s rescue relies on that shape.
|
|
250
276
|
*
|
|
251
|
-
* Cross-file
|
|
277
|
+
* Cross-file: a Function authored elsewhere walks in ITS file graph
|
|
252
278
|
* through the `fileGraphForFunction` seam — the same switch
|
|
253
279
|
* `walkDispatcherReturns` makes — so file-scoped lookups inside it (a dynamic
|
|
254
280
|
* map's binding) resolve where they were written.
|
|
@@ -286,13 +312,13 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
286
312
|
}
|
|
287
313
|
}
|
|
288
314
|
/**
|
|
289
|
-
* Static member access
|
|
315
|
+
* Static member access. Mirrors `walkDynamicMember` for one
|
|
290
316
|
* named prop and without the `dynamic-map` hop: resolve the object, take the
|
|
291
317
|
* member, walk the member's VALUE through `walkWithFolding` — so a wrapper
|
|
292
318
|
* product stored in a map (`MAP.memo` → `memo(Button)`) reaches
|
|
293
319
|
* `walkReturnTypeOf` and keeps its `hoc-wrapper` hop. Anything that is not an
|
|
294
320
|
* in-graph Object (an Array, an import-backed namespace the graph cannot see)
|
|
295
|
-
* takes the leaf fold
|
|
321
|
+
* takes the leaf fold.
|
|
296
322
|
*
|
|
297
323
|
* Cycle guard: mirrors `walkTypeOf`. `const A = { Item: () => B.Item };
|
|
298
324
|
* const B = { Item: () => A.Item }` is legal, loadable JS and the walk
|
|
@@ -340,8 +366,22 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
340
366
|
kind: "DynamicImport",
|
|
341
367
|
specifier: lazyShape.specifier,
|
|
342
368
|
projection: lazyShape.projection,
|
|
369
|
+
originFile: lazyShape.originFile,
|
|
343
370
|
};
|
|
344
|
-
const
|
|
371
|
+
const guardKey = `import()::${syntheticDI.specifier}::${syntheticDI.projection.join(".")}`;
|
|
372
|
+
if (guard.push(syntheticDI.originFile, guardKey) !== "ok") {
|
|
373
|
+
return [{ type: { kind: "Unknown" }, viaTrail: [], identity: null }];
|
|
374
|
+
}
|
|
375
|
+
let inner;
|
|
376
|
+
try {
|
|
377
|
+
const target = resolveDynamicImportTarget(graph, fileGraph, syntheticDI, guard);
|
|
378
|
+
inner = target
|
|
379
|
+
? walkWithFolding(graph, target.targetFileGraph, target.value, argMap, guard)
|
|
380
|
+
: walkWithFolding(graph, fileGraph, syntheticDI, argMap, guard);
|
|
381
|
+
}
|
|
382
|
+
finally {
|
|
383
|
+
guard.pop(syntheticDI.originFile, guardKey);
|
|
384
|
+
}
|
|
345
385
|
const wrapperCallee = deriveHocCallee(type.callee);
|
|
346
386
|
const importedName = lazyShape.projection[0] ?? "default";
|
|
347
387
|
const via = {
|
|
@@ -350,30 +390,9 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
350
390
|
specifier: lazyShape.specifier,
|
|
351
391
|
import: importedName,
|
|
352
392
|
};
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
// module the JSX came from); we know the source from lazyShape.
|
|
357
|
-
// Canonicalise via moduleResolver so `lazy(() => import('./x'))` and
|
|
358
|
-
// `lazy(() => import('../x'))` from different callers collapse (#109);
|
|
359
|
-
// promote to local when the chain resolves into the graph (#101).
|
|
360
|
-
// Workspace-sibling bare-package lazy targets promote to local too (#94
|
|
361
|
-
// parity with deriveIdentity's DynamicImport branch, #327 — validated
|
|
362
|
-
// against a real monorepo where `next/dynamic` member imports split
|
|
363
|
-
// identity: a bare-package specifier that resolves to an in-graph file
|
|
364
|
-
// via the workspace-exports resolver was falling through to `imported`
|
|
365
|
-
// because this chain never called `workspaceSiblingIdentity`).
|
|
366
|
-
const resolvedFile = resolveImportedFile(graph, fileGraph, lazyShape.specifier, importedName);
|
|
367
|
-
const workspaceLocal = workspaceSiblingIdentity(graph, lazyShape.specifier, importedName, resolvedFile);
|
|
368
|
-
const relativeLocal = relativeChainLocalIdentity(graph, fileGraph, lazyShape.specifier, importedName);
|
|
369
|
-
const identity = workspaceLocal ??
|
|
370
|
-
relativeLocal ??
|
|
371
|
-
memberLocalIdentity(graph, fileGraph, lazyShape.specifier, importedName) ?? {
|
|
372
|
-
kind: "imported",
|
|
373
|
-
specifier: lazyShape.specifier,
|
|
374
|
-
imported: importedName,
|
|
375
|
-
resolvedFile,
|
|
376
|
-
};
|
|
393
|
+
// A terminal keeps the identity its own fold produced; the import-derived
|
|
394
|
+
// identity covers terminals that carry none.
|
|
395
|
+
const identity = dynamicImportIdentity(graph, fileGraph, syntheticDI);
|
|
377
396
|
const innerHasNonUnknown = inner.some((t) => t.type.kind !== "Unknown");
|
|
378
397
|
if (innerHasNonUnknown) {
|
|
379
398
|
return inner
|
|
@@ -381,14 +400,17 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
381
400
|
.map((t) => ({
|
|
382
401
|
type: t.type,
|
|
383
402
|
viaTrail: [via, ...t.viaTrail],
|
|
384
|
-
identity,
|
|
403
|
+
identity: t.identity ?? identity,
|
|
385
404
|
}));
|
|
386
405
|
}
|
|
387
|
-
// External package: target not in graph.
|
|
388
|
-
//
|
|
406
|
+
// External package: target not in graph. Also reached when the inner walk
|
|
407
|
+
// was cut by the import cycle key above or by the depth budget, in which
|
|
408
|
+
// case the import-derived identity is the answer. We still have a valid
|
|
409
|
+
// specifier+projection from lazyShape — synthesize a JSX terminal rather
|
|
410
|
+
// than falling through to Unknown.
|
|
389
411
|
return [{ type: { kind: "JSX" }, viaTrail: [via], identity }];
|
|
390
412
|
}
|
|
391
|
-
// (2) Dispatcher detection
|
|
413
|
+
// (2) Dispatcher detection: if the callee resolves to a Function
|
|
392
414
|
// whose body returns a dynamic-map access (`return MAP[k]`), walk that body
|
|
393
415
|
// via walkWithFolding in the function's authoring fileGraph. This lets the
|
|
394
416
|
// walkDynamicMember rule fire and surfaces the dynamic-map via in the chain
|
|
@@ -397,7 +419,7 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
397
419
|
const dispatcherResult = walkDispatcherReturns(graph, fileGraph, type, argMap, guard);
|
|
398
420
|
if (dispatcherResult)
|
|
399
421
|
return dispatcherResult;
|
|
400
|
-
// (2b) Structural pass-through
|
|
422
|
+
// (2b) Structural pass-through. When every Function
|
|
401
423
|
// the callee resolves to returns its own parameter (a bare `ParameterOf`, or a `TypeOf` that resolves to one) bound by THIS call,
|
|
402
424
|
// the wrapper is identity-preserving by construction — `c => c`; React's
|
|
403
425
|
// memo/forwardRef reach this arm through a library stub table, regardless
|
|
@@ -417,17 +439,17 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
417
439
|
// (3) Pure algebra walk. Per-leaf identity uses each terminal's source
|
|
418
440
|
// (the leaf at the innermost fanout fork that produced it) so dynamic-map /
|
|
419
441
|
// Union / Array fanouts attribute each branch to its own binding instead
|
|
420
|
-
// of collapsing every leaf to the outer wrapper's identity
|
|
442
|
+
// of collapsing every leaf to the outer wrapper's identity.
|
|
421
443
|
// foldTerminals also converts unreducible identity-bearing leaves into
|
|
422
444
|
// synthesized JSX terminals, so a resolvable and an unresolvable imported
|
|
423
|
-
// leaf take the same emission path
|
|
445
|
+
// leaf take the same emission path.
|
|
424
446
|
const algResult = foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), type);
|
|
425
447
|
const algHasNonUnknown = algResult.some((r) => r.type.kind !== "Unknown");
|
|
426
448
|
if (algHasNonUnknown) {
|
|
427
449
|
// (3b) Pass-through HOC recovery. When the algebra reduces this call to the
|
|
428
450
|
// identity of its wrapped last arg (an identity-preserving `c => c` wrapper
|
|
429
|
-
// — withRouter/connect stubs,
|
|
430
|
-
// synthesis
|
|
451
|
+
// — withRouter/connect stubs, plus direct external-import calls the leaf
|
|
452
|
+
// synthesis lets the algebra see through), the reduction flattened
|
|
431
453
|
// the wrapper breadcrumb away. Recover it so a seen-through wrapper and an
|
|
432
454
|
// opaque one (step 4) attribute composition identically: via presence
|
|
433
455
|
// tracks the wrapper, not the algebra's reach. A wrapper whose body returns
|
|
@@ -451,14 +473,13 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
451
473
|
return [{ type: { kind: "Unknown" }, viaTrail: [], identity: null }];
|
|
452
474
|
}
|
|
453
475
|
const inner = walkWithFolding(graph, fileGraph, lastArg, argMap, guard);
|
|
454
|
-
//
|
|
476
|
+
// A data-method render call (`items.map(render)`) is not a
|
|
455
477
|
// wrapper — its result IS the callback's rendered JSX, not a component
|
|
456
478
|
// received as a prop. Return the callback's terminals AS-IS: no
|
|
457
479
|
// `hoc-wrapper` hop, and identity stays null so the engine's own fallback
|
|
458
480
|
// (the declaration being walked, e.g. `List`) wins rather than whatever
|
|
459
481
|
// the callback itself happened to resolve to. Import-backed MemberOf
|
|
460
|
-
// callees (`Sentry.withProfiler`) fall through to the
|
|
461
|
-
// below — arm 3 (D9) stands for those.
|
|
482
|
+
// callees (`Sentry.withProfiler`) fall through to the fold+hop below.
|
|
462
483
|
if (isDataMethodRenderCall(type, graph, fileGraph, guard)) {
|
|
463
484
|
const nonUnknown = inner.filter((t) => t.type.kind !== "Unknown");
|
|
464
485
|
if (nonUnknown.length > 0) {
|
|
@@ -475,7 +496,7 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
475
496
|
* a `TypeOf` resolved one hop) to a `ParameterOf` whose `fn` is the ref this
|
|
476
497
|
* call binds args to (`bindingRef`), and every return names the same index.
|
|
477
498
|
* Unions, JSX, nested Functions or an unresolvable callee all yield null so the
|
|
478
|
-
*
|
|
499
|
+
* algebra / opaque-HOC paths handle them instead.
|
|
479
500
|
*/
|
|
480
501
|
function passThroughParameterIndex(graph, fileGraph, type, argMap, guard) {
|
|
481
502
|
const calleeFns = resolveToFunctions(graph, fileGraph, type.callee, argMap, guard);
|
|
@@ -504,7 +525,7 @@ function passThroughParameterIndex(graph, fileGraph, type, argMap, guard) {
|
|
|
504
525
|
return index;
|
|
505
526
|
}
|
|
506
527
|
/**
|
|
507
|
-
* Cross-module dispatcher walk
|
|
528
|
+
* Cross-module dispatcher walk. When the callee of a ReturnTypeOf
|
|
508
529
|
* resolves to a Function whose body returns a `MemberOf-DYNAMIC` access
|
|
509
530
|
* (the canonical `function getMapped(k) { return MAP[k]; }` shape), the
|
|
510
531
|
* dynamic-map fanout lives across a module boundary from the call site.
|
|
@@ -556,13 +577,19 @@ function findLazyImportArg(args, fileGraph) {
|
|
|
556
577
|
for (const arg of args) {
|
|
557
578
|
const di = findFirstDynamicImport(arg, fileGraph, /*depthBudget=*/ 1);
|
|
558
579
|
if (di)
|
|
559
|
-
return
|
|
580
|
+
return di;
|
|
560
581
|
}
|
|
561
582
|
return null;
|
|
562
583
|
}
|
|
584
|
+
const PROMISE_METHODS = new Set(["then", "catch", "finally"]);
|
|
585
|
+
/** The crediting finder: narrow by design, and the only one that may claim a
|
|
586
|
+
* projection. `firstDynamicImport` (`engine/component-shape.ts`) is the
|
|
587
|
+
* reporting inverse — it descends everything and names a specifier only for
|
|
588
|
+
* the diagnostic. */
|
|
563
589
|
function findFirstDynamicImport(t, fileGraph, depthBudget) {
|
|
564
|
-
if (t.kind === "DynamicImport")
|
|
565
|
-
return { specifier: t.specifier, projection: t.projection };
|
|
590
|
+
if (t.kind === "DynamicImport") {
|
|
591
|
+
return { specifier: t.specifier, projection: t.projection, originFile: t.originFile };
|
|
592
|
+
}
|
|
566
593
|
if (t.kind === "Function") {
|
|
567
594
|
for (const r of t.returns) {
|
|
568
595
|
const hit = findFirstDynamicImport(r, fileGraph, depthBudget);
|
|
@@ -571,14 +598,21 @@ function findFirstDynamicImport(t, fileGraph, depthBudget) {
|
|
|
571
598
|
}
|
|
572
599
|
return null;
|
|
573
600
|
}
|
|
601
|
+
if (t.kind === "ReturnTypeOf" && t.callee.kind === "MemberOf" && PROMISE_METHODS.has(t.callee.member)) {
|
|
602
|
+
// `catch`/`finally` keep the import's value; a `then` the parser did not
|
|
603
|
+
// collapse cannot name an export, so the import stays unclaimed.
|
|
604
|
+
return t.callee.member === "then" ? null : findFirstDynamicImport(t.callee.obj, fileGraph, depthBudget);
|
|
605
|
+
}
|
|
574
606
|
if (t.kind === "MemberOf") {
|
|
607
|
+
if (PROMISE_METHODS.has(t.member))
|
|
608
|
+
return null;
|
|
575
609
|
// Descend into obj; if the inner result is a bare namespace DynamicImport
|
|
576
610
|
// (projection=[]), append this member as the named-export projection so
|
|
577
611
|
// that the async-await form `m.X` where `m = DynamicImport("./foo", [])`
|
|
578
612
|
// produces projection:["X"] instead of projection:[].
|
|
579
613
|
const inner = findFirstDynamicImport(t.obj, fileGraph, depthBudget);
|
|
580
614
|
if (inner && t.member !== "" && inner.projection.length === 0) {
|
|
581
|
-
return { specifier: inner.specifier, projection: [t.member] };
|
|
615
|
+
return { specifier: inner.specifier, projection: [t.member], originFile: inner.originFile };
|
|
582
616
|
}
|
|
583
617
|
return inner;
|
|
584
618
|
}
|
|
@@ -690,19 +724,19 @@ function resolveAnonymousLastArg(lastArg, fileGraph) {
|
|
|
690
724
|
let cur = lastArg;
|
|
691
725
|
while (cur.kind === "ReturnTypeOf")
|
|
692
726
|
cur = cur.callee;
|
|
693
|
-
// (1) Leaf TypeOf — surface the bound symbol directly
|
|
694
|
-
//
|
|
695
|
-
//
|
|
727
|
+
// (1) Leaf TypeOf — surface the bound symbol directly: for chains like
|
|
728
|
+
// `withA(withB(X))` the inner call's callee name is the most descriptive
|
|
729
|
+
// via.import.
|
|
696
730
|
if (cur.kind === "TypeOf") {
|
|
697
731
|
return { specifier: fileGraph.filePath, import: cur.ref.symbol };
|
|
698
732
|
}
|
|
699
733
|
// (2) Function tagged at parser-emit time — works across file boundaries
|
|
700
|
-
// because the annotation travels with the value
|
|
734
|
+
// because the annotation travels with the value.
|
|
701
735
|
if (cur.kind === "Function" && cur.enclosingBinding) {
|
|
702
736
|
return { specifier: cur.enclosingBinding.file, import: cur.enclosingBinding.symbol };
|
|
703
737
|
}
|
|
704
738
|
// (3) Same-file reference-equality scan — find the declaration whose
|
|
705
|
-
// value tree contains lastArg (
|
|
739
|
+
// value tree contains lastArg (same-file fallback).
|
|
706
740
|
for (const decl of fileGraph.declarations.values()) {
|
|
707
741
|
if (decl && typeContainsRef(decl.value, lastArg)) {
|
|
708
742
|
return { specifier: fileGraph.filePath, import: decl.symbol };
|
|
@@ -749,9 +783,9 @@ function typeContainsRef(haystack, needle) {
|
|
|
749
783
|
*
|
|
750
784
|
* When `imported` is provided, follows re-export chains (both
|
|
751
785
|
* `{kind: "named", from, fromImported}` and `{kind: "star", from}`) to the
|
|
752
|
-
* canonical source file via `followReExportChain`
|
|
753
|
-
*
|
|
754
|
-
*
|
|
786
|
+
* canonical source file via `followReExportChain` (star re-exports are
|
|
787
|
+
* first-wins). Mirrors what `resolveExport` in resolve-reference.ts
|
|
788
|
+
* does for value-flow purposes,
|
|
755
789
|
* applied here at identity-stamping time. So `import { X } from './barrel'`
|
|
756
790
|
* where `./barrel` re-exports X from `./source` attributes the component's
|
|
757
791
|
* identity to `./source.tsx`, not `./barrel.tsx`.
|
|
@@ -759,7 +793,7 @@ function typeContainsRef(haystack, needle) {
|
|
|
759
793
|
* Used by `deriveIdentity` so `TerminalIdentity.resolvedFile` carries the
|
|
760
794
|
* canonical repo-relative path. Downstream `componentIdFromIdentity` uses
|
|
761
795
|
* that canonical path so `'./page'` and `'../page'` from different callers
|
|
762
|
-
* collapse to the same identity
|
|
796
|
+
* collapse to the same identity.
|
|
763
797
|
*/
|
|
764
798
|
function resolveImportedFile(graph, fileGraph, specifier, imported) {
|
|
765
799
|
const abs = graph.moduleResolver(fileGraph.filePath, specifier);
|
|
@@ -776,9 +810,9 @@ function resolveImportedFile(graph, fileGraph, specifier, imported) {
|
|
|
776
810
|
* the exported name, promote the identity to `kind: "local"` directly. This
|
|
777
811
|
* matches what the local-index seeds emit (keyed on `{filePath, exportName}`)
|
|
778
812
|
* so the engine occurrence hashes to the same componentId as the seed —
|
|
779
|
-
* preventing the barrel-vs-source phantom split
|
|
813
|
+
* preventing the barrel-vs-source phantom split.
|
|
780
814
|
*
|
|
781
|
-
* Symmetric with `workspaceSiblingIdentity`
|
|
815
|
+
* Symmetric with `workspaceSiblingIdentity` but for relative imports.
|
|
782
816
|
*/
|
|
783
817
|
function relativeChainLocalIdentity(graph, fileGraph, specifier, imported) {
|
|
784
818
|
if (!graph)
|
|
@@ -797,7 +831,7 @@ function relativeChainLocalIdentity(graph, fileGraph, specifier, imported) {
|
|
|
797
831
|
return { kind: "local", filePath: chained.file, export: chained.localExport };
|
|
798
832
|
}
|
|
799
833
|
/**
|
|
800
|
-
* Workspace-sibling detection
|
|
834
|
+
* Workspace-sibling detection. A bare-package import (`@org/foo`) that
|
|
801
835
|
* resolves to a file inside the parsed graph is a workspace sibling — the
|
|
802
836
|
* package lives in this repo as a sibling workspace, not as a node_modules
|
|
803
837
|
* dependency. Treat it as a local identity so JSX edges land on the actual
|
|
@@ -832,7 +866,7 @@ function workspaceSiblingIdentity(graph, specifier, imported, resolvedFile) {
|
|
|
832
866
|
return { kind: "local", filePath: resolvedFile, export: exportName };
|
|
833
867
|
}
|
|
834
868
|
/**
|
|
835
|
-
* Membership fallback for composition-path identity
|
|
869
|
+
* Membership fallback for composition-path identity. When a specifier
|
|
836
870
|
* resolves to an absolute target that is NOT in the parsed graph (outside
|
|
837
871
|
* the scan globs, or reached through an unrealpathed node_modules symlink)
|
|
838
872
|
* but IS owned by a workspace member, promote to a LOCAL identity pinned via
|
|
@@ -857,7 +891,7 @@ function memberLocalIdentity(graph, fileGraph, specifier, imported) {
|
|
|
857
891
|
return null;
|
|
858
892
|
return { kind: "local", filePath: member.absFile, export: member.exportName };
|
|
859
893
|
}
|
|
860
|
-
/** Append a residual member chain
|
|
894
|
+
/** Append a residual member chain to a derived identity's export name. */
|
|
861
895
|
function withResidual(identity, residual) {
|
|
862
896
|
if (identity === null || residual.length === 0)
|
|
863
897
|
return identity;
|
|
@@ -876,42 +910,22 @@ function withResidual(identity, residual) {
|
|
|
876
910
|
* that match the ref actually live.
|
|
877
911
|
*/
|
|
878
912
|
export function deriveIdentity(type, fileGraph, graph) {
|
|
879
|
-
// A ReturnTypeOf never derives identity from its callee
|
|
913
|
+
// A ReturnTypeOf never derives identity from its callee:
|
|
880
914
|
// a resolvable factory call's identity is the declaration holding the call,
|
|
881
915
|
// which every engine push site already supplies as its fallback. Returning
|
|
882
|
-
// null here is what makes that fallback win.
|
|
916
|
+
// null here is what makes that fallback win.
|
|
883
917
|
const cur = type;
|
|
884
918
|
if (cur.kind === "DynamicImport") {
|
|
885
|
-
|
|
886
|
-
const resolvedFile = graph ? resolveImportedFile(graph, fileGraph, cur.specifier, importedName) : null;
|
|
887
|
-
const workspaceLocal = workspaceSiblingIdentity(graph, cur.specifier, importedName, resolvedFile);
|
|
888
|
-
if (workspaceLocal)
|
|
889
|
-
return workspaceLocal;
|
|
890
|
-
// Re-export-chain-followed relative imports promote to local (#101).
|
|
891
|
-
const relativeLocal = relativeChainLocalIdentity(graph, fileGraph, cur.specifier, importedName);
|
|
892
|
-
if (relativeLocal)
|
|
893
|
-
return relativeLocal;
|
|
894
|
-
const memberLocal = memberLocalIdentity(graph, fileGraph, cur.specifier, importedName);
|
|
895
|
-
if (memberLocal)
|
|
896
|
-
return memberLocal;
|
|
897
|
-
return {
|
|
898
|
-
kind: "imported",
|
|
899
|
-
specifier: cur.specifier,
|
|
900
|
-
imported: importedName,
|
|
901
|
-
// Canonicalise via moduleResolver — `lazy(() => import('./x'))` and
|
|
902
|
-
// `lazy(() => import('../x'))` from different callers collapse to one
|
|
903
|
-
// identity (#109).
|
|
904
|
-
resolvedFile,
|
|
905
|
-
};
|
|
919
|
+
return dynamicImportIdentity(graph, fileGraph, cur);
|
|
906
920
|
}
|
|
907
921
|
if (cur.kind === "TypeOf") {
|
|
908
922
|
const symbol = cur.ref.symbol;
|
|
909
923
|
const lookup = graph ? fileGraphForRef(graph, cur.ref, fileGraph) : fileGraph;
|
|
910
924
|
const imp = lookup.importsByLocal.get(symbol);
|
|
911
925
|
if (imp) {
|
|
912
|
-
// Namespace-import members canonicalise to the member name
|
|
913
|
-
// matching the direct-import path's `effectiveImported`
|
|
914
|
-
//
|
|
926
|
+
// Namespace-import members canonicalise to the member name,
|
|
927
|
+
// matching the direct-import path's `effectiveImported`
|
|
928
|
+
// (engine/index.ts) so both paths mint the same identity. The residual chain
|
|
915
929
|
// is appended AFTER every root lookup, so `Dialog.Popup` on an unparsed
|
|
916
930
|
// package and `NS.Inline` on a workspace member both mirror the direct
|
|
917
931
|
// path.
|
|
@@ -936,7 +950,7 @@ export function deriveIdentity(type, fileGraph, graph) {
|
|
|
936
950
|
}
|
|
937
951
|
// Check local declarations. A same-file symbol is never a namespace
|
|
938
952
|
// import, so the whole member chain is residual (mirror of
|
|
939
|
-
// `pushLocalOccurrence`'s fallback
|
|
953
|
+
// `pushLocalOccurrence`'s fallback) — an empty chain is a no-op.
|
|
940
954
|
for (const [, decl] of lookup.declarations) {
|
|
941
955
|
if (decl && decl.symbol === symbol) {
|
|
942
956
|
return withResidual({
|