@component-compass/reference-graph 0.1.17 → 0.1.18
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 +4 -0
- package/dist/engine/argument-map.js +7 -4
- package/dist/engine/argument-map.js.map +1 -1
- package/dist/engine/assert-never.d.ts +7 -0
- package/dist/engine/assert-never.js +10 -0
- package/dist/engine/assert-never.js.map +1 -0
- package/dist/engine/component-shape.d.ts +38 -0
- package/dist/engine/component-shape.js +177 -0
- package/dist/engine/component-shape.js.map +1 -0
- package/dist/engine/helper-callers.d.ts +31 -29
- package/dist/engine/helper-callers.js +98 -78
- package/dist/engine/helper-callers.js.map +1 -1
- package/dist/engine/index.d.ts +4 -0
- package/dist/engine/index.js +239 -99
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/library-stubs.d.ts +2 -0
- package/dist/engine/library-stubs.js +42 -0
- package/dist/engine/library-stubs.js.map +1 -0
- package/dist/engine/member-identity.d.ts +24 -4
- package/dist/engine/member-identity.js +28 -4
- package/dist/engine/member-identity.js.map +1 -1
- package/dist/engine/registry.d.ts +74 -0
- package/dist/engine/registry.js +226 -0
- package/dist/engine/registry.js.map +1 -0
- package/dist/engine/resolve-reference.js +7 -0
- package/dist/engine/resolve-reference.js.map +1 -1
- package/dist/engine/resolve-type.js +54 -1
- package/dist/engine/resolve-type.js.map +1 -1
- package/dist/engine/wrapper-folding.d.ts +21 -3
- package/dist/engine/wrapper-folding.js +386 -91
- package/dist/engine/wrapper-folding.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types/file-graph.d.ts +0 -10
- package/package.json +2 -2
|
@@ -11,4 +11,8 @@ export interface ArgumentMap {
|
|
|
11
11
|
unbind(fn: Reference): void;
|
|
12
12
|
lookup(fn: Reference, index: number): InferredType | undefined;
|
|
13
13
|
}
|
|
14
|
+
/** Key an ArgumentMap frame by. Exported so wrapper-folding's structural
|
|
15
|
+
* pass-through check compares `ParameterOf.fn` against a callee's
|
|
16
|
+
* `bindingRef` exactly the way `lookup` would. */
|
|
17
|
+
export declare function argumentRefKey(ref: Reference): string;
|
|
14
18
|
export declare function createArgumentMap(): ArgumentMap;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/** Key an ArgumentMap frame by. Exported so wrapper-folding's structural
|
|
2
|
+
* pass-through check compares `ParameterOf.fn` against a callee's
|
|
3
|
+
* `bindingRef` exactly the way `lookup` would. */
|
|
4
|
+
export function argumentRefKey(ref) {
|
|
2
5
|
return `${ref.symbol}::${ref.scope}`;
|
|
3
6
|
}
|
|
4
7
|
export function createArgumentMap() {
|
|
5
8
|
const stack = [];
|
|
6
9
|
return {
|
|
7
10
|
bind(fn, args) {
|
|
8
|
-
stack.push({ key:
|
|
11
|
+
stack.push({ key: argumentRefKey(fn), args });
|
|
9
12
|
},
|
|
10
13
|
unbind(fn) {
|
|
11
|
-
const key =
|
|
14
|
+
const key = argumentRefKey(fn);
|
|
12
15
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
13
16
|
if (stack[i]?.key === key) {
|
|
14
17
|
stack.splice(i, 1);
|
|
@@ -17,7 +20,7 @@ export function createArgumentMap() {
|
|
|
17
20
|
}
|
|
18
21
|
},
|
|
19
22
|
lookup(fn, index) {
|
|
20
|
-
const key =
|
|
23
|
+
const key = argumentRefKey(fn);
|
|
21
24
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
22
25
|
const frame = stack[i];
|
|
23
26
|
if (frame?.key === key)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"argument-map.js","sourceRoot":"","sources":["../../src/engine/argument-map.ts"],"names":[],"mappings":"AAeA,
|
|
1
|
+
{"version":3,"file":"argument-map.js","sourceRoot":"","sources":["../../src/engine/argument-map.ts"],"names":[],"mappings":"AAeA;;mDAEmD;AACnD,MAAM,UAAU,cAAc,CAAC,GAAc;IAC3C,OAAO,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,KAAK,GAAiD,EAAE,CAAC;IAC/D,OAAO;QACL,IAAI,CAAC,EAAE,EAAE,IAAI;YACX,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,cAAc,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,CAAC,EAAE;YACP,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YAC/B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,EAAE,CAAC;oBAC1B,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACnB,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC;QACD,MAAM,CAAC,EAAE,EAAE,KAAK;YACd,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YAC/B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACvB,IAAI,KAAK,EAAE,GAAG,KAAK,GAAG;oBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exhaustiveness guard for every `switch (type.kind)` over `InferredType`
|
|
3
|
+
* (spec D7). Reaching it is a type error at compile time — adding a kind to
|
|
4
|
+
* the IR must fail to build in every interpreter — and a loud error at
|
|
5
|
+
* runtime for the one way TypeScript can be bypassed (a cast).
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertNever(x: never): never;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exhaustiveness guard for every `switch (type.kind)` over `InferredType`
|
|
3
|
+
* (spec D7). Reaching it is a type error at compile time — adding a kind to
|
|
4
|
+
* the IR must fail to build in every interpreter — and a loud error at
|
|
5
|
+
* runtime for the one way TypeScript can be bypassed (a cast).
|
|
6
|
+
*/
|
|
7
|
+
export function assertNever(x) {
|
|
8
|
+
throw new Error(`Unhandled InferredType kind: ${JSON.stringify(x)}`);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=assert-never.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert-never.js","sourceRoot":"","sources":["../../src/engine/assert-never.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,CAAQ;IAClC,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FileGraph, Graph, InferredType } from "../index.js";
|
|
2
|
+
import { type ArgumentMap } from "./argument-map.js";
|
|
3
|
+
import { type CycleGuard } from "./cycle-detection.js";
|
|
4
|
+
/**
|
|
5
|
+
* THE component-shape predicate (spec D2, D7, D9). Reduces an InferredType
|
|
6
|
+
* to what its VALUE is:
|
|
7
|
+
*
|
|
8
|
+
* "jsx" — a JSX value (`const br = <br/>`, or what calling a
|
|
9
|
+
* component yields). Not a component.
|
|
10
|
+
* "component" — a function whose returns are DIRECTLY JSX, or a call that
|
|
11
|
+
* evaluates to one.
|
|
12
|
+
* "unresolved" — bottoms out at an import the graph cannot see with nothing
|
|
13
|
+
* component-shaped in hand (D7).
|
|
14
|
+
* "other" — anything else: factories, data, hooks, strings.
|
|
15
|
+
*
|
|
16
|
+
* The non-recursion into nested Function returns is load-bearing: a Function
|
|
17
|
+
* whose return is itself a Function evaluates that return to "component",
|
|
18
|
+
* not "jsx", so the outer function is "other" — a factory. That single rule
|
|
19
|
+
* separates `createField` from its products and is why `hasJsx`'s recursive
|
|
20
|
+
* arm was deleted (D11). Do not add a second implementation anywhere; every
|
|
21
|
+
* caller — registry membership, owner classification, occurrence admission —
|
|
22
|
+
* calls this one. Named in docs/CONTEXT.md ("Component-shape seam").
|
|
23
|
+
*/
|
|
24
|
+
export type ValueKind = "jsx" | "component" | "unresolved" | "other";
|
|
25
|
+
export declare function evalKind(type: InferredType, graph: Graph, fileGraph: FileGraph, argMap?: ArgumentMap, guard?: CycleGuard): ValueKind;
|
|
26
|
+
export declare function isComponentShaped(type: InferredType, graph: Graph, fileGraph: FileGraph): boolean;
|
|
27
|
+
/** A TypeOf (possibly under a ReturnTypeOf chain, or a MemberOf access on
|
|
28
|
+
* one — `Sentry.withProfiler`) that names an import the graph cannot
|
|
29
|
+
* resolve — the shape the opaque-HOC fold synthesises identity from (#274).
|
|
30
|
+
* Unwrapping MemberOf.obj is what lets Ruling 16(ii)'s render-callback
|
|
31
|
+
* narrowing correctly except FOR an import-backed namespace member call.
|
|
32
|
+
*
|
|
33
|
+
* Exported (ruling 19): `wrapper-folding.ts`'s opaque-callee identity-fold
|
|
34
|
+
* step shares this EXACT predicate for its parallel "is this a data-method
|
|
35
|
+
* render call, not a wrapper" decision — never re-derive it there. Takes
|
|
36
|
+
* plain params (not this module's private `Ctx`) so a caller outside this
|
|
37
|
+
* file doesn't need to construct one. */
|
|
38
|
+
export declare function isImportBackedLeaf(t: InferredType, graph: Graph, fileGraph: FileGraph, guard: CycleGuard): boolean;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createArgumentMap } from "./argument-map.js";
|
|
2
|
+
import { createCycleGuard } from "./cycle-detection.js";
|
|
3
|
+
import { fileGraphForRef, resolveReference } from "./resolve-reference.js";
|
|
4
|
+
import { resolveToFunctions, resolveType, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
|
|
5
|
+
import { assertNever } from "./assert-never.js";
|
|
6
|
+
export function evalKind(type, graph, fileGraph, argMap = createArgumentMap(), guard = createCycleGuard()) {
|
|
7
|
+
return evalInner(type, { graph, fileGraph, argMap, guard });
|
|
8
|
+
}
|
|
9
|
+
export function isComponentShaped(type, graph, fileGraph) {
|
|
10
|
+
return evalKind(type, graph, fileGraph) === "component";
|
|
11
|
+
}
|
|
12
|
+
/** Priority merge for fan-out shapes: jsx > component > unresolved > other. */
|
|
13
|
+
function merge(kinds) {
|
|
14
|
+
if (kinds.includes("jsx"))
|
|
15
|
+
return "jsx";
|
|
16
|
+
if (kinds.includes("component"))
|
|
17
|
+
return "component";
|
|
18
|
+
if (kinds.includes("unresolved"))
|
|
19
|
+
return "unresolved";
|
|
20
|
+
return "other";
|
|
21
|
+
}
|
|
22
|
+
function evalInner(t, ctx) {
|
|
23
|
+
if (ctx.guard.pushNode(t) === "cycle")
|
|
24
|
+
return "other";
|
|
25
|
+
try {
|
|
26
|
+
switch (t.kind) {
|
|
27
|
+
case "JSX":
|
|
28
|
+
return "jsx";
|
|
29
|
+
case "Function":
|
|
30
|
+
return t.returns.some((r) => evalInner(r, ctx) === "jsx") ? "component" : "other";
|
|
31
|
+
case "Union":
|
|
32
|
+
return merge(t.types.map((u) => evalInner(u, ctx)));
|
|
33
|
+
case "ParameterOf": {
|
|
34
|
+
const bound = ctx.argMap.lookup(t.fn, t.index);
|
|
35
|
+
return bound ? evalInner(bound, ctx) : "other";
|
|
36
|
+
}
|
|
37
|
+
case "TypeOf": {
|
|
38
|
+
const resolved = resolveReference(ctx.graph, ctx.fileGraph, t.ref, ctx.guard);
|
|
39
|
+
if (resolved.kind === "Unknown")
|
|
40
|
+
return isImportBacked(t, ctx.graph, ctx.fileGraph) ? "unresolved" : "other";
|
|
41
|
+
return evalInner(resolved, ctx);
|
|
42
|
+
}
|
|
43
|
+
case "MemberOf": {
|
|
44
|
+
const objs = resolveType(ctx.graph, ctx.fileGraph, t.obj, ctx.argMap, ctx.guard);
|
|
45
|
+
const kinds = [];
|
|
46
|
+
for (const { type: obj } of objs) {
|
|
47
|
+
if (obj.kind !== "Object")
|
|
48
|
+
continue;
|
|
49
|
+
// Ruling 16(iii): a dynamic-key access (`BannerComponent[type]`)
|
|
50
|
+
// could select any prop at runtime — merge the shape of every
|
|
51
|
+
// prop the object carries, the same "any branch could be it"
|
|
52
|
+
// treatment Union already gets.
|
|
53
|
+
if (t.member === DYNAMIC_MEMBER_KEY) {
|
|
54
|
+
for (const prop of Object.values(obj.props))
|
|
55
|
+
kinds.push(evalInner(prop, ctx));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const prop = obj.props[t.member];
|
|
59
|
+
if (prop)
|
|
60
|
+
kinds.push(evalInner(prop, ctx));
|
|
61
|
+
}
|
|
62
|
+
return kinds.length === 0 ? "other" : merge(kinds);
|
|
63
|
+
}
|
|
64
|
+
case "ReturnTypeOf":
|
|
65
|
+
return evalCall(t, ctx);
|
|
66
|
+
case "Str":
|
|
67
|
+
case "Object":
|
|
68
|
+
case "Array":
|
|
69
|
+
case "Unknown":
|
|
70
|
+
case "DynamicImport":
|
|
71
|
+
// A literal array of JSX elements is deliberately "other", the same
|
|
72
|
+
// treatment a Function returning Object-containing-JSX gets ("data
|
|
73
|
+
// factory"): helper-callers.ts's helper/component split (D11) depends
|
|
74
|
+
// on a Function returning Array<JSX> classifying as a helper, not a
|
|
75
|
+
// component, so its callers attribute correctly via helper-call
|
|
76
|
+
// chains instead of it becoming a standalone identity. Ruling 16's
|
|
77
|
+
// ".map(render)" and dynamic-member patterns don't need this arm —
|
|
78
|
+
// both resolve to "jsx" through evalCall's opaque-MemberOf-callee
|
|
79
|
+
// rule and the MemberOf DYNAMIC_MEMBER_KEY merge instead.
|
|
80
|
+
return "other";
|
|
81
|
+
default:
|
|
82
|
+
return assertNever(t);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
ctx.guard.popNode(t);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function evalCall(t, ctx) {
|
|
90
|
+
// `lazy(() => import('./x'))` and friends: the engine's lazy-import rule
|
|
91
|
+
// owns identity; for shape purposes the call is a component.
|
|
92
|
+
if (t.args.some(containsDynamicImport))
|
|
93
|
+
return "component";
|
|
94
|
+
const fns = resolveToFunctions(ctx.graph, ctx.fileGraph, t.callee, ctx.argMap, ctx.guard);
|
|
95
|
+
const resolvable = [];
|
|
96
|
+
for (const f of fns) {
|
|
97
|
+
if (f.fn.kind === "Function")
|
|
98
|
+
resolvable.push(f);
|
|
99
|
+
}
|
|
100
|
+
if (resolvable.length === 0) {
|
|
101
|
+
// Opaque callee (D9 arm 3 / D7): component iff some argument is
|
|
102
|
+
// component-shaped or an import-backed leaf the engine will fold to.
|
|
103
|
+
const carries = t.args.some((a) => evalInner(a, ctx) === "component" || isImportBackedLeaf(a, ctx.graph, ctx.fileGraph, ctx.guard));
|
|
104
|
+
if (!carries)
|
|
105
|
+
return "unresolved";
|
|
106
|
+
// Ruling 16(ii): a method call on non-import-backed data — a parameter,
|
|
107
|
+
// a local array/object literal, a hook result, a global like
|
|
108
|
+
// `Object.keys(x)` — with a component-shaped argument is a *render
|
|
109
|
+
// callback* (`items.map(({...}) => <Button/>)`), not a wrapper that
|
|
110
|
+
// takes a component as a prop: the call's OUTPUT is rendered JSX, not a
|
|
111
|
+
// component. An import-backed callee (the actual unresolvable-HOC case
|
|
112
|
+
// D9 arm 3 exists for) keeps "component". Ruling 19: wrapper-folding.ts's
|
|
113
|
+
// opaque step shares this exact condition (isImportBackedLeaf, exported
|
|
114
|
+
// below) for the parallel identity-folding decision — don't re-derive it.
|
|
115
|
+
if (t.callee.kind === "MemberOf" && !isImportBackedLeaf(t.callee, ctx.graph, ctx.fileGraph, ctx.guard))
|
|
116
|
+
return "jsx";
|
|
117
|
+
return "component";
|
|
118
|
+
}
|
|
119
|
+
const kinds = [];
|
|
120
|
+
for (const { fn, bindingRef } of resolvable) {
|
|
121
|
+
if (fn.kind !== "Function")
|
|
122
|
+
continue;
|
|
123
|
+
if (bindingRef)
|
|
124
|
+
ctx.argMap.bind(bindingRef, t.args);
|
|
125
|
+
try {
|
|
126
|
+
for (const ret of fn.returns)
|
|
127
|
+
kinds.push(evalInner(ret, ctx));
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
if (bindingRef)
|
|
131
|
+
ctx.argMap.unbind(bindingRef);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return merge(kinds);
|
|
135
|
+
}
|
|
136
|
+
function isImportBacked(t, graph, fileGraph) {
|
|
137
|
+
return fileGraphForRef(graph, t.ref, fileGraph).importsByLocal.has(t.ref.symbol);
|
|
138
|
+
}
|
|
139
|
+
/** A TypeOf (possibly under a ReturnTypeOf chain, or a MemberOf access on
|
|
140
|
+
* one — `Sentry.withProfiler`) that names an import the graph cannot
|
|
141
|
+
* resolve — the shape the opaque-HOC fold synthesises identity from (#274).
|
|
142
|
+
* Unwrapping MemberOf.obj is what lets Ruling 16(ii)'s render-callback
|
|
143
|
+
* narrowing correctly except FOR an import-backed namespace member call.
|
|
144
|
+
*
|
|
145
|
+
* Exported (ruling 19): `wrapper-folding.ts`'s opaque-callee identity-fold
|
|
146
|
+
* step shares this EXACT predicate for its parallel "is this a data-method
|
|
147
|
+
* render call, not a wrapper" decision — never re-derive it there. Takes
|
|
148
|
+
* plain params (not this module's private `Ctx`) so a caller outside this
|
|
149
|
+
* file doesn't need to construct one. */
|
|
150
|
+
export function isImportBackedLeaf(t, graph, fileGraph, guard) {
|
|
151
|
+
let cur = t;
|
|
152
|
+
while (cur.kind === "ReturnTypeOf" || cur.kind === "MemberOf") {
|
|
153
|
+
cur = cur.kind === "ReturnTypeOf" ? cur.callee : cur.obj;
|
|
154
|
+
}
|
|
155
|
+
if (cur.kind !== "TypeOf")
|
|
156
|
+
return false;
|
|
157
|
+
if (!isImportBacked(cur, graph, fileGraph))
|
|
158
|
+
return false;
|
|
159
|
+
return resolveReference(graph, fileGraph, cur.ref, guard).kind === "Unknown";
|
|
160
|
+
}
|
|
161
|
+
function containsDynamicImport(t) {
|
|
162
|
+
switch (t.kind) {
|
|
163
|
+
case "DynamicImport":
|
|
164
|
+
return true;
|
|
165
|
+
case "Function":
|
|
166
|
+
return t.returns.some(containsDynamicImport);
|
|
167
|
+
case "ReturnTypeOf":
|
|
168
|
+
return containsDynamicImport(t.callee) || t.args.some(containsDynamicImport);
|
|
169
|
+
case "MemberOf":
|
|
170
|
+
return containsDynamicImport(t.obj);
|
|
171
|
+
case "Union":
|
|
172
|
+
return t.types.some(containsDynamicImport);
|
|
173
|
+
default:
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=component-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-shape.js","sourceRoot":"","sources":["../../src/engine/component-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,33 +1,21 @@
|
|
|
1
|
-
import type { BindingDecl, Graph,
|
|
2
|
-
import { type CycleGuard } from "./cycle-detection.js";
|
|
3
|
-
export type Classification = "component" | "helper" | "unknown";
|
|
1
|
+
import type { BindingDecl, FileGraph, Graph, Reference } from "../index.js";
|
|
4
2
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* caller preserves today's behavior (no helper reassignment).
|
|
10
|
-
*
|
|
11
|
-
* NOTE: an `originFile` argument is required when the decl's value contains
|
|
12
|
-
* cross-module type refs (TypeOf / ReturnTypeOf with refs), so that
|
|
13
|
-
* `resolveReference` knows which FileGraph to look up scope chains against.
|
|
3
|
+
* Walk the scope chain for `ref.symbol` in `fileGraph.declarations` and
|
|
4
|
+
* return the first matching BindingDecl, or null if not found. THE
|
|
5
|
+
* scope-chain walk (I4) — `registry.ts`'s `resolveUsageTarget` calls this
|
|
6
|
+
* seam instead of re-deriving it.
|
|
14
7
|
*/
|
|
15
|
-
export declare function
|
|
8
|
+
export declare function findLocalDeclaration(fileGraph: FileGraph, ref: Reference): BindingDecl | null;
|
|
9
|
+
export type Classification = "component" | "helper" | "unknown";
|
|
16
10
|
/**
|
|
17
|
-
*
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* MemberOf<obj, member> where resolved has JSX → true (recursive)
|
|
24
|
-
* TypeOf<ref> where resolved value has JSX → true (recursive, cross-file)
|
|
25
|
-
* Object / Array / Str / Unknown / etc. → false
|
|
26
|
-
*
|
|
27
|
-
* The `Object{cta: JSX}` and `Array<{x: JSX}>` cases deliberately return false
|
|
28
|
-
* — that's what classifies data factories and JSX-in-container hooks as helpers.
|
|
11
|
+
* Component-vs-helper classification for owner attribution. Delegates to
|
|
12
|
+
* THE component-shape predicate (`evalKind`, spec D11) — this file no longer
|
|
13
|
+
* carries its own JSX-bearing walk. A Function or call that is not
|
|
14
|
+
* component-shaped is a "helper" (JSX inside it fans out to its callers);
|
|
15
|
+
* plain values stay "unknown" so `resolveOwnerChain` treats them as it
|
|
16
|
+
* always has.
|
|
29
17
|
*/
|
|
30
|
-
export declare function
|
|
18
|
+
export declare function classifyDeclaration(decl: BindingDecl, graph: Graph, originFile?: string): Classification;
|
|
31
19
|
export type SymbolKey = {
|
|
32
20
|
file: string;
|
|
33
21
|
symbol: string;
|
|
@@ -42,8 +30,10 @@ export type ClassifiedHelperIndex = HelperCallerIndex & {
|
|
|
42
30
|
* Three-phase build of the helper → component-callers reverse index plus
|
|
43
31
|
* per-decl classification cache.
|
|
44
32
|
*
|
|
45
|
-
* Phase 1 (collect) walks every module-scope `Function`-
|
|
46
|
-
* across the graph
|
|
33
|
+
* Phase 1 (collect) walks every module-scope `Function`- or
|
|
34
|
+
* `ReturnTypeOf`-valued BindingDecl across the graph (the latter is a
|
|
35
|
+
* factory's product, spec D13), collecting caller-side references from
|
|
36
|
+
* three sources:
|
|
47
37
|
*
|
|
48
38
|
* 1. `TypeOf` refs inside the decl's return-type expressions
|
|
49
39
|
* (`decl.value.returns`) — captures `() => helper()` shapes where the
|
|
@@ -61,7 +51,7 @@ export type ClassifiedHelperIndex = HelperCallerIndex & {
|
|
|
61
51
|
* (the dominant React call shape) never appear as called by anyone.
|
|
62
52
|
*
|
|
63
53
|
* Phase 2 (classify) runs structural classification on every module-scope
|
|
64
|
-
* Function decl, with a reclassification gate: a decl classified as
|
|
54
|
+
* Function- or ReturnTypeOf-valued decl, with a reclassification gate: a decl classified as
|
|
65
55
|
* `"component"` but consumed only as a value (never JSX) is reclassified
|
|
66
56
|
* as `"helper"`.
|
|
67
57
|
*
|
|
@@ -74,3 +64,15 @@ export type ClassifiedHelperIndex = HelperCallerIndex & {
|
|
|
74
64
|
* resolution uses the existing module resolver — no new IO.
|
|
75
65
|
*/
|
|
76
66
|
export declare function buildHelperCallers(graph: Graph): ClassifiedHelperIndex;
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a Reference to the BindingDecl it ultimately points at, crossing
|
|
69
|
+
* files via imports + the module resolver. Returns `{file, decl}` keyed by
|
|
70
|
+
* the target file (not the import-bearing file) so caller-set membership
|
|
71
|
+
* dedupes across multiple import paths.
|
|
72
|
+
*
|
|
73
|
+
* Returns null for unresolved or external refs.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveRefToDecl(ref: Reference, callerFile: string, callerGraph: FileGraph, graph: Graph): {
|
|
76
|
+
file: string;
|
|
77
|
+
decl: BindingDecl;
|
|
78
|
+
} | null;
|
|
@@ -1,82 +1,68 @@
|
|
|
1
1
|
import { MODULE_SCOPE } from "../index.js";
|
|
2
|
-
import {
|
|
3
|
-
import { createCycleGuard } from "./cycle-detection.js";
|
|
2
|
+
import { evalKind } from "./component-shape.js";
|
|
4
3
|
import { followReExportChain } from "./re-export-chain.js";
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* caller preserves today's behavior (no helper reassignment).
|
|
11
|
-
*
|
|
12
|
-
* NOTE: an `originFile` argument is required when the decl's value contains
|
|
13
|
-
* cross-module type refs (TypeOf / ReturnTypeOf with refs), so that
|
|
14
|
-
* `resolveReference` knows which FileGraph to look up scope chains against.
|
|
5
|
+
* Walk the scope chain for `ref.symbol` in `fileGraph.declarations` and
|
|
6
|
+
* return the first matching BindingDecl, or null if not found. THE
|
|
7
|
+
* scope-chain walk (I4) — `registry.ts`'s `resolveUsageTarget` calls this
|
|
8
|
+
* seam instead of re-deriving it.
|
|
15
9
|
*/
|
|
16
|
-
export function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
10
|
+
export function findLocalDeclaration(fileGraph, ref) {
|
|
11
|
+
let scope = ref.scope;
|
|
12
|
+
while (true) {
|
|
13
|
+
const decl = fileGraph.declarations.get(`${scope}::${ref.symbol}`);
|
|
14
|
+
if (decl)
|
|
15
|
+
return decl;
|
|
16
|
+
if (scope === MODULE_SCOPE)
|
|
17
|
+
return null;
|
|
18
|
+
const parent = fileGraph.scopes.get(scope)?.parent;
|
|
19
|
+
if (parent === null || parent === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
scope = parent;
|
|
23
22
|
}
|
|
24
|
-
return "helper";
|
|
25
23
|
}
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* MemberOf<obj, member> where resolved has JSX → true (recursive)
|
|
34
|
-
* TypeOf<ref> where resolved value has JSX → true (recursive, cross-file)
|
|
35
|
-
* Object / Array / Str / Unknown / etc. → false
|
|
36
|
-
*
|
|
37
|
-
* The `Object{cta: JSX}` and `Array<{x: JSX}>` cases deliberately return false
|
|
38
|
-
* — that's what classifies data factories and JSX-in-container hooks as helpers.
|
|
25
|
+
* Component-vs-helper classification for owner attribution. Delegates to
|
|
26
|
+
* THE component-shape predicate (`evalKind`, spec D11) — this file no longer
|
|
27
|
+
* carries its own JSX-bearing walk. A Function or call that is not
|
|
28
|
+
* component-shaped is a "helper" (JSX inside it fans out to its callers);
|
|
29
|
+
* plain values stay "unknown" so `resolveOwnerChain` treats them as it
|
|
30
|
+
* always has.
|
|
39
31
|
*/
|
|
40
|
-
export function
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
guard.pop(effectiveFile, t.ref.symbol);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
case "MemberOf":
|
|
69
|
-
return hasJsx(t.obj, graph, originFile, guard);
|
|
70
|
-
default:
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
32
|
+
export function classifyDeclaration(decl, graph, originFile) {
|
|
33
|
+
const fileGraph = (originFile !== undefined ? graph.files.get(originFile) : undefined) ?? syntheticFileGraph(originFile);
|
|
34
|
+
const kind = evalKind(decl.value, graph, fileGraph);
|
|
35
|
+
if (kind === "component")
|
|
36
|
+
return "component";
|
|
37
|
+
if (decl.value.kind === "Function" || decl.value.kind === "ReturnTypeOf")
|
|
38
|
+
return "helper";
|
|
39
|
+
return "unknown";
|
|
40
|
+
}
|
|
41
|
+
/** Tests call `classifyDeclaration(decl, graph)` with no file; give the
|
|
42
|
+
* evaluator an empty FileGraph to resolve against. */
|
|
43
|
+
function syntheticFileGraph(filePath) {
|
|
44
|
+
return {
|
|
45
|
+
filePath: filePath ?? "",
|
|
46
|
+
dialect: "react",
|
|
47
|
+
scopes: new Map([[MODULE_SCOPE, { parent: null }]]),
|
|
48
|
+
declarations: new Map(),
|
|
49
|
+
imports: [],
|
|
50
|
+
importsByLocal: new Map(),
|
|
51
|
+
exports: [],
|
|
52
|
+
jsxUsages: [],
|
|
53
|
+
tagUsages: [],
|
|
54
|
+
ownership: [],
|
|
55
|
+
bodyCalls: [],
|
|
56
|
+
};
|
|
73
57
|
}
|
|
74
58
|
/**
|
|
75
59
|
* Three-phase build of the helper → component-callers reverse index plus
|
|
76
60
|
* per-decl classification cache.
|
|
77
61
|
*
|
|
78
|
-
* Phase 1 (collect) walks every module-scope `Function`-
|
|
79
|
-
* across the graph
|
|
62
|
+
* Phase 1 (collect) walks every module-scope `Function`- or
|
|
63
|
+
* `ReturnTypeOf`-valued BindingDecl across the graph (the latter is a
|
|
64
|
+
* factory's product, spec D13), collecting caller-side references from
|
|
65
|
+
* three sources:
|
|
80
66
|
*
|
|
81
67
|
* 1. `TypeOf` refs inside the decl's return-type expressions
|
|
82
68
|
* (`decl.value.returns`) — captures `() => helper()` shapes where the
|
|
@@ -94,7 +80,7 @@ export function hasJsx(t, graph, originFile, guard) {
|
|
|
94
80
|
* (the dominant React call shape) never appear as called by anyone.
|
|
95
81
|
*
|
|
96
82
|
* Phase 2 (classify) runs structural classification on every module-scope
|
|
97
|
-
* Function decl, with a reclassification gate: a decl classified as
|
|
83
|
+
* Function- or ReturnTypeOf-valued decl, with a reclassification gate: a decl classified as
|
|
98
84
|
* `"component"` but consumed only as a value (never JSX) is reclassified
|
|
99
85
|
* as `"helper"`.
|
|
100
86
|
*
|
|
@@ -131,11 +117,27 @@ export function buildHelperCallers(graph) {
|
|
|
131
117
|
for (const decl of fileGraph.declarations.values()) {
|
|
132
118
|
if (decl.scope !== MODULE_SCOPE)
|
|
133
119
|
continue;
|
|
134
|
-
if (decl.value.kind !== "Function")
|
|
135
|
-
continue;
|
|
136
120
|
const refs = [];
|
|
137
|
-
|
|
138
|
-
|
|
121
|
+
if (decl.value.kind === "Function") {
|
|
122
|
+
for (const r of decl.value.returns)
|
|
123
|
+
collectRefsInType(r, refs);
|
|
124
|
+
}
|
|
125
|
+
else if (decl.value.kind === "ReturnTypeOf") {
|
|
126
|
+
// A factory's product (`const Foo = withLoading(FooView)`) is a
|
|
127
|
+
// caller of the factory (spec D13), so JSX inside the factory's body
|
|
128
|
+
// fans out to each product. Only the CALLEE chain contributes — an
|
|
129
|
+
// argument is the thing being wrapped, not a value the product
|
|
130
|
+
// consumes, and must not feed the reclassification gate. Restricted
|
|
131
|
+
// to `ReturnTypeOf` specifically: D13 is about the PRODUCT OF A
|
|
132
|
+
// CALL. A bare alias (`const Item = RadioArea`, `TypeOf`) or a
|
|
133
|
+
// member alias (`const Root = Primitive.Root`, `MemberOf`) is not a
|
|
134
|
+
// call — running `collectCalleeRefs` on those would index the alias
|
|
135
|
+
// as a caller of what it aliases, flip the aliased declaration's
|
|
136
|
+
// classification to "helper" via the reclassification rule, and
|
|
137
|
+
// fan its JSX out to the alias instead of leaving it owned by the
|
|
138
|
+
// original declaration (found via the cal.diy real-consumer diff).
|
|
139
|
+
collectCalleeRefs(decl.value, refs);
|
|
140
|
+
}
|
|
139
141
|
for (const bc of fileGraph.bodyCalls) {
|
|
140
142
|
if (bc.ownerSymbol === decl.symbol)
|
|
141
143
|
refs.push(bc.callee);
|
|
@@ -183,8 +185,7 @@ export function buildHelperCallers(graph) {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
// ── Phase 2: classify ───────────────────────────────────────────────────
|
|
186
|
-
// Structural classification, then reclassification gate.
|
|
187
|
-
// in at task 3; today it's a no-op because we already cache the result.)
|
|
188
|
+
// Structural classification, then reclassification gate.
|
|
188
189
|
function classifyKey(file, decl) {
|
|
189
190
|
const key = `${file}::${decl.symbol}`;
|
|
190
191
|
const hit = classificationCache.get(key);
|
|
@@ -199,14 +200,15 @@ export function buildHelperCallers(graph) {
|
|
|
199
200
|
classificationCache.set(key, result);
|
|
200
201
|
return result;
|
|
201
202
|
}
|
|
202
|
-
// Walk every module-scope Function-valued decl so its
|
|
203
|
-
// in the cache before resolveOwnerChain runs
|
|
204
|
-
//
|
|
203
|
+
// Walk every module-scope Function- or ReturnTypeOf-valued decl so its
|
|
204
|
+
// classification lands in the cache before resolveOwnerChain runs (a
|
|
205
|
+
// factory's product is ReturnTypeOf-valued — spec D13). Other decls
|
|
206
|
+
// classify lazily via the same path.
|
|
205
207
|
for (const [file, fileGraph] of graph.files) {
|
|
206
208
|
for (const decl of fileGraph.declarations.values()) {
|
|
207
209
|
if (decl.scope !== MODULE_SCOPE)
|
|
208
210
|
continue;
|
|
209
|
-
if (decl.value.kind !== "Function")
|
|
211
|
+
if (decl.value.kind !== "Function" && decl.value.kind !== "ReturnTypeOf")
|
|
210
212
|
continue;
|
|
211
213
|
classifyKey(file, decl);
|
|
212
214
|
}
|
|
@@ -249,6 +251,24 @@ export function buildHelperCallers(graph) {
|
|
|
249
251
|
},
|
|
250
252
|
};
|
|
251
253
|
}
|
|
254
|
+
/** Walk only the callee side of a value: `ReturnTypeOf.callee` chains and
|
|
255
|
+
* `MemberOf.obj`, collecting the `TypeOf` refs that name what is being
|
|
256
|
+
* called. Arguments are deliberately skipped (see Pass A). */
|
|
257
|
+
function collectCalleeRefs(t, out) {
|
|
258
|
+
switch (t.kind) {
|
|
259
|
+
case "ReturnTypeOf":
|
|
260
|
+
collectCalleeRefs(t.callee, out);
|
|
261
|
+
return;
|
|
262
|
+
case "MemberOf":
|
|
263
|
+
collectCalleeRefs(t.obj, out);
|
|
264
|
+
return;
|
|
265
|
+
case "TypeOf":
|
|
266
|
+
out.push(t.ref);
|
|
267
|
+
return;
|
|
268
|
+
default:
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
252
272
|
/** Walk an InferredType, collecting every `TypeOf` ref found at any depth. */
|
|
253
273
|
function collectRefsInType(t, out) {
|
|
254
274
|
switch (t.kind) {
|
|
@@ -295,7 +315,7 @@ function collectRefsInType(t, out) {
|
|
|
295
315
|
*
|
|
296
316
|
* Returns null for unresolved or external refs.
|
|
297
317
|
*/
|
|
298
|
-
function resolveRefToDecl(ref, callerFile, callerGraph, graph) {
|
|
318
|
+
export function resolveRefToDecl(ref, callerFile, callerGraph, graph) {
|
|
299
319
|
// 1. Try the local declaration first.
|
|
300
320
|
const localKey = `${MODULE_SCOPE}::${ref.symbol}`;
|
|
301
321
|
const localDecl = callerGraph.declarations.get(localKey);
|