@component-compass/reference-graph 0.1.23 → 0.1.25
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/engine/argument-map.d.ts +7 -0
- package/dist/engine/argument-map.js +7 -0
- package/dist/engine/argument-map.js.map +1 -1
- package/dist/engine/component-shape.d.ts +26 -2
- package/dist/engine/component-shape.js +75 -12
- package/dist/engine/component-shape.js.map +1 -1
- package/dist/engine/denotation.d.ts +90 -0
- package/dist/engine/denotation.js +55 -0
- package/dist/engine/denotation.js.map +1 -0
- package/dist/engine/host-element.d.ts +7 -3
- package/dist/engine/host-element.js +7 -3
- package/dist/engine/host-element.js.map +1 -1
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.js +389 -291
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/library-stubs.d.ts +36 -0
- package/dist/engine/library-stubs.js +26 -29
- package/dist/engine/library-stubs.js.map +1 -1
- package/dist/engine/member-identity.d.ts +8 -0
- package/dist/engine/member-identity.js +15 -0
- package/dist/engine/member-identity.js.map +1 -1
- package/dist/engine/registry.d.ts +70 -15
- package/dist/engine/registry.js +164 -80
- package/dist/engine/registry.js.map +1 -1
- package/dist/engine/resolve-type.d.ts +1 -1
- package/dist/engine/resolve-type.js +3 -2
- package/dist/engine/resolve-type.js.map +1 -1
- package/dist/engine/wrapper-folding.d.ts +12 -15
- package/dist/engine/wrapper-folding.js +361 -75
- package/dist/engine/wrapper-folding.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { findLocalDeclaration } from "./helper-callers.js";
|
|
1
2
|
import { resolveType, resolveToFunctions, resolveDynamicImportTarget, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
|
|
2
|
-
import { argumentRefKey } from "./argument-map.js";
|
|
3
|
+
import { argumentRefKey, boundArgument } from "./argument-map.js";
|
|
3
4
|
import { createCycleGuard } from "./cycle-detection.js";
|
|
4
5
|
import { fileGraphForRef, resolveReference } from "./resolve-reference.js";
|
|
5
6
|
import { packageNameFromSpecifier } from "./specifier.js";
|
|
6
7
|
import { followReExportChain } from "./re-export-chain.js";
|
|
7
8
|
import { unparsedMemberDefinition, effectiveExportName, residualMemberChain, compoundExportName } from "./member-identity.js";
|
|
8
|
-
import { evalKind, isImportBackedLeaf } from "./component-shape.js";
|
|
9
|
+
import { containsDynamicImport, evalKind, isImportBackedLeaf, nonComponentProduct } from "./component-shape.js";
|
|
10
|
+
import { HOOK_NAME } from "./host-element.js";
|
|
9
11
|
import { assertNever } from "./assert-never.js";
|
|
12
|
+
import { creditedTerminals, isCallable, matchDenotation } from "./denotation.js";
|
|
10
13
|
/** True when a `ReturnTypeOf`'s callee is a data-method render
|
|
11
14
|
* call (`items.map(render)`) rather than a wrapper taking a component as a
|
|
12
15
|
* prop — the exact structural condition `component-shape.ts`'s rule (ii)
|
|
@@ -16,13 +19,192 @@ import { assertNever } from "./assert-never.js";
|
|
|
16
19
|
function isDataMethodRenderCall(type, graph, fileGraph, guard) {
|
|
17
20
|
return type.callee.kind === "MemberOf" && !isImportBackedLeaf(type.callee, graph, fileGraph, guard);
|
|
18
21
|
}
|
|
22
|
+
/** True when a value is a loader: a function whose body holds an `import()`,
|
|
23
|
+
* inline, or reached from a reference through a chain of aliases, or held by
|
|
24
|
+
* a static member of an object literal (by every member, for a dynamic one)
|
|
25
|
+
* that the value names directly or through such a chain. Each reference
|
|
26
|
+
* resolves in its own file. Anything else, a `lazy()` holder or a wrapper
|
|
27
|
+
* product included, is not. */
|
|
28
|
+
function isLoader(value, graph, fileGraph, guard) {
|
|
29
|
+
if (value.kind === "Function")
|
|
30
|
+
return containsDynamicImport(value, { graph, fileGraph });
|
|
31
|
+
if (value.kind !== "TypeOf" && value.kind !== "MemberOf")
|
|
32
|
+
return false;
|
|
33
|
+
if (guard.pushNode(value) === "cycle")
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
if (value.kind === "TypeOf")
|
|
37
|
+
return isLoader(resolveReference(graph, fileGraph, value.ref, guard), graph, fileGraph, guard);
|
|
38
|
+
const holder = objectLiteralOf(value.obj, graph, fileGraph, guard);
|
|
39
|
+
if (holder === null)
|
|
40
|
+
return false;
|
|
41
|
+
if (value.member !== DYNAMIC_MEMBER_KEY) {
|
|
42
|
+
const held = holder.props[value.member];
|
|
43
|
+
return held !== undefined && isLoader(held, graph, fileGraph, guard);
|
|
44
|
+
}
|
|
45
|
+
const all = Object.values(holder.props);
|
|
46
|
+
return all.length > 0 && all.every((v) => isLoader(v, graph, fileGraph, guard));
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
guard.popNode(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The object literal a value is, directly or through a chain of aliases. */
|
|
53
|
+
function objectLiteralOf(value, graph, fileGraph, guard) {
|
|
54
|
+
if (value.kind === "Object")
|
|
55
|
+
return value;
|
|
56
|
+
if (value.kind !== "TypeOf" || guard.pushNode(value) === "cycle")
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return objectLiteralOf(resolveReference(graph, fileGraph, value.ref, guard), graph, fileGraph, guard);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
guard.popNode(value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** The denotation of a branch the walk learned nothing about. */
|
|
66
|
+
const INDETERMINATE = { kind: "indeterminate" };
|
|
67
|
+
function withSemantics(t, semantics) {
|
|
68
|
+
return { denotation: t.denotation, viaTrail: t.viaTrail, identity: t.identity, semantics };
|
|
69
|
+
}
|
|
70
|
+
/** The product every branch of an evaluation is marked with; null when any
|
|
71
|
+
* branch is unmarked or marked differently. */
|
|
72
|
+
function markedProduct(terminals) {
|
|
73
|
+
const first = terminals[0]?.semantics;
|
|
74
|
+
if (first === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
return terminals.every((t) => t.semantics === first) ? first : null;
|
|
77
|
+
}
|
|
78
|
+
/** THE in-walker replacement for `t.type.kind === "Unknown"`, stated once and
|
|
79
|
+
* read only through `matchDenotation`: "this branch came to nothing".
|
|
80
|
+
*
|
|
81
|
+
* `late-bound` answers `true`: the values it classifies reduce to `Unknown` in
|
|
82
|
+
* the algebra. `opaque` is in the record for exhaustiveness only — the walk
|
|
83
|
+
* mints it at the public boundary and never reads it back, so no internal
|
|
84
|
+
* path can observe that handler. */
|
|
85
|
+
const CAME_TO_NOTHING = {
|
|
86
|
+
element: () => false,
|
|
87
|
+
foreign: () => false,
|
|
88
|
+
"host-string": () => false,
|
|
89
|
+
opaque: () => true,
|
|
90
|
+
"late-bound": () => true,
|
|
91
|
+
data: () => false,
|
|
92
|
+
indeterminate: () => true,
|
|
93
|
+
callable: () => false,
|
|
94
|
+
};
|
|
95
|
+
function cameToNothing(t) {
|
|
96
|
+
return matchDenotation(t, CAME_TO_NOTHING);
|
|
97
|
+
}
|
|
98
|
+
const IS_LATE_BOUND = {
|
|
99
|
+
element: () => false,
|
|
100
|
+
foreign: () => false,
|
|
101
|
+
"host-string": () => false,
|
|
102
|
+
opaque: () => false,
|
|
103
|
+
"late-bound": () => true,
|
|
104
|
+
data: () => false,
|
|
105
|
+
indeterminate: () => false,
|
|
106
|
+
callable: () => false,
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Rename a branch that came to nothing, keeping its identity and viaTrail
|
|
110
|
+
* exactly as the fold produced them, and dropping any product mark: the new
|
|
111
|
+
* denotation is what the branch is now. A branch that reduced to something the
|
|
112
|
+
* walk does know keeps its own denotation and its mark.
|
|
113
|
+
*/
|
|
114
|
+
function relabel(denotation) {
|
|
115
|
+
return (t) => (cameToNothing(t) ? { denotation, viaTrail: t.viaTrail, identity: t.identity } : t);
|
|
116
|
+
}
|
|
117
|
+
function asLateBound(source) {
|
|
118
|
+
return relabel({ kind: "late-bound", source });
|
|
119
|
+
}
|
|
120
|
+
/** True when every branch of an evaluation is a value supplied elsewhere. */
|
|
121
|
+
function evaluatesLateBound(terminals) {
|
|
122
|
+
return terminals.length > 0 && terminals.every((t) => matchDenotation(t, IS_LATE_BOUND));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* THE boundary between "what this value IS" and "what this function returns".
|
|
126
|
+
*
|
|
127
|
+
* A string becomes the data the function returns; a late-bound value becomes
|
|
128
|
+
* "nothing known"; a product mark is dropped, so a function that returns a
|
|
129
|
+
* context is not itself a context. `opaque` is in the record for
|
|
130
|
+
* exhaustiveness only — no internal path produces that denotation.
|
|
131
|
+
*
|
|
132
|
+
* Stated once and applied at exactly two places: the `Function` arm of
|
|
133
|
+
* `walkInner` and the exit of `walkReturnTypeOf`.
|
|
134
|
+
*/
|
|
135
|
+
const AS_RETURNED_VALUE = {
|
|
136
|
+
element: (d) => d,
|
|
137
|
+
foreign: (d) => d,
|
|
138
|
+
"host-string": () => ({ kind: "data", shape: "string" }),
|
|
139
|
+
opaque: () => INDETERMINATE,
|
|
140
|
+
"late-bound": () => INDETERMINATE,
|
|
141
|
+
data: (d) => d,
|
|
142
|
+
indeterminate: (d) => d,
|
|
143
|
+
callable: (d) => d,
|
|
144
|
+
};
|
|
145
|
+
function returnedValue(t) {
|
|
146
|
+
const denotation = matchDenotation(t, AS_RETURNED_VALUE);
|
|
147
|
+
return denotation === t.denotation && t.semantics === undefined
|
|
148
|
+
? t
|
|
149
|
+
: { denotation, viaTrail: t.viaTrail, identity: t.identity };
|
|
150
|
+
}
|
|
151
|
+
function indeterminateTerminal() {
|
|
152
|
+
return { denotation: INDETERMINATE, viaTrail: [], identity: null };
|
|
153
|
+
}
|
|
154
|
+
/** THE one place a product mark becomes a denotation. A marked branch the tag
|
|
155
|
+
* would credit keeps the denotation it earned; any other marked branch IS the
|
|
156
|
+
* product. */
|
|
157
|
+
function published(t) {
|
|
158
|
+
if (t.semantics === undefined)
|
|
159
|
+
return t;
|
|
160
|
+
const denotation = creditedTerminals([t]).length > 0 ? t.denotation : { kind: "opaque", semantics: t.semantics };
|
|
161
|
+
return { denotation, viaTrail: t.viaTrail, identity: t.identity };
|
|
162
|
+
}
|
|
163
|
+
/** The public boundary's guarantee, and only the public boundary's: an
|
|
164
|
+
* `Evaluation` handed to a caller is never empty, because a caller has
|
|
165
|
+
* nothing to read otherwise. Inside the walk `[]` stays `[]`. */
|
|
166
|
+
function atLeastOne(terminals) {
|
|
167
|
+
return terminals.length > 0 ? terminals : [indeterminateTerminal()];
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* What one `resolveType` terminal denotes. `synthesised` marks the unreducible
|
|
171
|
+
* import-backed leaf `foldTerminals` mints an identity for: the reference is
|
|
172
|
+
* real and its target unparseable, so the value is `foreign`. `indeterminate`
|
|
173
|
+
* is reserved for the terminal the algebra bottomed out at knowing nothing —
|
|
174
|
+
* a value the algebra did reduce, to a string or to data, is not that.
|
|
175
|
+
*/
|
|
176
|
+
function denote(type, synthesised) {
|
|
177
|
+
if (synthesised)
|
|
178
|
+
return { kind: "foreign" };
|
|
179
|
+
switch (type.kind) {
|
|
180
|
+
case "JSX":
|
|
181
|
+
return { kind: "element" };
|
|
182
|
+
case "Str":
|
|
183
|
+
return { kind: "host-string", value: type.value };
|
|
184
|
+
case "Object":
|
|
185
|
+
return { kind: "data", shape: "object" };
|
|
186
|
+
case "Array":
|
|
187
|
+
return { kind: "data", shape: "array" };
|
|
188
|
+
case "TypeOf":
|
|
189
|
+
case "Function":
|
|
190
|
+
case "ReturnTypeOf":
|
|
191
|
+
case "ParameterOf":
|
|
192
|
+
case "MemberOf":
|
|
193
|
+
case "Union":
|
|
194
|
+
case "Unknown":
|
|
195
|
+
case "DynamicImport":
|
|
196
|
+
return INDETERMINATE;
|
|
197
|
+
default:
|
|
198
|
+
return assertNever(type);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
19
201
|
/**
|
|
20
202
|
* THE single seam for unreducible-leaf identity synthesis.
|
|
21
203
|
*
|
|
22
|
-
* Maps `resolveType` results into
|
|
204
|
+
* Maps `resolveType` results into Terminals. When the algebra preserved
|
|
23
205
|
* an identity-bearing leaf — an import-backed TypeOf or a DynamicImport that
|
|
24
206
|
* couldn't be reduced (see resolve-type.ts's TypeOf/DynamicImport cases) —
|
|
25
|
-
* synthesize a
|
|
207
|
+
* synthesize a `foreign` terminal carrying the identity derived FROM THE TERMINAL
|
|
26
208
|
* ITSELF: the reference is real even though its target can't be parsed
|
|
27
209
|
* (external package, CJS, unresolvable specifier). Every other terminal
|
|
28
210
|
* derives identity from `source`, falling back to `fallback` only when the
|
|
@@ -36,7 +218,7 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
36
218
|
if (t.kind === "TypeOf" || t.kind === "DynamicImport") {
|
|
37
219
|
const identity = deriveIdentity(t, fileGraph, graph);
|
|
38
220
|
if (identity !== null) {
|
|
39
|
-
return {
|
|
221
|
+
return { denotation: denote(t, true), viaTrail: [], identity };
|
|
40
222
|
}
|
|
41
223
|
}
|
|
42
224
|
// A fan-out branch (Union / dynamic-map) is stamped as `source`. Derive
|
|
@@ -46,7 +228,7 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
46
228
|
// ternary/logical render body).
|
|
47
229
|
const identity = (source && deriveIdentity(source, fileGraph, graph)) ?? deriveIdentity(fallback, fileGraph, graph);
|
|
48
230
|
return {
|
|
49
|
-
|
|
231
|
+
denotation: denote(t, false),
|
|
50
232
|
viaTrail: [],
|
|
51
233
|
identity,
|
|
52
234
|
};
|
|
@@ -58,22 +240,24 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
58
240
|
* a seen-through wrapper and an opaque one attribute identically. Inner
|
|
59
241
|
* terminals pass through with the wrapper hop prepended. When the inner walk
|
|
60
242
|
* bottomed out at Unknown but carried an identity hint (imported, or
|
|
61
|
-
* local-promoted through the re-export chain), synthesise a
|
|
243
|
+
* local-promoted through the re-export chain), synthesise a `foreign` terminal
|
|
62
244
|
* from it — without this
|
|
63
245
|
* rescue, HOC-wrapped occurrences drop silently (~128 in example-web's
|
|
64
246
|
* connect/flow/withTranslators patterns). Truly
|
|
65
247
|
* nothing → a single Unknown terminal.
|
|
66
248
|
*/
|
|
67
249
|
function foldToWrappedArg(inner, via) {
|
|
68
|
-
|
|
250
|
+
// Drops the product mark: what a wrapper call returns is not the wrapped
|
|
251
|
+
// value, so `memo(Ctx)` is not a context.
|
|
252
|
+
const innerHasNonUnknown = inner.some((t) => !cameToNothing(t));
|
|
69
253
|
if (innerHasNonUnknown) {
|
|
70
|
-
return inner.map((t) => ({
|
|
254
|
+
return inner.map((t) => ({ denotation: t.denotation, viaTrail: [via, ...t.viaTrail], identity: t.identity }));
|
|
71
255
|
}
|
|
72
256
|
const synthesisIdentity = inner.find((t) => t.identity !== null)?.identity ?? null;
|
|
73
257
|
if (synthesisIdentity) {
|
|
74
|
-
return [{
|
|
258
|
+
return [{ denotation: { kind: "foreign" }, viaTrail: [via], identity: synthesisIdentity }];
|
|
75
259
|
}
|
|
76
|
-
return [
|
|
260
|
+
return [indeterminateTerminal()];
|
|
77
261
|
}
|
|
78
262
|
/**
|
|
79
263
|
* Provenance of ONE argument of a wrapper call: the wrapper's `callee` name
|
|
@@ -150,8 +334,8 @@ function identityKey(id) {
|
|
|
150
334
|
* returns a DIFFERENT component fails this and keeps its richer algebra result.
|
|
151
335
|
*/
|
|
152
336
|
function isPassThroughWrapper(algResult, inner) {
|
|
153
|
-
const innerKeys = new Set(inner.filter((t) => t
|
|
154
|
-
const algKeys = algResult.filter((t) => t
|
|
337
|
+
const innerKeys = new Set(inner.filter((t) => !cameToNothing(t) && t.identity !== null).map((t) => identityKey(t.identity)));
|
|
338
|
+
const algKeys = algResult.filter((t) => !cameToNothing(t));
|
|
155
339
|
// A null identity on either side is "no information", never a match:
|
|
156
340
|
// null-equals-null folds a factory call onto its rehydrate/config argument.
|
|
157
341
|
return algKeys.length > 0 && algKeys.every((t) => t.identity !== null && innerKeys.has(identityKey(t.identity)));
|
|
@@ -166,7 +350,23 @@ function isPassThroughWrapper(algResult, inner) {
|
|
|
166
350
|
* (pure algebra) and computes identity from the input type's leaf TypeOf
|
|
167
351
|
* when one can be confidently identified.
|
|
168
352
|
*/
|
|
169
|
-
export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(),
|
|
353
|
+
export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(), identityFallback, position = "value") {
|
|
354
|
+
return atLeastOne(walkInner(graph, fileGraph, type, argMap, guard, identityFallback, position).map(published));
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The walk itself. Returns exactly what each arm produced, empty included:
|
|
358
|
+
* inside the walk an empty result means "this branch contributed nothing",
|
|
359
|
+
* which `walkTypeOf`'s vacuous `every` and `foldToWrappedArg`'s rescue both
|
|
360
|
+
* read. Only the exported boundary above may substitute a terminal for it.
|
|
361
|
+
*
|
|
362
|
+
* `position` says whether `type` is what a tag renders. At a `tag`, a
|
|
363
|
+
* function is one `callable` terminal and its body is not walked; the
|
|
364
|
+
* position carries through the value hops (a reference, a union branch, a
|
|
365
|
+
* static or dynamic member, a call's folded argument, a lazy target, a
|
|
366
|
+
* dispatcher's returns) and nowhere else. In `value` position a function is
|
|
367
|
+
* walked into its returns.
|
|
368
|
+
*/
|
|
369
|
+
function walkInner(graph, fileGraph, type, argMap, guard,
|
|
170
370
|
// The identity `foldTerminals` falls back to when a terminal
|
|
171
371
|
// carries no identity of its own (no `source`, no unreducible TypeOf/
|
|
172
372
|
// DynamicImport leaf). Defaults to `type` (the walked node itself). The
|
|
@@ -178,38 +378,46 @@ export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCy
|
|
|
178
378
|
// that happens to sit in `foldTerminals`' fallback slot. Per-branch
|
|
179
379
|
// identity from a fanout `source` (Union([TypeOf A, TypeOf B])) is
|
|
180
380
|
// untouched: `source` is checked BEFORE this fallback in `foldTerminals`.
|
|
181
|
-
identityFallback) {
|
|
381
|
+
identityFallback, position) {
|
|
182
382
|
const fallback = identityFallback ?? type;
|
|
183
383
|
switch (type.kind) {
|
|
184
384
|
case "ReturnTypeOf":
|
|
185
|
-
return walkReturnTypeOf(graph, fileGraph, type, argMap, guard);
|
|
385
|
+
return walkReturnTypeOf(graph, fileGraph, type, argMap, guard, position);
|
|
186
386
|
case "MemberOf":
|
|
187
387
|
if (type.member === DYNAMIC_MEMBER_KEY) {
|
|
188
|
-
return walkDynamicMember(graph, fileGraph, type, argMap, guard);
|
|
388
|
+
return walkDynamicMember(graph, fileGraph, type, argMap, guard, position);
|
|
189
389
|
}
|
|
190
|
-
return walkStaticMember(graph, fileGraph, type, argMap, guard, fallback);
|
|
390
|
+
return walkStaticMember(graph, fileGraph, type, argMap, guard, fallback, position);
|
|
191
391
|
case "Function":
|
|
192
|
-
//
|
|
392
|
+
// At a tag the function is what JSX renders: one `callable` terminal
|
|
393
|
+
// with no identity of its own, and the body is not walked.
|
|
394
|
+
//
|
|
395
|
+
// In value position a component's own returns can contain a nested
|
|
193
396
|
// ReturnTypeOf/MemberOf (`List = ({items}) => items.map(render)`) that
|
|
194
397
|
// needs wrapper-folding's steps (the opaque-callee / dynamic-map
|
|
195
398
|
// handling below), not `resolveType`'s plain algebra — `resolveType`'s
|
|
196
399
|
// own "Function" case recurses via plain `resolveType`, which never
|
|
197
|
-
// re-enters this walk, so a
|
|
198
|
-
//
|
|
400
|
+
// re-enters this walk, so a value resolving to such a Function would
|
|
401
|
+
// silently produce zero terminals.
|
|
199
402
|
// Mirrors `resolveType`'s "Function" case, one level up. Pass THIS
|
|
200
403
|
// Function as the identity fallback for every return — a
|
|
201
404
|
// Function is never TypeOf/DynamicImport, so `deriveIdentity` on it
|
|
202
405
|
// is always null.
|
|
203
|
-
|
|
406
|
+
if (position === "tag")
|
|
407
|
+
return [{ denotation: { kind: "callable" }, identity: null, viaTrail: [] }];
|
|
408
|
+
return type.returns
|
|
409
|
+
.flatMap((r) => walkInner(graph, fileGraph, r, argMap, guard, type, "value"))
|
|
410
|
+
.map(returnedValue);
|
|
204
411
|
case "TypeOf":
|
|
205
|
-
return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback);
|
|
412
|
+
return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback, position);
|
|
206
413
|
case "Union":
|
|
207
414
|
// Structural re-entry: each branch walks through the walker so
|
|
208
415
|
// a TypeOf branch reaches `walkTypeOf` and decides its own identity
|
|
209
416
|
// (per-branch attribution) while a bare JSX branch — a lowered
|
|
210
417
|
// ternary/logical render body — derives from the outer fallback.
|
|
211
|
-
return type.types.flatMap((branch) =>
|
|
418
|
+
return type.types.flatMap((branch) => walkInner(graph, fileGraph, branch, argMap, guard, fallback, position));
|
|
212
419
|
case "ParameterOf":
|
|
420
|
+
return walkParameter(graph, fileGraph, type, argMap, guard, fallback);
|
|
213
421
|
case "DynamicImport":
|
|
214
422
|
case "Object":
|
|
215
423
|
case "Array":
|
|
@@ -221,12 +429,25 @@ identityFallback) {
|
|
|
221
429
|
return assertNever(type);
|
|
222
430
|
}
|
|
223
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* A parameter. Bound by the call the walk is inside — `resolveType`
|
|
434
|
+
* substitutes the argument and the walk continues into it, exactly as before.
|
|
435
|
+
* Unbound, the value is supplied by whoever calls this function later, which
|
|
436
|
+
* is a different fact from "nothing is known": the algebra flattens both to
|
|
437
|
+
* `Unknown`, and only the render site acts on the difference.
|
|
438
|
+
*/
|
|
439
|
+
function walkParameter(graph, fileGraph, type, argMap, guard, fallback) {
|
|
440
|
+
const leaf = foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
441
|
+
if (boundArgument(argMap, type) !== undefined)
|
|
442
|
+
return leaf;
|
|
443
|
+
return leaf.map(asLateBound("parameter"));
|
|
444
|
+
}
|
|
224
445
|
/**
|
|
225
446
|
* The leaf fold: pure algebra, then identity synthesis through the ONE
|
|
226
447
|
* unreducible-leaf seam (`foldTerminals`). This is the only place the walker
|
|
227
448
|
* hands a subtree to `resolveType`; every structural kind (TypeOf, Union,
|
|
228
449
|
* MemberOf, Function, ReturnTypeOf) has its own arm above and re-enters
|
|
229
|
-
* `
|
|
450
|
+
* `walkInner` instead.
|
|
230
451
|
*/
|
|
231
452
|
function foldLeaf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
232
453
|
return foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), fallback);
|
|
@@ -282,7 +503,7 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
|
282
503
|
}
|
|
283
504
|
/**
|
|
284
505
|
* Structural re-entry for a reference. Resolves ONE
|
|
285
|
-
* hop and walks the resolved value through `
|
|
506
|
+
* hop and walks the resolved value through `walkInner`, so every walker
|
|
286
507
|
* rule — wrapper hops, dynamic maps, `.map()` bodies — applies
|
|
287
508
|
* through an alias exactly as at a direct render site. Without this arm the
|
|
288
509
|
* default fold hands the whole subtree to `resolveType`, which never comes
|
|
@@ -291,9 +512,14 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
|
291
512
|
* Identity: a terminal that comes back with no identity of its own is
|
|
292
513
|
* stamped with THIS reference's identity only when the reference names a
|
|
293
514
|
* component-shaped value — `evalKind === "component"`, the one component-
|
|
294
|
-
* shape seam, called not re-derived
|
|
295
|
-
*
|
|
296
|
-
*
|
|
515
|
+
* shape seam, called not re-derived — or, at a tag, when the walk came back
|
|
516
|
+
* with an identity-less `callable`: a reference to a function, or to a
|
|
517
|
+
* wrapper call over an anonymous one, names what the tag renders, whatever
|
|
518
|
+
* it returns. A reference to a JSX value (`return priceElement`) or to a
|
|
519
|
+
* parameter binding stays null so the outer fallback wins, by construction
|
|
520
|
+
* rather than by special case. At a tag, a self-stamp of a same-file reference
|
|
521
|
+
* also carries the declaration that reference resolves to
|
|
522
|
+
* (`withOwnDeclaration`).
|
|
297
523
|
*
|
|
298
524
|
* An import the graph cannot see (`resolved` is Unknown) is NOT re-entered:
|
|
299
525
|
* it is the identity-bearing leaf `foldLeaf` synthesises from, and
|
|
@@ -307,9 +533,9 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
|
307
533
|
* Cycle guard: mirrors `resolveType`'s node guard. `const A = B; const B = A`
|
|
308
534
|
* is legal JS and would otherwise re-enter forever.
|
|
309
535
|
*/
|
|
310
|
-
function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
536
|
+
function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback, position) {
|
|
311
537
|
if (guard.pushNode(type) === "cycle") {
|
|
312
|
-
return [
|
|
538
|
+
return [indeterminateTerminal()];
|
|
313
539
|
}
|
|
314
540
|
try {
|
|
315
541
|
const resolved = resolveReference(graph, fileGraph, type.ref, guard);
|
|
@@ -323,23 +549,40 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
323
549
|
return foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
324
550
|
}
|
|
325
551
|
const innerGraph = resolved.kind === "Function" ? fileGraphForFunction(graph, fileGraph, resolved) : fileGraph;
|
|
326
|
-
const inner =
|
|
552
|
+
const inner = walkInner(graph, innerGraph, resolved, argMap, guard, fallback, position);
|
|
327
553
|
if (inner.every((t) => t.identity !== null))
|
|
328
554
|
return inner;
|
|
329
|
-
const
|
|
555
|
+
const derived = (position === "tag" && inner.some((t) => t.identity === null && isCallable(t))) ||
|
|
556
|
+
(resolved.kind !== "ParameterOf" && evalKind(resolved, graph, innerGraph, argMap) === "component")
|
|
330
557
|
? deriveIdentity(type, fileGraph, graph)
|
|
331
558
|
: null;
|
|
559
|
+
const self = position === "tag" ? withOwnDeclaration(derived, graph, fileGraph, type.ref) : derived;
|
|
332
560
|
const stamp = self ?? deriveIdentity(fallback, fileGraph, graph);
|
|
333
|
-
|
|
561
|
+
// Carries the product mark: a reference to a context IS that context.
|
|
562
|
+
return inner.map((t) => t.identity === null ? { denotation: t.denotation, viaTrail: t.viaTrail, identity: stamp, semantics: t.semantics } : t);
|
|
334
563
|
}
|
|
335
564
|
finally {
|
|
336
565
|
guard.popNode(type);
|
|
337
566
|
}
|
|
338
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* A local identity stamped from a same-file reference with no member chain,
|
|
570
|
+
* given the declaration that reference resolves to in its own scope
|
|
571
|
+
* (`findLocalDeclaration`). Any other identity is returned unchanged.
|
|
572
|
+
*/
|
|
573
|
+
function withOwnDeclaration(identity, graph, fileGraph, ref) {
|
|
574
|
+
if (identity === null || identity.kind !== "local" || ref.memberChain.length > 0)
|
|
575
|
+
return identity;
|
|
576
|
+
const lookup = fileGraphForRef(graph, ref, fileGraph);
|
|
577
|
+
if (identity.filePath !== lookup.filePath || lookup.importsByLocal.has(ref.symbol))
|
|
578
|
+
return identity;
|
|
579
|
+
const declaration = findLocalDeclaration(lookup, ref);
|
|
580
|
+
return declaration === null ? identity : { ...identity, declaration };
|
|
581
|
+
}
|
|
339
582
|
/**
|
|
340
583
|
* Static member access. Mirrors `walkDynamicMember` for one
|
|
341
584
|
* named prop and without the `dynamic-map` hop: resolve the object, take the
|
|
342
|
-
* member, walk the member's VALUE through `
|
|
585
|
+
* member, walk the member's VALUE through `walkInner` — so a wrapper
|
|
343
586
|
* product stored in a map (`MAP.memo` → `memo(Button)`) reaches
|
|
344
587
|
* `walkReturnTypeOf` and keeps its `hoc-wrapper` hop. Anything that is not an
|
|
345
588
|
* in-graph Object (an Array, an import-backed namespace the graph cannot see)
|
|
@@ -350,9 +593,9 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
350
593
|
* re-enters this arm forever without it — `resolveType` pops every node it
|
|
351
594
|
* pushes, so nothing on such a cycle stays on the guard's path otherwise.
|
|
352
595
|
*/
|
|
353
|
-
function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
|
|
596
|
+
function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback, position) {
|
|
354
597
|
if (guard.pushNode(type) === "cycle") {
|
|
355
|
-
return [
|
|
598
|
+
return [indeterminateTerminal()];
|
|
356
599
|
}
|
|
357
600
|
try {
|
|
358
601
|
const objects = resolveType(graph, fileGraph, type.obj, argMap, guard)
|
|
@@ -365,22 +608,61 @@ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
365
608
|
// identity), not just on real cycles. The `finally` below still runs
|
|
366
609
|
// (a no-op double pop) once this returns.
|
|
367
610
|
guard.popNode(type);
|
|
368
|
-
|
|
611
|
+
const leaf = foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
612
|
+
if (!(leaf.length > 0 && leaf.every(cameToNothing)))
|
|
613
|
+
return leaf;
|
|
614
|
+
// The object answers for the member: a prop read off a value supplied
|
|
615
|
+
// elsewhere (`props.Icon`, `useThing()[0]`) is supplied elsewhere too,
|
|
616
|
+
// and any member of a non-component product is that product. ONE walk
|
|
617
|
+
// answers both, and only once the member came to nothing, so a member
|
|
618
|
+
// that resolved is never re-walked.
|
|
619
|
+
const object = walkInner(graph, fileGraph, type.obj, argMap, guard, undefined, "value");
|
|
620
|
+
const semantics = markedProduct(object);
|
|
621
|
+
if (semantics !== null)
|
|
622
|
+
return leaf.map((t) => withSemantics(t, semantics));
|
|
623
|
+
return evaluatesLateBound(object) ? leaf.map(asLateBound("parameter-member")) : leaf;
|
|
369
624
|
}
|
|
370
625
|
const out = [];
|
|
371
626
|
for (const obj of objects) {
|
|
372
627
|
const value = obj.props[type.member];
|
|
373
628
|
if (value === undefined)
|
|
374
629
|
continue;
|
|
375
|
-
out.push(...
|
|
630
|
+
out.push(...walkInner(graph, fileGraph, value, argMap, guard, fallback, position));
|
|
376
631
|
}
|
|
377
|
-
return out.length > 0 ? out : [
|
|
632
|
+
return out.length > 0 ? out : [indeterminateTerminal()];
|
|
378
633
|
}
|
|
379
634
|
finally {
|
|
380
635
|
guard.popNode(type);
|
|
381
636
|
}
|
|
382
637
|
}
|
|
383
|
-
|
|
638
|
+
/**
|
|
639
|
+
* A call. A call of an export the stub table names a non-component product
|
|
640
|
+
* IS that product, before any fold runs: its arguments are values the product
|
|
641
|
+
* holds (a context's default), never a wrapped component, so none of them is
|
|
642
|
+
* walked or credited.
|
|
643
|
+
*
|
|
644
|
+
* Every other call runs the folding rules first (`foldCall`). The call's own
|
|
645
|
+
* shape gets a name only when all of them declined: no argument credited and no
|
|
646
|
+
* algebra result. A call of a hook the graph cannot see returns a value
|
|
647
|
+
* bound at render time by the hook's own implementation, so it is late-bound
|
|
648
|
+
* rather than unknown. The predicate is React's `HOOK_NAME` over a callee
|
|
649
|
+
* `isImportBackedLeaf` says the graph cannot resolve; it runs after the folds,
|
|
650
|
+
* so `const X = useStyled(Base)` keeps the credit its fold gave it.
|
|
651
|
+
*
|
|
652
|
+
* ORDER IS LOAD-BEARING: `returnedValue` runs on the fold's results before
|
|
653
|
+
* `late-bound{hook-result}` is minted for the call itself.
|
|
654
|
+
*/
|
|
655
|
+
function walkReturnTypeOf(graph, fileGraph, type, argMap, guard, position) {
|
|
656
|
+
const semantics = nonComponentProduct(graph, fileGraph, type.callee, guard);
|
|
657
|
+
if (semantics !== null)
|
|
658
|
+
return [withSemantics(indeterminateTerminal(), semantics)];
|
|
659
|
+
const returns = foldCall(graph, fileGraph, type, argMap, guard, position).map(returnedValue);
|
|
660
|
+
if (!(returns.length > 0 && returns.every(cameToNothing)))
|
|
661
|
+
return returns;
|
|
662
|
+
const unseenHook = HOOK_NAME.test(deriveHocCallee(type.callee)) && isImportBackedLeaf(type.callee, graph, fileGraph, guard);
|
|
663
|
+
return unseenHook ? returns.map(asLateBound("hook-result")) : returns;
|
|
664
|
+
}
|
|
665
|
+
function foldCall(graph, fileGraph, type, argMap, guard, position) {
|
|
384
666
|
// (1) lazy-import shape: any arg's subtree contains a DynamicImport
|
|
385
667
|
// (with one-ref-deep peek into local-file bindings to catch the async-await form).
|
|
386
668
|
const lazyShape = findLazyImportArg(type.args, fileGraph);
|
|
@@ -395,14 +677,14 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
395
677
|
};
|
|
396
678
|
const guardKey = `import()::${syntheticDI.specifier}::${syntheticDI.projection.join(".")}`;
|
|
397
679
|
if (guard.push(syntheticDI.originFile, guardKey) !== "ok") {
|
|
398
|
-
return [
|
|
680
|
+
return [indeterminateTerminal()];
|
|
399
681
|
}
|
|
400
682
|
let inner;
|
|
401
683
|
try {
|
|
402
684
|
const target = resolveDynamicImportTarget(graph, fileGraph, syntheticDI, guard);
|
|
403
685
|
inner = target
|
|
404
|
-
?
|
|
405
|
-
:
|
|
686
|
+
? walkInner(graph, target.targetFileGraph, target.value, argMap, guard, undefined, position)
|
|
687
|
+
: walkInner(graph, fileGraph, syntheticDI, argMap, guard, undefined, position);
|
|
406
688
|
}
|
|
407
689
|
finally {
|
|
408
690
|
guard.pop(syntheticDI.originFile, guardKey);
|
|
@@ -418,12 +700,12 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
418
700
|
// A terminal keeps the identity its own fold produced; the import-derived
|
|
419
701
|
// identity covers terminals that carry none.
|
|
420
702
|
const identity = dynamicImportIdentity(graph, fileGraph, syntheticDI);
|
|
421
|
-
const innerHasNonUnknown = inner.some((t) => t
|
|
703
|
+
const innerHasNonUnknown = inner.some((t) => !cameToNothing(t));
|
|
422
704
|
if (innerHasNonUnknown) {
|
|
423
705
|
return inner
|
|
424
|
-
.filter((t) => t
|
|
706
|
+
.filter((t) => !cameToNothing(t))
|
|
425
707
|
.map((t) => ({
|
|
426
|
-
|
|
708
|
+
denotation: t.denotation,
|
|
427
709
|
viaTrail: [via, ...t.viaTrail],
|
|
428
710
|
identity: t.identity ?? identity,
|
|
429
711
|
}));
|
|
@@ -431,17 +713,17 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
431
713
|
// External package: target not in graph. Also reached when the inner walk
|
|
432
714
|
// was cut by the import cycle key above or by the depth budget, in which
|
|
433
715
|
// case the import-derived identity is the answer. We still have a valid
|
|
434
|
-
// specifier+projection from lazyShape — synthesize a
|
|
716
|
+
// specifier+projection from lazyShape — synthesize a `foreign` terminal rather
|
|
435
717
|
// than falling through to Unknown.
|
|
436
|
-
return [{
|
|
718
|
+
return [{ denotation: { kind: "foreign" }, viaTrail: [via], identity }];
|
|
437
719
|
}
|
|
438
720
|
// (2) Dispatcher detection: if the callee resolves to a Function
|
|
439
721
|
// whose body returns a dynamic-map access (`return MAP[k]`), walk that body
|
|
440
|
-
// via
|
|
722
|
+
// via walkInner in the function's authoring fileGraph. This lets the
|
|
441
723
|
// walkDynamicMember rule fire and surfaces the dynamic-map via in the chain
|
|
442
724
|
// — without it, cross-module dispatcher patterns would lose the via even
|
|
443
725
|
// though identity attribution worked.
|
|
444
|
-
const dispatcherResult = walkDispatcherReturns(graph, fileGraph, type, argMap, guard);
|
|
726
|
+
const dispatcherResult = walkDispatcherReturns(graph, fileGraph, type, argMap, guard, position);
|
|
445
727
|
if (dispatcherResult)
|
|
446
728
|
return dispatcherResult;
|
|
447
729
|
// (2b) Structural pass-through. When every Function
|
|
@@ -457,7 +739,7 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
457
739
|
if (passThroughIndex !== null) {
|
|
458
740
|
const wrapped = type.args[passThroughIndex];
|
|
459
741
|
if (wrapped !== undefined) {
|
|
460
|
-
const inner =
|
|
742
|
+
const inner = walkInner(graph, fileGraph, wrapped, argMap, guard, undefined, position);
|
|
461
743
|
return foldToWrappedArg(inner, buildHocVia(type, wrapped, fileGraph));
|
|
462
744
|
}
|
|
463
745
|
}
|
|
@@ -466,10 +748,10 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
466
748
|
// Union / Array fanouts attribute each branch to its own binding instead
|
|
467
749
|
// of collapsing every leaf to the outer wrapper's identity.
|
|
468
750
|
// foldTerminals also converts unreducible identity-bearing leaves into
|
|
469
|
-
// synthesized
|
|
751
|
+
// synthesized `foreign` terminals, so a resolvable and an unresolvable imported
|
|
470
752
|
// leaf take the same emission path.
|
|
471
753
|
const algResult = foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), type);
|
|
472
|
-
const algHasNonUnknown = algResult.some((r) => r
|
|
754
|
+
const algHasNonUnknown = algResult.some((r) => !cameToNothing(r));
|
|
473
755
|
if (algHasNonUnknown) {
|
|
474
756
|
// (3b) Pass-through HOC recovery. When the algebra reduces this call to the
|
|
475
757
|
// identity of its wrapped last arg (an identity-preserving `c => c` wrapper
|
|
@@ -482,12 +764,12 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
482
764
|
// stands with no via.
|
|
483
765
|
const lastArg = type.args.at(-1);
|
|
484
766
|
if (lastArg !== undefined) {
|
|
485
|
-
const inner =
|
|
767
|
+
const inner = walkInner(graph, fileGraph, lastArg, argMap, guard, undefined, position);
|
|
486
768
|
if (isPassThroughWrapper(algResult, inner)) {
|
|
487
769
|
const via = buildHocVia(type, lastArg, fileGraph);
|
|
488
770
|
return inner
|
|
489
|
-
.filter((t) => t
|
|
490
|
-
.map((t) => ({
|
|
771
|
+
.filter((t) => !cameToNothing(t))
|
|
772
|
+
.map((t) => ({ denotation: t.denotation, viaTrail: [via, ...t.viaTrail], identity: t.identity }));
|
|
491
773
|
}
|
|
492
774
|
}
|
|
493
775
|
return algResult;
|
|
@@ -495,9 +777,13 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
495
777
|
// (4) HOC fallback: algebra failed, last arg is the wrapper target.
|
|
496
778
|
const lastArg = type.args.at(-1);
|
|
497
779
|
if (lastArg === undefined) {
|
|
498
|
-
return [
|
|
780
|
+
return [indeterminateTerminal()];
|
|
499
781
|
}
|
|
500
|
-
|
|
782
|
+
// A data-method callback and a loader holding an `import()` the lazy arm
|
|
783
|
+
// declined are called, not rendered, so they walk in value position.
|
|
784
|
+
const dataMethod = isDataMethodRenderCall(type, graph, fileGraph, guard);
|
|
785
|
+
const called = dataMethod || isLoader(lastArg, graph, fileGraph, guard);
|
|
786
|
+
const inner = walkInner(graph, fileGraph, lastArg, argMap, guard, undefined, called ? "value" : position);
|
|
501
787
|
// A data-method render call (`items.map(render)`) is not a
|
|
502
788
|
// wrapper — its result IS the callback's rendered JSX, not a component
|
|
503
789
|
// received as a prop. Return the callback's terminals AS-IS: no
|
|
@@ -505,12 +791,12 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
505
791
|
// (the declaration being walked, e.g. `List`) wins rather than whatever
|
|
506
792
|
// the callback itself happened to resolve to. Import-backed MemberOf
|
|
507
793
|
// callees (`Sentry.withProfiler`) fall through to the fold+hop below.
|
|
508
|
-
if (
|
|
509
|
-
const nonUnknown = inner.filter((t) => t
|
|
794
|
+
if (dataMethod) {
|
|
795
|
+
const nonUnknown = inner.filter((t) => !cameToNothing(t));
|
|
510
796
|
if (nonUnknown.length > 0) {
|
|
511
|
-
return nonUnknown.map((t) => ({
|
|
797
|
+
return nonUnknown.map((t) => ({ denotation: t.denotation, viaTrail: t.viaTrail, identity: null }));
|
|
512
798
|
}
|
|
513
|
-
return [
|
|
799
|
+
return [indeterminateTerminal()];
|
|
514
800
|
}
|
|
515
801
|
return foldToWrappedArg(inner, buildHocVia(type, lastArg, fileGraph));
|
|
516
802
|
}
|
|
@@ -559,11 +845,11 @@ function passThroughParameterIndex(graph, fileGraph, type, argMap, guard) {
|
|
|
559
845
|
* so no `dynamic-map` via would land in the chain.
|
|
560
846
|
*
|
|
561
847
|
* This helper detects the shape and walks each function's returns through
|
|
562
|
-
* `
|
|
848
|
+
* `walkInner` in the function's authoring fileGraph (via
|
|
563
849
|
* `enclosingBinding.file`). Returns null when no callee matches the shape so
|
|
564
850
|
* the caller falls through to the pure algebra path.
|
|
565
851
|
*/
|
|
566
|
-
function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
|
|
852
|
+
function walkDispatcherReturns(graph, fileGraph, type, argMap, guard, position) {
|
|
567
853
|
const calleeFns = resolveToFunctions(graph, fileGraph, type.callee, argMap, guard);
|
|
568
854
|
const hasDispatcher = calleeFns.some(({ fn }) => fn.kind === "Function" && fn.returns.some(isDynamicMemberReturn));
|
|
569
855
|
if (!hasDispatcher)
|
|
@@ -577,7 +863,7 @@ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
|
|
|
577
863
|
argMap.bind(bindingRef, type.args);
|
|
578
864
|
try {
|
|
579
865
|
for (const ret of fn.returns) {
|
|
580
|
-
folded.push(...
|
|
866
|
+
folded.push(...walkInner(graph, fnFileGraph, ret, argMap, guard, undefined, position));
|
|
581
867
|
}
|
|
582
868
|
}
|
|
583
869
|
finally {
|
|
@@ -585,10 +871,10 @@ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
|
|
|
585
871
|
argMap.unbind(bindingRef);
|
|
586
872
|
}
|
|
587
873
|
}
|
|
588
|
-
const hasNonUnknown = folded.some((t) => t
|
|
874
|
+
const hasNonUnknown = folded.some((t) => !cameToNothing(t));
|
|
589
875
|
if (!hasNonUnknown)
|
|
590
876
|
return null;
|
|
591
|
-
return folded.filter((t) => t
|
|
877
|
+
return folded.filter((t) => !cameToNothing(t));
|
|
592
878
|
}
|
|
593
879
|
function isDynamicMemberReturn(t) {
|
|
594
880
|
return t.kind === "MemberOf" && t.member === DYNAMIC_MEMBER_KEY;
|
|
@@ -657,9 +943,9 @@ function findFirstDynamicImport(t, fileGraph, depthBudget) {
|
|
|
657
943
|
* reached through `MAP[k]` re-enters this arm forever without it. No
|
|
658
944
|
* `foldLeaf` path here, so no early pop is needed.
|
|
659
945
|
*/
|
|
660
|
-
function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
|
|
946
|
+
function walkDynamicMember(graph, fileGraph, type, argMap, guard, position) {
|
|
661
947
|
if (guard.pushNode(type) === "cycle") {
|
|
662
|
-
return [
|
|
948
|
+
return [indeterminateTerminal()];
|
|
663
949
|
}
|
|
664
950
|
try {
|
|
665
951
|
const objResults = resolveType(graph, fileGraph, type.obj, argMap, guard);
|
|
@@ -675,15 +961,15 @@ function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
|
|
|
675
961
|
column: mapBinding?.loc.column ?? 0,
|
|
676
962
|
};
|
|
677
963
|
for (const propValue of Object.values(obj.props)) {
|
|
678
|
-
// Recurse via
|
|
964
|
+
// Recurse via walkInner so identity is derived from each branch's
|
|
679
965
|
// leaf TypeOf (Foo/Bar resolve to their respective bindings).
|
|
680
|
-
const inner =
|
|
966
|
+
const inner = walkInner(graph, fileGraph, propValue, argMap, guard, undefined, position);
|
|
681
967
|
const via = { kind: "dynamic-map", mapName, mapLoc };
|
|
682
968
|
for (const t of inner) {
|
|
683
|
-
if (t
|
|
969
|
+
if (cameToNothing(t))
|
|
684
970
|
continue;
|
|
685
971
|
out.push({
|
|
686
|
-
|
|
972
|
+
denotation: t.denotation,
|
|
687
973
|
viaTrail: [via, ...t.viaTrail],
|
|
688
974
|
identity: t.identity, // per-branch identity, distinct per map value
|
|
689
975
|
});
|
|
@@ -969,7 +1255,7 @@ export function deriveIdentity(type, fileGraph, graph) {
|
|
|
969
1255
|
}
|
|
970
1256
|
// Check local declarations. A same-file symbol is never a namespace
|
|
971
1257
|
// import, so the whole member chain is residual (mirror of
|
|
972
|
-
// `
|
|
1258
|
+
// `evaluateJsxUsage`'s same-file fallback) — an empty chain is a no-op.
|
|
973
1259
|
for (const [, decl] of lookup.declarations) {
|
|
974
1260
|
if (decl && decl.symbol === symbol) {
|
|
975
1261
|
return withResidual({
|