@component-compass/reference-graph 0.1.23 → 0.1.24
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.js +2 -2
- package/dist/engine/component-shape.js.map +1 -1
- package/dist/engine/denotation.d.ts +83 -0
- package/dist/engine/denotation.js +40 -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.js +58 -52
- 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/registry.d.ts +13 -2
- package/dist/engine/registry.js +68 -32
- 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 +3 -13
- package/dist/engine/wrapper-folding.js +283 -54
- package/dist/engine/wrapper-folding.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import type { Graph, FileGraph, InferredType } from "../index.js";
|
|
|
2
2
|
import type { OccurrenceVia } from "@component-compass/plugin-core";
|
|
3
3
|
import type { ArgumentMap } from "./argument-map.js";
|
|
4
4
|
import { type CycleGuard } from "./cycle-detection.js";
|
|
5
|
+
import { type Evaluation, type Terminal } from "./denotation.js";
|
|
5
6
|
/**
|
|
6
7
|
* Identity hint for a folded terminal. Tells the engine which binding produced
|
|
7
8
|
* this terminal so componentId derives from the folded-to-inner identity.
|
|
@@ -25,17 +26,6 @@ export type TerminalIdentity = {
|
|
|
25
26
|
filePath: string;
|
|
26
27
|
export: string;
|
|
27
28
|
} | null;
|
|
28
|
-
/**
|
|
29
|
-
* Terminal returned by `walkWithFolding`. Each terminal carries an inner viaTrail
|
|
30
|
-
* accumulated through any folding rules that fired along the walk, plus an identity
|
|
31
|
-
* hint for componentId derivation. The engine's outer occurrence-builder prepends
|
|
32
|
-
* the outer via (direct-import/local-component/etc.) to form the full viaChain.
|
|
33
|
-
*/
|
|
34
|
-
export type FoldedTerminal = {
|
|
35
|
-
type: InferredType;
|
|
36
|
-
viaTrail: OccurrenceVia[];
|
|
37
|
-
identity: TerminalIdentity;
|
|
38
|
-
};
|
|
39
29
|
/**
|
|
40
30
|
* Provenance of ONE argument of a wrapper call: the wrapper's `callee` name
|
|
41
31
|
* and the `{ specifier, import }` pair naming the argument's own binding —
|
|
@@ -64,7 +54,7 @@ export declare function argumentProvenance(type: Extract<InferredType, {
|
|
|
64
54
|
*/
|
|
65
55
|
export declare function foldedArgument(graph: Graph, fileGraph: FileGraph, call: Extract<InferredType, {
|
|
66
56
|
kind: "ReturnTypeOf";
|
|
67
|
-
}>, jsx:
|
|
57
|
+
}>, jsx: readonly Terminal[], argMap: ArgumentMap): {
|
|
68
58
|
arg: InferredType;
|
|
69
59
|
via: OccurrenceVia;
|
|
70
60
|
} | null;
|
|
@@ -78,7 +68,7 @@ export declare function foldedArgument(graph: Graph, fileGraph: FileGraph, call:
|
|
|
78
68
|
* (pure algebra) and computes identity from the input type's leaf TypeOf
|
|
79
69
|
* when one can be confidently identified.
|
|
80
70
|
*/
|
|
81
|
-
export declare function walkWithFolding(graph: Graph, fileGraph: FileGraph, type: InferredType, argMap: ArgumentMap, guard?: CycleGuard, identityFallback?: InferredType):
|
|
71
|
+
export declare function walkWithFolding(graph: Graph, fileGraph: FileGraph, type: InferredType, argMap: ArgumentMap, guard?: CycleGuard, identityFallback?: InferredType): Evaluation;
|
|
82
72
|
/**
|
|
83
73
|
* The file a DynamicImport's specifier resolves against: the file that
|
|
84
74
|
* contains the `import()`, whichever file the walk is standing in.
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { resolveType, resolveToFunctions, resolveDynamicImportTarget, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
|
|
2
|
-
import { argumentRefKey } from "./argument-map.js";
|
|
2
|
+
import { argumentRefKey, boundArgument } from "./argument-map.js";
|
|
3
3
|
import { createCycleGuard } from "./cycle-detection.js";
|
|
4
4
|
import { fileGraphForRef, resolveReference } from "./resolve-reference.js";
|
|
5
5
|
import { packageNameFromSpecifier } from "./specifier.js";
|
|
6
6
|
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
|
+
import { HOOK_NAME } from "./host-element.js";
|
|
9
10
|
import { assertNever } from "./assert-never.js";
|
|
11
|
+
import { creditedTerminals, matchDenotation } from "./denotation.js";
|
|
12
|
+
import { libraryExportFor } from "./library-stubs.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,146 @@ 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
|
+
/** The denotation of a branch the walk learned nothing about. */
|
|
23
|
+
const INDETERMINATE = { kind: "indeterminate" };
|
|
24
|
+
function withSemantics(t, semantics) {
|
|
25
|
+
return { denotation: t.denotation, viaTrail: t.viaTrail, identity: t.identity, semantics };
|
|
26
|
+
}
|
|
27
|
+
/** The product every branch of an evaluation is marked with; null when any
|
|
28
|
+
* branch is unmarked or marked differently. */
|
|
29
|
+
function markedProduct(terminals) {
|
|
30
|
+
const first = terminals[0]?.semantics;
|
|
31
|
+
if (first === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
return terminals.every((t) => t.semantics === first) ? first : null;
|
|
34
|
+
}
|
|
35
|
+
/** THE in-walker replacement for `t.type.kind === "Unknown"`, stated once and
|
|
36
|
+
* read only through `matchDenotation`: "this branch came to nothing".
|
|
37
|
+
*
|
|
38
|
+
* `late-bound` answers `true`: the values it classifies reduce to `Unknown` in
|
|
39
|
+
* the algebra. `opaque` is in the record for exhaustiveness only — the walk
|
|
40
|
+
* mints it at the public boundary and never reads it back, so no internal
|
|
41
|
+
* path can observe that handler. */
|
|
42
|
+
const CAME_TO_NOTHING = {
|
|
43
|
+
element: () => false,
|
|
44
|
+
foreign: () => false,
|
|
45
|
+
"host-string": () => false,
|
|
46
|
+
opaque: () => true,
|
|
47
|
+
"late-bound": () => true,
|
|
48
|
+
data: () => false,
|
|
49
|
+
indeterminate: () => true,
|
|
50
|
+
};
|
|
51
|
+
function cameToNothing(t) {
|
|
52
|
+
return matchDenotation(t, CAME_TO_NOTHING);
|
|
53
|
+
}
|
|
54
|
+
const IS_LATE_BOUND = {
|
|
55
|
+
element: () => false,
|
|
56
|
+
foreign: () => false,
|
|
57
|
+
"host-string": () => false,
|
|
58
|
+
opaque: () => false,
|
|
59
|
+
"late-bound": () => true,
|
|
60
|
+
data: () => false,
|
|
61
|
+
indeterminate: () => false,
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Rename a branch that came to nothing, keeping its identity and viaTrail
|
|
65
|
+
* exactly as the fold produced them, and dropping any product mark: the new
|
|
66
|
+
* denotation is what the branch is now. A branch that reduced to something the
|
|
67
|
+
* walk does know keeps its own denotation and its mark.
|
|
68
|
+
*/
|
|
69
|
+
function relabel(denotation) {
|
|
70
|
+
return (t) => (cameToNothing(t) ? { denotation, viaTrail: t.viaTrail, identity: t.identity } : t);
|
|
71
|
+
}
|
|
72
|
+
function asLateBound(source) {
|
|
73
|
+
return relabel({ kind: "late-bound", source });
|
|
74
|
+
}
|
|
75
|
+
/** True when every branch of an evaluation is a value supplied elsewhere. */
|
|
76
|
+
function evaluatesLateBound(terminals) {
|
|
77
|
+
return terminals.length > 0 && terminals.every((t) => matchDenotation(t, IS_LATE_BOUND));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* THE boundary between "what this value IS" and "what this function returns".
|
|
81
|
+
*
|
|
82
|
+
* A string becomes the data the function returns; a late-bound value becomes
|
|
83
|
+
* "nothing known"; a product mark is dropped, so a function that returns a
|
|
84
|
+
* context is not itself a context. `opaque` is in the record for
|
|
85
|
+
* exhaustiveness only — no internal path produces that denotation.
|
|
86
|
+
*
|
|
87
|
+
* Stated once and applied at exactly two places: the `Function` arm of
|
|
88
|
+
* `walkInner` and the exit of `walkReturnTypeOf`.
|
|
89
|
+
*/
|
|
90
|
+
const AS_RETURNED_VALUE = {
|
|
91
|
+
element: (d) => d,
|
|
92
|
+
foreign: (d) => d,
|
|
93
|
+
"host-string": () => ({ kind: "data", shape: "string" }),
|
|
94
|
+
opaque: () => INDETERMINATE,
|
|
95
|
+
"late-bound": () => INDETERMINATE,
|
|
96
|
+
data: (d) => d,
|
|
97
|
+
indeterminate: (d) => d,
|
|
98
|
+
};
|
|
99
|
+
function returnedValue(t) {
|
|
100
|
+
const denotation = matchDenotation(t, AS_RETURNED_VALUE);
|
|
101
|
+
return denotation === t.denotation && t.semantics === undefined
|
|
102
|
+
? t
|
|
103
|
+
: { denotation, viaTrail: t.viaTrail, identity: t.identity };
|
|
104
|
+
}
|
|
105
|
+
function indeterminateTerminal() {
|
|
106
|
+
return { denotation: INDETERMINATE, viaTrail: [], identity: null };
|
|
107
|
+
}
|
|
108
|
+
/** THE one place a product mark becomes a denotation. A marked branch the tag
|
|
109
|
+
* would credit keeps the denotation it earned; any other marked branch IS the
|
|
110
|
+
* product. */
|
|
111
|
+
function published(t) {
|
|
112
|
+
if (t.semantics === undefined)
|
|
113
|
+
return t;
|
|
114
|
+
const denotation = creditedTerminals([t]).length > 0 ? t.denotation : { kind: "opaque", semantics: t.semantics };
|
|
115
|
+
return { denotation, viaTrail: t.viaTrail, identity: t.identity };
|
|
116
|
+
}
|
|
117
|
+
/** The public boundary's guarantee, and only the public boundary's: an
|
|
118
|
+
* `Evaluation` handed to a caller is never empty, because a caller has
|
|
119
|
+
* nothing to read otherwise. Inside the walk `[]` stays `[]`. */
|
|
120
|
+
function atLeastOne(terminals) {
|
|
121
|
+
return terminals.length > 0 ? terminals : [indeterminateTerminal()];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What one `resolveType` terminal denotes. `synthesised` marks the unreducible
|
|
125
|
+
* import-backed leaf `foldTerminals` mints an identity for: the reference is
|
|
126
|
+
* real and its target unparseable, so the value is `foreign`. `indeterminate`
|
|
127
|
+
* is reserved for the terminal the algebra bottomed out at knowing nothing —
|
|
128
|
+
* a value the algebra did reduce, to a string or to data, is not that.
|
|
129
|
+
*/
|
|
130
|
+
function denote(type, synthesised) {
|
|
131
|
+
if (synthesised)
|
|
132
|
+
return { kind: "foreign" };
|
|
133
|
+
switch (type.kind) {
|
|
134
|
+
case "JSX":
|
|
135
|
+
return { kind: "element" };
|
|
136
|
+
case "Str":
|
|
137
|
+
return { kind: "host-string", value: type.value };
|
|
138
|
+
case "Object":
|
|
139
|
+
return { kind: "data", shape: "object" };
|
|
140
|
+
case "Array":
|
|
141
|
+
return { kind: "data", shape: "array" };
|
|
142
|
+
case "TypeOf":
|
|
143
|
+
case "Function":
|
|
144
|
+
case "ReturnTypeOf":
|
|
145
|
+
case "ParameterOf":
|
|
146
|
+
case "MemberOf":
|
|
147
|
+
case "Union":
|
|
148
|
+
case "Unknown":
|
|
149
|
+
case "DynamicImport":
|
|
150
|
+
return INDETERMINATE;
|
|
151
|
+
default:
|
|
152
|
+
return assertNever(type);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
19
155
|
/**
|
|
20
156
|
* THE single seam for unreducible-leaf identity synthesis.
|
|
21
157
|
*
|
|
22
|
-
* Maps `resolveType` results into
|
|
158
|
+
* Maps `resolveType` results into Terminals. When the algebra preserved
|
|
23
159
|
* an identity-bearing leaf — an import-backed TypeOf or a DynamicImport that
|
|
24
160
|
* couldn't be reduced (see resolve-type.ts's TypeOf/DynamicImport cases) —
|
|
25
|
-
* synthesize a
|
|
161
|
+
* synthesize a `foreign` terminal carrying the identity derived FROM THE TERMINAL
|
|
26
162
|
* ITSELF: the reference is real even though its target can't be parsed
|
|
27
163
|
* (external package, CJS, unresolvable specifier). Every other terminal
|
|
28
164
|
* derives identity from `source`, falling back to `fallback` only when the
|
|
@@ -36,7 +172,7 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
36
172
|
if (t.kind === "TypeOf" || t.kind === "DynamicImport") {
|
|
37
173
|
const identity = deriveIdentity(t, fileGraph, graph);
|
|
38
174
|
if (identity !== null) {
|
|
39
|
-
return {
|
|
175
|
+
return { denotation: denote(t, true), viaTrail: [], identity };
|
|
40
176
|
}
|
|
41
177
|
}
|
|
42
178
|
// A fan-out branch (Union / dynamic-map) is stamped as `source`. Derive
|
|
@@ -46,7 +182,7 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
46
182
|
// ternary/logical render body).
|
|
47
183
|
const identity = (source && deriveIdentity(source, fileGraph, graph)) ?? deriveIdentity(fallback, fileGraph, graph);
|
|
48
184
|
return {
|
|
49
|
-
|
|
185
|
+
denotation: denote(t, false),
|
|
50
186
|
viaTrail: [],
|
|
51
187
|
identity,
|
|
52
188
|
};
|
|
@@ -58,22 +194,24 @@ function foldTerminals(graph, fileGraph, results, fallback) {
|
|
|
58
194
|
* a seen-through wrapper and an opaque one attribute identically. Inner
|
|
59
195
|
* terminals pass through with the wrapper hop prepended. When the inner walk
|
|
60
196
|
* bottomed out at Unknown but carried an identity hint (imported, or
|
|
61
|
-
* local-promoted through the re-export chain), synthesise a
|
|
197
|
+
* local-promoted through the re-export chain), synthesise a `foreign` terminal
|
|
62
198
|
* from it — without this
|
|
63
199
|
* rescue, HOC-wrapped occurrences drop silently (~128 in example-web's
|
|
64
200
|
* connect/flow/withTranslators patterns). Truly
|
|
65
201
|
* nothing → a single Unknown terminal.
|
|
66
202
|
*/
|
|
67
203
|
function foldToWrappedArg(inner, via) {
|
|
68
|
-
|
|
204
|
+
// Drops the product mark: what a wrapper call returns is not the wrapped
|
|
205
|
+
// value, so `memo(Ctx)` is not a context.
|
|
206
|
+
const innerHasNonUnknown = inner.some((t) => !cameToNothing(t));
|
|
69
207
|
if (innerHasNonUnknown) {
|
|
70
|
-
return inner.map((t) => ({
|
|
208
|
+
return inner.map((t) => ({ denotation: t.denotation, viaTrail: [via, ...t.viaTrail], identity: t.identity }));
|
|
71
209
|
}
|
|
72
210
|
const synthesisIdentity = inner.find((t) => t.identity !== null)?.identity ?? null;
|
|
73
211
|
if (synthesisIdentity) {
|
|
74
|
-
return [{
|
|
212
|
+
return [{ denotation: { kind: "foreign" }, viaTrail: [via], identity: synthesisIdentity }];
|
|
75
213
|
}
|
|
76
|
-
return [
|
|
214
|
+
return [indeterminateTerminal()];
|
|
77
215
|
}
|
|
78
216
|
/**
|
|
79
217
|
* Provenance of ONE argument of a wrapper call: the wrapper's `callee` name
|
|
@@ -150,8 +288,8 @@ function identityKey(id) {
|
|
|
150
288
|
* returns a DIFFERENT component fails this and keeps its richer algebra result.
|
|
151
289
|
*/
|
|
152
290
|
function isPassThroughWrapper(algResult, inner) {
|
|
153
|
-
const innerKeys = new Set(inner.filter((t) => t
|
|
154
|
-
const algKeys = algResult.filter((t) => t
|
|
291
|
+
const innerKeys = new Set(inner.filter((t) => !cameToNothing(t) && t.identity !== null).map((t) => identityKey(t.identity)));
|
|
292
|
+
const algKeys = algResult.filter((t) => !cameToNothing(t));
|
|
155
293
|
// A null identity on either side is "no information", never a match:
|
|
156
294
|
// null-equals-null folds a factory call onto its rehydrate/config argument.
|
|
157
295
|
return algKeys.length > 0 && algKeys.every((t) => t.identity !== null && innerKeys.has(identityKey(t.identity)));
|
|
@@ -166,7 +304,16 @@ function isPassThroughWrapper(algResult, inner) {
|
|
|
166
304
|
* (pure algebra) and computes identity from the input type's leaf TypeOf
|
|
167
305
|
* when one can be confidently identified.
|
|
168
306
|
*/
|
|
169
|
-
export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(),
|
|
307
|
+
export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(), identityFallback) {
|
|
308
|
+
return atLeastOne(walkInner(graph, fileGraph, type, argMap, guard, identityFallback).map(published));
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* The walk itself. Returns exactly what each arm produced, empty included:
|
|
312
|
+
* inside the walk an empty result means "this branch contributed nothing",
|
|
313
|
+
* which `walkTypeOf`'s vacuous `every` and `foldToWrappedArg`'s rescue both
|
|
314
|
+
* read. Only the exported boundary above may substitute a terminal for it.
|
|
315
|
+
*/
|
|
316
|
+
function walkInner(graph, fileGraph, type, argMap, guard,
|
|
170
317
|
// The identity `foldTerminals` falls back to when a terminal
|
|
171
318
|
// carries no identity of its own (no `source`, no unreducible TypeOf/
|
|
172
319
|
// DynamicImport leaf). Defaults to `type` (the walked node itself). The
|
|
@@ -200,7 +347,9 @@ identityFallback) {
|
|
|
200
347
|
// Function as the identity fallback for every return — a
|
|
201
348
|
// Function is never TypeOf/DynamicImport, so `deriveIdentity` on it
|
|
202
349
|
// is always null.
|
|
203
|
-
return type.returns
|
|
350
|
+
return type.returns
|
|
351
|
+
.flatMap((r) => walkInner(graph, fileGraph, r, argMap, guard, type))
|
|
352
|
+
.map(returnedValue);
|
|
204
353
|
case "TypeOf":
|
|
205
354
|
return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback);
|
|
206
355
|
case "Union":
|
|
@@ -208,8 +357,9 @@ identityFallback) {
|
|
|
208
357
|
// a TypeOf branch reaches `walkTypeOf` and decides its own identity
|
|
209
358
|
// (per-branch attribution) while a bare JSX branch — a lowered
|
|
210
359
|
// ternary/logical render body — derives from the outer fallback.
|
|
211
|
-
return type.types.flatMap((branch) =>
|
|
360
|
+
return type.types.flatMap((branch) => walkInner(graph, fileGraph, branch, argMap, guard, fallback));
|
|
212
361
|
case "ParameterOf":
|
|
362
|
+
return walkParameter(graph, fileGraph, type, argMap, guard, fallback);
|
|
213
363
|
case "DynamicImport":
|
|
214
364
|
case "Object":
|
|
215
365
|
case "Array":
|
|
@@ -221,12 +371,25 @@ identityFallback) {
|
|
|
221
371
|
return assertNever(type);
|
|
222
372
|
}
|
|
223
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* A parameter. Bound by the call the walk is inside — `resolveType`
|
|
376
|
+
* substitutes the argument and the walk continues into it, exactly as before.
|
|
377
|
+
* Unbound, the value is supplied by whoever calls this function later, which
|
|
378
|
+
* is a different fact from "nothing is known": the algebra flattens both to
|
|
379
|
+
* `Unknown`, and only the render site acts on the difference.
|
|
380
|
+
*/
|
|
381
|
+
function walkParameter(graph, fileGraph, type, argMap, guard, fallback) {
|
|
382
|
+
const leaf = foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
383
|
+
if (boundArgument(argMap, type) !== undefined)
|
|
384
|
+
return leaf;
|
|
385
|
+
return leaf.map(asLateBound("parameter"));
|
|
386
|
+
}
|
|
224
387
|
/**
|
|
225
388
|
* The leaf fold: pure algebra, then identity synthesis through the ONE
|
|
226
389
|
* unreducible-leaf seam (`foldTerminals`). This is the only place the walker
|
|
227
390
|
* hands a subtree to `resolveType`; every structural kind (TypeOf, Union,
|
|
228
391
|
* MemberOf, Function, ReturnTypeOf) has its own arm above and re-enters
|
|
229
|
-
* `
|
|
392
|
+
* `walkInner` instead.
|
|
230
393
|
*/
|
|
231
394
|
function foldLeaf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
232
395
|
return foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), fallback);
|
|
@@ -282,7 +445,7 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
|
282
445
|
}
|
|
283
446
|
/**
|
|
284
447
|
* Structural re-entry for a reference. Resolves ONE
|
|
285
|
-
* hop and walks the resolved value through `
|
|
448
|
+
* hop and walks the resolved value through `walkInner`, so every walker
|
|
286
449
|
* rule — wrapper hops, dynamic maps, `.map()` bodies — applies
|
|
287
450
|
* through an alias exactly as at a direct render site. Without this arm the
|
|
288
451
|
* default fold hands the whole subtree to `resolveType`, which never comes
|
|
@@ -309,7 +472,7 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
|
|
|
309
472
|
*/
|
|
310
473
|
function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
311
474
|
if (guard.pushNode(type) === "cycle") {
|
|
312
|
-
return [
|
|
475
|
+
return [indeterminateTerminal()];
|
|
313
476
|
}
|
|
314
477
|
try {
|
|
315
478
|
const resolved = resolveReference(graph, fileGraph, type.ref, guard);
|
|
@@ -323,14 +486,15 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
323
486
|
return foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
324
487
|
}
|
|
325
488
|
const innerGraph = resolved.kind === "Function" ? fileGraphForFunction(graph, fileGraph, resolved) : fileGraph;
|
|
326
|
-
const inner =
|
|
489
|
+
const inner = walkInner(graph, innerGraph, resolved, argMap, guard, fallback);
|
|
327
490
|
if (inner.every((t) => t.identity !== null))
|
|
328
491
|
return inner;
|
|
329
492
|
const self = resolved.kind !== "ParameterOf" && evalKind(resolved, graph, innerGraph, argMap) === "component"
|
|
330
493
|
? deriveIdentity(type, fileGraph, graph)
|
|
331
494
|
: null;
|
|
332
495
|
const stamp = self ?? deriveIdentity(fallback, fileGraph, graph);
|
|
333
|
-
|
|
496
|
+
// Carries the product mark: a reference to a context IS that context.
|
|
497
|
+
return inner.map((t) => t.identity === null ? { denotation: t.denotation, viaTrail: t.viaTrail, identity: stamp, semantics: t.semantics } : t);
|
|
334
498
|
}
|
|
335
499
|
finally {
|
|
336
500
|
guard.popNode(type);
|
|
@@ -339,7 +503,7 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
339
503
|
/**
|
|
340
504
|
* Static member access. Mirrors `walkDynamicMember` for one
|
|
341
505
|
* named prop and without the `dynamic-map` hop: resolve the object, take the
|
|
342
|
-
* member, walk the member's VALUE through `
|
|
506
|
+
* member, walk the member's VALUE through `walkInner` — so a wrapper
|
|
343
507
|
* product stored in a map (`MAP.memo` → `memo(Button)`) reaches
|
|
344
508
|
* `walkReturnTypeOf` and keeps its `hoc-wrapper` hop. Anything that is not an
|
|
345
509
|
* in-graph Object (an Array, an import-backed namespace the graph cannot see)
|
|
@@ -352,7 +516,7 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
352
516
|
*/
|
|
353
517
|
function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
|
|
354
518
|
if (guard.pushNode(type) === "cycle") {
|
|
355
|
-
return [
|
|
519
|
+
return [indeterminateTerminal()];
|
|
356
520
|
}
|
|
357
521
|
try {
|
|
358
522
|
const objects = resolveType(graph, fileGraph, type.obj, argMap, guard)
|
|
@@ -365,22 +529,87 @@ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
|
|
|
365
529
|
// identity), not just on real cycles. The `finally` below still runs
|
|
366
530
|
// (a no-op double pop) once this returns.
|
|
367
531
|
guard.popNode(type);
|
|
368
|
-
|
|
532
|
+
const leaf = foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
|
|
533
|
+
if (!(leaf.length > 0 && leaf.every(cameToNothing)))
|
|
534
|
+
return leaf;
|
|
535
|
+
// The object answers for the member: a prop read off a value supplied
|
|
536
|
+
// elsewhere (`props.Icon`, `useThing()[0]`) is supplied elsewhere too,
|
|
537
|
+
// and any member of a non-component product is that product. ONE walk
|
|
538
|
+
// answers both, and only once the member came to nothing, so a member
|
|
539
|
+
// that resolved is never re-walked.
|
|
540
|
+
const object = walkInner(graph, fileGraph, type.obj, argMap, guard);
|
|
541
|
+
const semantics = markedProduct(object);
|
|
542
|
+
if (semantics !== null)
|
|
543
|
+
return leaf.map((t) => withSemantics(t, semantics));
|
|
544
|
+
return evaluatesLateBound(object) ? leaf.map(asLateBound("parameter-member")) : leaf;
|
|
369
545
|
}
|
|
370
546
|
const out = [];
|
|
371
547
|
for (const obj of objects) {
|
|
372
548
|
const value = obj.props[type.member];
|
|
373
549
|
if (value === undefined)
|
|
374
550
|
continue;
|
|
375
|
-
out.push(...
|
|
551
|
+
out.push(...walkInner(graph, fileGraph, value, argMap, guard, fallback));
|
|
376
552
|
}
|
|
377
|
-
return out.length > 0 ? out : [
|
|
553
|
+
return out.length > 0 ? out : [indeterminateTerminal()];
|
|
378
554
|
}
|
|
379
555
|
finally {
|
|
380
556
|
guard.popNode(type);
|
|
381
557
|
}
|
|
382
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* A call. Every folding rule runs first (`foldCall`); only when all of them
|
|
561
|
+
* declined — no argument credited, no algebra result — does the call's own
|
|
562
|
+
* shape get a name. A call of a hook the graph cannot see returns a value
|
|
563
|
+
* bound at render time by the hook's own implementation, so it is late-bound
|
|
564
|
+
* rather than unknown. The predicate is React's `HOOK_NAME` over a callee
|
|
565
|
+
* `isImportBackedLeaf` says the graph cannot resolve; it runs after the folds,
|
|
566
|
+
* so `const X = useStyled(Base)` keeps the credit its fold gave it.
|
|
567
|
+
*
|
|
568
|
+
* A call of an export the stub table names a non-component product carries
|
|
569
|
+
* that product's mark on the terminals the folds produced, and only when the
|
|
570
|
+
* folds credited nothing.
|
|
571
|
+
*
|
|
572
|
+
* ORDER IS LOAD-BEARING: `returnedValue` runs on the fold's results before
|
|
573
|
+
* `late-bound{hook-result}` is minted for the call itself.
|
|
574
|
+
*/
|
|
383
575
|
function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
576
|
+
const returns = foldCall(graph, fileGraph, type, argMap, guard).map(returnedValue);
|
|
577
|
+
if (creditedTerminals(returns).length === 0) {
|
|
578
|
+
const semantics = nonComponentProduct(graph, fileGraph, type.callee, guard);
|
|
579
|
+
if (semantics !== null)
|
|
580
|
+
return returns.map((t) => withSemantics(t, semantics));
|
|
581
|
+
}
|
|
582
|
+
if (!(returns.length > 0 && returns.every(cameToNothing)))
|
|
583
|
+
return returns;
|
|
584
|
+
const unseenHook = HOOK_NAME.test(deriveHocCallee(type.callee)) && isImportBackedLeaf(type.callee, graph, fileGraph, guard);
|
|
585
|
+
return unseenHook ? returns.map(asLateBound("hook-result")) : returns;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* The semantics of the non-component value a call produces: the stub table's
|
|
589
|
+
* entry for the callee's export, when the callee is an import the graph cannot
|
|
590
|
+
* see (`isImportBackedLeaf`, the same predicate the unseen-hook rule uses).
|
|
591
|
+
* Null for every other call.
|
|
592
|
+
*
|
|
593
|
+
* The callee's spelling is read through `libraryExportFor`, the one table
|
|
594
|
+
* `libraryStubFor` reads: a reference (a named import, or a default /
|
|
595
|
+
* namespace import carrying the member on the ref) or a member access on one
|
|
596
|
+
* (`React.createContext`).
|
|
597
|
+
*/
|
|
598
|
+
function nonComponentProduct(graph, fileGraph, callee, guard) {
|
|
599
|
+
const ref = callee.kind === "TypeOf"
|
|
600
|
+
? callee.ref
|
|
601
|
+
: callee.kind === "MemberOf" && callee.obj.kind === "TypeOf"
|
|
602
|
+
? { ...callee.obj.ref, memberChain: [callee.member] }
|
|
603
|
+
: null;
|
|
604
|
+
if (ref === null)
|
|
605
|
+
return null;
|
|
606
|
+
const imp = fileGraphForRef(graph, ref, fileGraph).importsByLocal.get(ref.symbol);
|
|
607
|
+
const entry = imp === undefined ? null : libraryExportFor(imp, ref);
|
|
608
|
+
if (entry === null || entry.kind !== "non-component-product")
|
|
609
|
+
return null;
|
|
610
|
+
return isImportBackedLeaf(callee, graph, fileGraph, guard) ? entry.semantics : null;
|
|
611
|
+
}
|
|
612
|
+
function foldCall(graph, fileGraph, type, argMap, guard) {
|
|
384
613
|
// (1) lazy-import shape: any arg's subtree contains a DynamicImport
|
|
385
614
|
// (with one-ref-deep peek into local-file bindings to catch the async-await form).
|
|
386
615
|
const lazyShape = findLazyImportArg(type.args, fileGraph);
|
|
@@ -395,14 +624,14 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
395
624
|
};
|
|
396
625
|
const guardKey = `import()::${syntheticDI.specifier}::${syntheticDI.projection.join(".")}`;
|
|
397
626
|
if (guard.push(syntheticDI.originFile, guardKey) !== "ok") {
|
|
398
|
-
return [
|
|
627
|
+
return [indeterminateTerminal()];
|
|
399
628
|
}
|
|
400
629
|
let inner;
|
|
401
630
|
try {
|
|
402
631
|
const target = resolveDynamicImportTarget(graph, fileGraph, syntheticDI, guard);
|
|
403
632
|
inner = target
|
|
404
|
-
?
|
|
405
|
-
:
|
|
633
|
+
? walkInner(graph, target.targetFileGraph, target.value, argMap, guard)
|
|
634
|
+
: walkInner(graph, fileGraph, syntheticDI, argMap, guard);
|
|
406
635
|
}
|
|
407
636
|
finally {
|
|
408
637
|
guard.pop(syntheticDI.originFile, guardKey);
|
|
@@ -418,12 +647,12 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
418
647
|
// A terminal keeps the identity its own fold produced; the import-derived
|
|
419
648
|
// identity covers terminals that carry none.
|
|
420
649
|
const identity = dynamicImportIdentity(graph, fileGraph, syntheticDI);
|
|
421
|
-
const innerHasNonUnknown = inner.some((t) => t
|
|
650
|
+
const innerHasNonUnknown = inner.some((t) => !cameToNothing(t));
|
|
422
651
|
if (innerHasNonUnknown) {
|
|
423
652
|
return inner
|
|
424
|
-
.filter((t) => t
|
|
653
|
+
.filter((t) => !cameToNothing(t))
|
|
425
654
|
.map((t) => ({
|
|
426
|
-
|
|
655
|
+
denotation: t.denotation,
|
|
427
656
|
viaTrail: [via, ...t.viaTrail],
|
|
428
657
|
identity: t.identity ?? identity,
|
|
429
658
|
}));
|
|
@@ -431,13 +660,13 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
431
660
|
// External package: target not in graph. Also reached when the inner walk
|
|
432
661
|
// was cut by the import cycle key above or by the depth budget, in which
|
|
433
662
|
// case the import-derived identity is the answer. We still have a valid
|
|
434
|
-
// specifier+projection from lazyShape — synthesize a
|
|
663
|
+
// specifier+projection from lazyShape — synthesize a `foreign` terminal rather
|
|
435
664
|
// than falling through to Unknown.
|
|
436
|
-
return [{
|
|
665
|
+
return [{ denotation: { kind: "foreign" }, viaTrail: [via], identity }];
|
|
437
666
|
}
|
|
438
667
|
// (2) Dispatcher detection: if the callee resolves to a Function
|
|
439
668
|
// whose body returns a dynamic-map access (`return MAP[k]`), walk that body
|
|
440
|
-
// via
|
|
669
|
+
// via walkInner in the function's authoring fileGraph. This lets the
|
|
441
670
|
// walkDynamicMember rule fire and surfaces the dynamic-map via in the chain
|
|
442
671
|
// — without it, cross-module dispatcher patterns would lose the via even
|
|
443
672
|
// though identity attribution worked.
|
|
@@ -457,7 +686,7 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
457
686
|
if (passThroughIndex !== null) {
|
|
458
687
|
const wrapped = type.args[passThroughIndex];
|
|
459
688
|
if (wrapped !== undefined) {
|
|
460
|
-
const inner =
|
|
689
|
+
const inner = walkInner(graph, fileGraph, wrapped, argMap, guard);
|
|
461
690
|
return foldToWrappedArg(inner, buildHocVia(type, wrapped, fileGraph));
|
|
462
691
|
}
|
|
463
692
|
}
|
|
@@ -466,10 +695,10 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
466
695
|
// Union / Array fanouts attribute each branch to its own binding instead
|
|
467
696
|
// of collapsing every leaf to the outer wrapper's identity.
|
|
468
697
|
// foldTerminals also converts unreducible identity-bearing leaves into
|
|
469
|
-
// synthesized
|
|
698
|
+
// synthesized `foreign` terminals, so a resolvable and an unresolvable imported
|
|
470
699
|
// leaf take the same emission path.
|
|
471
700
|
const algResult = foldTerminals(graph, fileGraph, resolveType(graph, fileGraph, type, argMap, guard), type);
|
|
472
|
-
const algHasNonUnknown = algResult.some((r) => r
|
|
701
|
+
const algHasNonUnknown = algResult.some((r) => !cameToNothing(r));
|
|
473
702
|
if (algHasNonUnknown) {
|
|
474
703
|
// (3b) Pass-through HOC recovery. When the algebra reduces this call to the
|
|
475
704
|
// identity of its wrapped last arg (an identity-preserving `c => c` wrapper
|
|
@@ -482,12 +711,12 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
482
711
|
// stands with no via.
|
|
483
712
|
const lastArg = type.args.at(-1);
|
|
484
713
|
if (lastArg !== undefined) {
|
|
485
|
-
const inner =
|
|
714
|
+
const inner = walkInner(graph, fileGraph, lastArg, argMap, guard);
|
|
486
715
|
if (isPassThroughWrapper(algResult, inner)) {
|
|
487
716
|
const via = buildHocVia(type, lastArg, fileGraph);
|
|
488
717
|
return inner
|
|
489
|
-
.filter((t) => t
|
|
490
|
-
.map((t) => ({
|
|
718
|
+
.filter((t) => !cameToNothing(t))
|
|
719
|
+
.map((t) => ({ denotation: t.denotation, viaTrail: [via, ...t.viaTrail], identity: t.identity }));
|
|
491
720
|
}
|
|
492
721
|
}
|
|
493
722
|
return algResult;
|
|
@@ -495,9 +724,9 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
495
724
|
// (4) HOC fallback: algebra failed, last arg is the wrapper target.
|
|
496
725
|
const lastArg = type.args.at(-1);
|
|
497
726
|
if (lastArg === undefined) {
|
|
498
|
-
return [
|
|
727
|
+
return [indeterminateTerminal()];
|
|
499
728
|
}
|
|
500
|
-
const inner =
|
|
729
|
+
const inner = walkInner(graph, fileGraph, lastArg, argMap, guard);
|
|
501
730
|
// A data-method render call (`items.map(render)`) is not a
|
|
502
731
|
// wrapper — its result IS the callback's rendered JSX, not a component
|
|
503
732
|
// received as a prop. Return the callback's terminals AS-IS: no
|
|
@@ -506,11 +735,11 @@ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard) {
|
|
|
506
735
|
// the callback itself happened to resolve to. Import-backed MemberOf
|
|
507
736
|
// callees (`Sentry.withProfiler`) fall through to the fold+hop below.
|
|
508
737
|
if (isDataMethodRenderCall(type, graph, fileGraph, guard)) {
|
|
509
|
-
const nonUnknown = inner.filter((t) => t
|
|
738
|
+
const nonUnknown = inner.filter((t) => !cameToNothing(t));
|
|
510
739
|
if (nonUnknown.length > 0) {
|
|
511
|
-
return nonUnknown.map((t) => ({
|
|
740
|
+
return nonUnknown.map((t) => ({ denotation: t.denotation, viaTrail: t.viaTrail, identity: null }));
|
|
512
741
|
}
|
|
513
|
-
return [
|
|
742
|
+
return [indeterminateTerminal()];
|
|
514
743
|
}
|
|
515
744
|
return foldToWrappedArg(inner, buildHocVia(type, lastArg, fileGraph));
|
|
516
745
|
}
|
|
@@ -559,7 +788,7 @@ function passThroughParameterIndex(graph, fileGraph, type, argMap, guard) {
|
|
|
559
788
|
* so no `dynamic-map` via would land in the chain.
|
|
560
789
|
*
|
|
561
790
|
* This helper detects the shape and walks each function's returns through
|
|
562
|
-
* `
|
|
791
|
+
* `walkInner` in the function's authoring fileGraph (via
|
|
563
792
|
* `enclosingBinding.file`). Returns null when no callee matches the shape so
|
|
564
793
|
* the caller falls through to the pure algebra path.
|
|
565
794
|
*/
|
|
@@ -577,7 +806,7 @@ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
|
|
|
577
806
|
argMap.bind(bindingRef, type.args);
|
|
578
807
|
try {
|
|
579
808
|
for (const ret of fn.returns) {
|
|
580
|
-
folded.push(...
|
|
809
|
+
folded.push(...walkInner(graph, fnFileGraph, ret, argMap, guard));
|
|
581
810
|
}
|
|
582
811
|
}
|
|
583
812
|
finally {
|
|
@@ -585,10 +814,10 @@ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
|
|
|
585
814
|
argMap.unbind(bindingRef);
|
|
586
815
|
}
|
|
587
816
|
}
|
|
588
|
-
const hasNonUnknown = folded.some((t) => t
|
|
817
|
+
const hasNonUnknown = folded.some((t) => !cameToNothing(t));
|
|
589
818
|
if (!hasNonUnknown)
|
|
590
819
|
return null;
|
|
591
|
-
return folded.filter((t) => t
|
|
820
|
+
return folded.filter((t) => !cameToNothing(t));
|
|
592
821
|
}
|
|
593
822
|
function isDynamicMemberReturn(t) {
|
|
594
823
|
return t.kind === "MemberOf" && t.member === DYNAMIC_MEMBER_KEY;
|
|
@@ -659,7 +888,7 @@ function findFirstDynamicImport(t, fileGraph, depthBudget) {
|
|
|
659
888
|
*/
|
|
660
889
|
function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
|
|
661
890
|
if (guard.pushNode(type) === "cycle") {
|
|
662
|
-
return [
|
|
891
|
+
return [indeterminateTerminal()];
|
|
663
892
|
}
|
|
664
893
|
try {
|
|
665
894
|
const objResults = resolveType(graph, fileGraph, type.obj, argMap, guard);
|
|
@@ -675,15 +904,15 @@ function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
|
|
|
675
904
|
column: mapBinding?.loc.column ?? 0,
|
|
676
905
|
};
|
|
677
906
|
for (const propValue of Object.values(obj.props)) {
|
|
678
|
-
// Recurse via
|
|
907
|
+
// Recurse via walkInner so identity is derived from each branch's
|
|
679
908
|
// leaf TypeOf (Foo/Bar resolve to their respective bindings).
|
|
680
|
-
const inner =
|
|
909
|
+
const inner = walkInner(graph, fileGraph, propValue, argMap, guard);
|
|
681
910
|
const via = { kind: "dynamic-map", mapName, mapLoc };
|
|
682
911
|
for (const t of inner) {
|
|
683
|
-
if (t
|
|
912
|
+
if (cameToNothing(t))
|
|
684
913
|
continue;
|
|
685
914
|
out.push({
|
|
686
|
-
|
|
915
|
+
denotation: t.denotation,
|
|
687
916
|
viaTrail: [via, ...t.viaTrail],
|
|
688
917
|
identity: t.identity, // per-branch identity, distinct per map value
|
|
689
918
|
});
|