@component-compass/reference-graph 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/engine/binding.d.ts +96 -0
  2. package/dist/engine/binding.js +405 -0
  3. package/dist/engine/binding.js.map +1 -0
  4. package/dist/engine/component-shape.d.ts +26 -2
  5. package/dist/engine/component-shape.js +74 -11
  6. package/dist/engine/component-shape.js.map +1 -1
  7. package/dist/engine/denotation.d.ts +7 -0
  8. package/dist/engine/denotation.js +15 -0
  9. package/dist/engine/denotation.js.map +1 -1
  10. package/dist/engine/helper-callers.d.ts +0 -7
  11. package/dist/engine/helper-callers.js +2 -23
  12. package/dist/engine/helper-callers.js.map +1 -1
  13. package/dist/engine/index.d.ts +1 -1
  14. package/dist/engine/index.js +259 -308
  15. package/dist/engine/index.js.map +1 -1
  16. package/dist/engine/member-identity.d.ts +7 -19
  17. package/dist/engine/member-identity.js +13 -19
  18. package/dist/engine/member-identity.js.map +1 -1
  19. package/dist/engine/re-export-chain.d.ts +1 -1
  20. package/dist/engine/re-export-chain.js +6 -6
  21. package/dist/engine/re-export-chain.js.map +1 -1
  22. package/dist/engine/registry.d.ts +52 -13
  23. package/dist/engine/registry.js +73 -58
  24. package/dist/engine/registry.js.map +1 -1
  25. package/dist/engine/resolve-reference.d.ts +2 -21
  26. package/dist/engine/resolve-reference.js +4 -178
  27. package/dist/engine/resolve-reference.js.map +1 -1
  28. package/dist/engine/resolve-type.d.ts +7 -7
  29. package/dist/engine/resolve-type.js +23 -29
  30. package/dist/engine/resolve-type.js.map +1 -1
  31. package/dist/engine/wrapper-folding.d.ts +18 -22
  32. package/dist/engine/wrapper-folding.js +147 -282
  33. package/dist/engine/wrapper-folding.js.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/package.json +1 -1
@@ -1,15 +1,13 @@
1
1
  import { resolveType, resolveToFunctions, resolveDynamicImportTarget, DYNAMIC_MEMBER_KEY } from "./resolve-type.js";
2
2
  import { argumentRefKey, boundArgument } from "./argument-map.js";
3
3
  import { createCycleGuard } from "./cycle-detection.js";
4
- import { fileGraphForRef, resolveReference } from "./resolve-reference.js";
4
+ import { resolveReference } from "./resolve-reference.js";
5
+ import { bindingIdentity, pinUnparsed, resolveBinding, resolveModuleExport } from "./binding.js";
5
6
  import { packageNameFromSpecifier } from "./specifier.js";
6
- import { followReExportChain } from "./re-export-chain.js";
7
- import { unparsedMemberDefinition, effectiveExportName, residualMemberChain, compoundExportName } from "./member-identity.js";
8
- import { evalKind, isImportBackedLeaf } from "./component-shape.js";
7
+ import { containsDynamicImport, evalKind, isImportBackedLeaf, nonComponentProduct } from "./component-shape.js";
9
8
  import { HOOK_NAME } from "./host-element.js";
10
9
  import { assertNever } from "./assert-never.js";
11
- import { creditedTerminals, matchDenotation } from "./denotation.js";
12
- import { libraryExportFor } from "./library-stubs.js";
10
+ import { creditedTerminals, isCallable, matchDenotation } from "./denotation.js";
13
11
  /** True when a `ReturnTypeOf`'s callee is a data-method render
14
12
  * call (`items.map(render)`) rather than a wrapper taking a component as a
15
13
  * prop — the exact structural condition `component-shape.ts`'s rule (ii)
@@ -19,6 +17,49 @@ import { libraryExportFor } from "./library-stubs.js";
19
17
  function isDataMethodRenderCall(type, graph, fileGraph, guard) {
20
18
  return type.callee.kind === "MemberOf" && !isImportBackedLeaf(type.callee, graph, fileGraph, guard);
21
19
  }
20
+ /** True when a value is a loader: a function whose body holds an `import()`,
21
+ * inline, or reached from a reference through a chain of aliases, or held by
22
+ * a static member of an object literal (by every member, for a dynamic one)
23
+ * that the value names directly or through such a chain. Each reference
24
+ * resolves in its own file. Anything else, a `lazy()` holder or a wrapper
25
+ * product included, is not. */
26
+ function isLoader(value, graph, fileGraph, guard) {
27
+ if (value.kind === "Function")
28
+ return containsDynamicImport(value, { graph, fileGraph });
29
+ if (value.kind !== "TypeOf" && value.kind !== "MemberOf")
30
+ return false;
31
+ if (guard.pushNode(value) === "cycle")
32
+ return false;
33
+ try {
34
+ if (value.kind === "TypeOf")
35
+ return isLoader(resolveReference(graph, fileGraph, value.ref, guard), graph, fileGraph, guard);
36
+ const holder = objectLiteralOf(value.obj, graph, fileGraph, guard);
37
+ if (holder === null)
38
+ return false;
39
+ if (value.member !== DYNAMIC_MEMBER_KEY) {
40
+ const held = holder.props[value.member];
41
+ return held !== undefined && isLoader(held, graph, fileGraph, guard);
42
+ }
43
+ const all = Object.values(holder.props);
44
+ return all.length > 0 && all.every((v) => isLoader(v, graph, fileGraph, guard));
45
+ }
46
+ finally {
47
+ guard.popNode(value);
48
+ }
49
+ }
50
+ /** The object literal a value is, directly or through a chain of aliases. */
51
+ function objectLiteralOf(value, graph, fileGraph, guard) {
52
+ if (value.kind === "Object")
53
+ return value;
54
+ if (value.kind !== "TypeOf" || guard.pushNode(value) === "cycle")
55
+ return null;
56
+ try {
57
+ return objectLiteralOf(resolveReference(graph, fileGraph, value.ref, guard), graph, fileGraph, guard);
58
+ }
59
+ finally {
60
+ guard.popNode(value);
61
+ }
62
+ }
22
63
  /** The denotation of a branch the walk learned nothing about. */
23
64
  const INDETERMINATE = { kind: "indeterminate" };
24
65
  function withSemantics(t, semantics) {
@@ -47,6 +88,7 @@ const CAME_TO_NOTHING = {
47
88
  "late-bound": () => true,
48
89
  data: () => false,
49
90
  indeterminate: () => true,
91
+ callable: () => false,
50
92
  };
51
93
  function cameToNothing(t) {
52
94
  return matchDenotation(t, CAME_TO_NOTHING);
@@ -59,6 +101,7 @@ const IS_LATE_BOUND = {
59
101
  "late-bound": () => true,
60
102
  data: () => false,
61
103
  indeterminate: () => false,
104
+ callable: () => false,
62
105
  };
63
106
  /**
64
107
  * Rename a branch that came to nothing, keeping its identity and viaTrail
@@ -95,6 +138,7 @@ const AS_RETURNED_VALUE = {
95
138
  "late-bound": () => INDETERMINATE,
96
139
  data: (d) => d,
97
140
  indeterminate: (d) => d,
141
+ callable: (d) => d,
98
142
  };
99
143
  function returnedValue(t) {
100
144
  const denotation = matchDenotation(t, AS_RETURNED_VALUE);
@@ -223,8 +267,8 @@ function foldToWrappedArg(inner, via) {
223
267
  * is seeded at its site), so the two name a binding identically.
224
268
  * Never re-derive `{ specifier, import }` for an argument elsewhere.
225
269
  */
226
- export function argumentProvenance(type, arg, fileGraph) {
227
- const importRecord = findImportForLastArg(arg, fileGraph);
270
+ export function argumentProvenance(graph, type, arg, fileGraph) {
271
+ const importRecord = findImportForLastArg(graph, arg, fileGraph);
228
272
  const resolved = importRecord
229
273
  ? { specifier: importRecord.specifier, import: importRecord.imported }
230
274
  : resolveAnonymousLastArg(arg, fileGraph);
@@ -236,8 +280,8 @@ export function argumentProvenance(type, arg, fileGraph) {
236
280
  * the opaque HOC fallback (step 4) so all three paths attribute composition
237
281
  * identically; the binding is named through `argumentProvenance`.
238
282
  */
239
- function buildHocVia(type, lastArg, fileGraph) {
240
- const { callee, specifier, import: imported } = argumentProvenance(type, lastArg, fileGraph);
283
+ function buildHocVia(graph, type, lastArg, fileGraph) {
284
+ const { callee, specifier, import: imported } = argumentProvenance(graph, type, lastArg, fileGraph);
241
285
  return { kind: "hoc-wrapper", hocCallee: callee, specifier, import: imported };
242
286
  }
243
287
  /**
@@ -254,7 +298,7 @@ export function foldedArgument(graph, fileGraph, call, jsx, argMap) {
254
298
  const arg = call.args[index];
255
299
  if (arg === undefined || jsx.length === 0)
256
300
  return null;
257
- const via = buildHocVia(call, arg, fileGraph);
301
+ const via = buildHocVia(graph, call, arg, fileGraph);
258
302
  return jsx.every((t) => t.viaTrail[0] !== undefined && sameHocVia(t.viaTrail[0], via)) ? { arg, via } : null;
259
303
  }
260
304
  /** Two `hoc-wrapper` vias naming the same wrapper call and the same binding. */
@@ -304,14 +348,21 @@ function isPassThroughWrapper(algResult, inner) {
304
348
  * (pure algebra) and computes identity from the input type's leaf TypeOf
305
349
  * when one can be confidently identified.
306
350
  */
307
- export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(), identityFallback) {
308
- return atLeastOne(walkInner(graph, fileGraph, type, argMap, guard, identityFallback).map(published));
351
+ export function walkWithFolding(graph, fileGraph, type, argMap, guard = createCycleGuard(), identityFallback, position = "value") {
352
+ return atLeastOne(walkInner(graph, fileGraph, type, argMap, guard, identityFallback, position).map(published));
309
353
  }
310
354
  /**
311
355
  * The walk itself. Returns exactly what each arm produced, empty included:
312
356
  * inside the walk an empty result means "this branch contributed nothing",
313
357
  * which `walkTypeOf`'s vacuous `every` and `foldToWrappedArg`'s rescue both
314
358
  * read. Only the exported boundary above may substitute a terminal for it.
359
+ *
360
+ * `position` says whether `type` is what a tag renders. At a `tag`, a
361
+ * function is one `callable` terminal and its body is not walked; the
362
+ * position carries through the value hops (a reference, a union branch, a
363
+ * static or dynamic member, a call's folded argument, a lazy target, a
364
+ * dispatcher's returns) and nowhere else. In `value` position a function is
365
+ * walked into its returns.
315
366
  */
316
367
  function walkInner(graph, fileGraph, type, argMap, guard,
317
368
  // The identity `foldTerminals` falls back to when a terminal
@@ -325,39 +376,44 @@ function walkInner(graph, fileGraph, type, argMap, guard,
325
376
  // that happens to sit in `foldTerminals`' fallback slot. Per-branch
326
377
  // identity from a fanout `source` (Union([TypeOf A, TypeOf B])) is
327
378
  // untouched: `source` is checked BEFORE this fallback in `foldTerminals`.
328
- identityFallback) {
379
+ identityFallback, position) {
329
380
  const fallback = identityFallback ?? type;
330
381
  switch (type.kind) {
331
382
  case "ReturnTypeOf":
332
- return walkReturnTypeOf(graph, fileGraph, type, argMap, guard);
383
+ return walkReturnTypeOf(graph, fileGraph, type, argMap, guard, position);
333
384
  case "MemberOf":
334
385
  if (type.member === DYNAMIC_MEMBER_KEY) {
335
- return walkDynamicMember(graph, fileGraph, type, argMap, guard);
386
+ return walkDynamicMember(graph, fileGraph, type, argMap, guard, position);
336
387
  }
337
- return walkStaticMember(graph, fileGraph, type, argMap, guard, fallback);
388
+ return walkStaticMember(graph, fileGraph, type, argMap, guard, fallback, position);
338
389
  case "Function":
339
- // A component's own returns can contain a nested
390
+ // At a tag the function is what JSX renders: one `callable` terminal
391
+ // with no identity of its own, and the body is not walked.
392
+ //
393
+ // In value position a component's own returns can contain a nested
340
394
  // ReturnTypeOf/MemberOf (`List = ({items}) => items.map(render)`) that
341
395
  // needs wrapper-folding's steps (the opaque-callee / dynamic-map
342
396
  // handling below), not `resolveType`'s plain algebra — `resolveType`'s
343
397
  // own "Function" case recurses via plain `resolveType`, which never
344
- // re-enters this walk, so a JSX usage resolving to such a Function
345
- // (e.g. `<List/>`) would silently produce zero occurrences.
398
+ // re-enters this walk, so a value resolving to such a Function would
399
+ // silently produce zero terminals.
346
400
  // Mirrors `resolveType`'s "Function" case, one level up. Pass THIS
347
401
  // Function as the identity fallback for every return — a
348
402
  // Function is never TypeOf/DynamicImport, so `deriveIdentity` on it
349
403
  // is always null.
404
+ if (position === "tag")
405
+ return [{ denotation: { kind: "callable" }, identity: null, viaTrail: [] }];
350
406
  return type.returns
351
- .flatMap((r) => walkInner(graph, fileGraph, r, argMap, guard, type))
407
+ .flatMap((r) => walkInner(graph, fileGraph, r, argMap, guard, type, "value"))
352
408
  .map(returnedValue);
353
409
  case "TypeOf":
354
- return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback);
410
+ return walkTypeOf(graph, fileGraph, type, argMap, guard, fallback, position);
355
411
  case "Union":
356
412
  // Structural re-entry: each branch walks through the walker so
357
413
  // a TypeOf branch reaches `walkTypeOf` and decides its own identity
358
414
  // (per-branch attribution) while a bare JSX branch — a lowered
359
415
  // ternary/logical render body — derives from the outer fallback.
360
- return type.types.flatMap((branch) => walkInner(graph, fileGraph, branch, argMap, guard, fallback));
416
+ return type.types.flatMap((branch) => walkInner(graph, fileGraph, branch, argMap, guard, fallback, position));
361
417
  case "ParameterOf":
362
418
  return walkParameter(graph, fileGraph, type, argMap, guard, fallback);
363
419
  case "DynamicImport":
@@ -420,28 +476,13 @@ export function fileGraphForDynamicImport(graph, di, fallback) {
420
476
  return graph.files.get(di.originFile) ?? fallback;
421
477
  }
422
478
  /**
423
- * The identity cascade for a lazy target.
424
- *
425
- * `resolvedFile` is the specifier canonicalised through the module resolver, so
426
- * two spellings of one target — `./x` from one file, `../x` from another —
427
- * carry the same `resolvedFile` and collapse to a single componentId
428
- * downstream instead of minting a phantom each. A chain that resolves into the
429
- * graph (workspace sibling, relative import, or unparsed workspace member)
430
- * promotes to `local`, hashing to the same id as the local-index seed;
431
- * everything else stays `imported`.
479
+ * The identity of a lazy target: the binding its specifier's projected export
480
+ * names from the file that contains the `import()`.
432
481
  */
433
482
  export function dynamicImportIdentity(graph, fileGraph, di) {
434
483
  const origin = fileGraphForDynamicImport(graph, di, fileGraph);
435
- const importedName = di.projection[0] ?? "default";
436
- const resolvedFile = graph ? resolveImportedFile(graph, origin, di.specifier, importedName) : null;
437
- return (workspaceSiblingIdentity(graph, origin, di.specifier, importedName) ??
438
- relativeChainLocalIdentity(graph, origin, di.specifier, importedName) ??
439
- memberLocalIdentity(graph, origin, di.specifier, importedName) ?? {
440
- kind: "imported",
441
- specifier: di.specifier,
442
- imported: importedName,
443
- resolvedFile,
444
- });
484
+ const binding = resolveModuleExport(graph, origin.filePath, di.specifier, di.projection[0] ?? "default", []);
485
+ return bindingIdentity(pinUnparsed(graph, binding));
445
486
  }
446
487
  /**
447
488
  * Structural re-entry for a reference. Resolves ONE
@@ -454,9 +495,13 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
454
495
  * Identity: a terminal that comes back with no identity of its own is
455
496
  * stamped with THIS reference's identity only when the reference names a
456
497
  * component-shaped value — `evalKind === "component"`, the one component-
457
- * shape seam, called not re-derived. A reference to a JSX value
458
- * (`return priceElement`) or to a parameter binding stays null so the outer
459
- * fallback wins, by construction rather than by special case.
498
+ * shape seam, called not re-derived — or, at a tag, when the walk came back
499
+ * with an identity-less `callable`: a reference to a function, or to a
500
+ * wrapper call over an anonymous one, names what the tag renders, whatever
501
+ * it returns. A reference to a JSX value (`return priceElement`) or to a
502
+ * parameter binding stays null so the outer fallback wins, by construction
503
+ * rather than by special case. A stamp naming a declaration with no member
504
+ * chain carries that declaration (`bindingIdentity`).
460
505
  *
461
506
  * An import the graph cannot see (`resolved` is Unknown) is NOT re-entered:
462
507
  * it is the identity-bearing leaf `foldLeaf` synthesises from, and
@@ -470,7 +515,7 @@ export function dynamicImportIdentity(graph, fileGraph, di) {
470
515
  * Cycle guard: mirrors `resolveType`'s node guard. `const A = B; const B = A`
471
516
  * is legal JS and would otherwise re-enter forever.
472
517
  */
473
- function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
518
+ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback, position) {
474
519
  if (guard.pushNode(type) === "cycle") {
475
520
  return [indeterminateTerminal()];
476
521
  }
@@ -486,13 +531,14 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
486
531
  return foldLeaf(graph, fileGraph, type, argMap, guard, fallback);
487
532
  }
488
533
  const innerGraph = resolved.kind === "Function" ? fileGraphForFunction(graph, fileGraph, resolved) : fileGraph;
489
- const inner = walkInner(graph, innerGraph, resolved, argMap, guard, fallback);
534
+ const inner = walkInner(graph, innerGraph, resolved, argMap, guard, fallback, position);
490
535
  if (inner.every((t) => t.identity !== null))
491
536
  return inner;
492
- const self = resolved.kind !== "ParameterOf" && evalKind(resolved, graph, innerGraph, argMap) === "component"
537
+ const derived = (position === "tag" && inner.some((t) => t.identity === null && isCallable(t))) ||
538
+ (resolved.kind !== "ParameterOf" && evalKind(resolved, graph, innerGraph, argMap) === "component")
493
539
  ? deriveIdentity(type, fileGraph, graph)
494
540
  : null;
495
- const stamp = self ?? deriveIdentity(fallback, fileGraph, graph);
541
+ const stamp = derived ?? deriveIdentity(fallback, fileGraph, graph);
496
542
  // Carries the product mark: a reference to a context IS that context.
497
543
  return inner.map((t) => t.identity === null ? { denotation: t.denotation, viaTrail: t.viaTrail, identity: stamp, semantics: t.semantics } : t);
498
544
  }
@@ -514,7 +560,7 @@ function walkTypeOf(graph, fileGraph, type, argMap, guard, fallback) {
514
560
  * re-enters this arm forever without it — `resolveType` pops every node it
515
561
  * pushes, so nothing on such a cycle stays on the guard's path otherwise.
516
562
  */
517
- function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
563
+ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback, position) {
518
564
  if (guard.pushNode(type) === "cycle") {
519
565
  return [indeterminateTerminal()];
520
566
  }
@@ -537,7 +583,7 @@ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
537
583
  // and any member of a non-component product is that product. ONE walk
538
584
  // answers both, and only once the member came to nothing, so a member
539
585
  // that resolved is never re-walked.
540
- const object = walkInner(graph, fileGraph, type.obj, argMap, guard);
586
+ const object = walkInner(graph, fileGraph, type.obj, argMap, guard, undefined, "value");
541
587
  const semantics = markedProduct(object);
542
588
  if (semantics !== null)
543
589
  return leaf.map((t) => withSemantics(t, semantics));
@@ -548,7 +594,7 @@ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
548
594
  const value = obj.props[type.member];
549
595
  if (value === undefined)
550
596
  continue;
551
- out.push(...walkInner(graph, fileGraph, value, argMap, guard, fallback));
597
+ out.push(...walkInner(graph, fileGraph, value, argMap, guard, fallback, position));
552
598
  }
553
599
  return out.length > 0 ? out : [indeterminateTerminal()];
554
600
  }
@@ -557,59 +603,33 @@ function walkStaticMember(graph, fileGraph, type, argMap, guard, fallback) {
557
603
  }
558
604
  }
559
605
  /**
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
606
+ * A call. A call of an export the stub table names a non-component product
607
+ * IS that product, before any fold runs: its arguments are values the product
608
+ * holds (a context's default), never a wrapped component, so none of them is
609
+ * walked or credited.
610
+ *
611
+ * Every other call runs the folding rules first (`foldCall`). The call's own
612
+ * shape gets a name only when all of them declined: no argument credited and no
613
+ * algebra result. A call of a hook the graph cannot see returns a value
563
614
  * bound at render time by the hook's own implementation, so it is late-bound
564
615
  * rather than unknown. The predicate is React's `HOOK_NAME` over a callee
565
616
  * `isImportBackedLeaf` says the graph cannot resolve; it runs after the folds,
566
617
  * so `const X = useStyled(Base)` keeps the credit its fold gave it.
567
618
  *
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
619
  * ORDER IS LOAD-BEARING: `returnedValue` runs on the fold's results before
573
620
  * `late-bound{hook-result}` is minted for the call itself.
574
621
  */
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
- }
622
+ function walkReturnTypeOf(graph, fileGraph, type, argMap, guard, position) {
623
+ const semantics = nonComponentProduct(graph, fileGraph, type.callee, guard);
624
+ if (semantics !== null)
625
+ return [withSemantics(indeterminateTerminal(), semantics)];
626
+ const returns = foldCall(graph, fileGraph, type, argMap, guard, position).map(returnedValue);
582
627
  if (!(returns.length > 0 && returns.every(cameToNothing)))
583
628
  return returns;
584
629
  const unseenHook = HOOK_NAME.test(deriveHocCallee(type.callee)) && isImportBackedLeaf(type.callee, graph, fileGraph, guard);
585
630
  return unseenHook ? returns.map(asLateBound("hook-result")) : returns;
586
631
  }
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) {
632
+ function foldCall(graph, fileGraph, type, argMap, guard, position) {
613
633
  // (1) lazy-import shape: any arg's subtree contains a DynamicImport
614
634
  // (with one-ref-deep peek into local-file bindings to catch the async-await form).
615
635
  const lazyShape = findLazyImportArg(type.args, fileGraph);
@@ -628,10 +648,10 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
628
648
  }
629
649
  let inner;
630
650
  try {
631
- const target = resolveDynamicImportTarget(graph, fileGraph, syntheticDI, guard);
651
+ const target = resolveDynamicImportTarget(graph, fileGraph, syntheticDI);
632
652
  inner = target
633
- ? walkInner(graph, target.targetFileGraph, target.value, argMap, guard)
634
- : walkInner(graph, fileGraph, syntheticDI, argMap, guard);
653
+ ? walkInner(graph, target.targetFileGraph, target.value, argMap, guard, undefined, position)
654
+ : walkInner(graph, fileGraph, syntheticDI, argMap, guard, undefined, position);
635
655
  }
636
656
  finally {
637
657
  guard.pop(syntheticDI.originFile, guardKey);
@@ -670,7 +690,7 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
670
690
  // walkDynamicMember rule fire and surfaces the dynamic-map via in the chain
671
691
  // — without it, cross-module dispatcher patterns would lose the via even
672
692
  // though identity attribution worked.
673
- const dispatcherResult = walkDispatcherReturns(graph, fileGraph, type, argMap, guard);
693
+ const dispatcherResult = walkDispatcherReturns(graph, fileGraph, type, argMap, guard, position);
674
694
  if (dispatcherResult)
675
695
  return dispatcherResult;
676
696
  // (2b) Structural pass-through. When every Function
@@ -686,8 +706,8 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
686
706
  if (passThroughIndex !== null) {
687
707
  const wrapped = type.args[passThroughIndex];
688
708
  if (wrapped !== undefined) {
689
- const inner = walkInner(graph, fileGraph, wrapped, argMap, guard);
690
- return foldToWrappedArg(inner, buildHocVia(type, wrapped, fileGraph));
709
+ const inner = walkInner(graph, fileGraph, wrapped, argMap, guard, undefined, position);
710
+ return foldToWrappedArg(inner, buildHocVia(graph, type, wrapped, fileGraph));
691
711
  }
692
712
  }
693
713
  // (3) Pure algebra walk. Per-leaf identity uses each terminal's source
@@ -711,9 +731,9 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
711
731
  // stands with no via.
712
732
  const lastArg = type.args.at(-1);
713
733
  if (lastArg !== undefined) {
714
- const inner = walkInner(graph, fileGraph, lastArg, argMap, guard);
734
+ const inner = walkInner(graph, fileGraph, lastArg, argMap, guard, undefined, position);
715
735
  if (isPassThroughWrapper(algResult, inner)) {
716
- const via = buildHocVia(type, lastArg, fileGraph);
736
+ const via = buildHocVia(graph, type, lastArg, fileGraph);
717
737
  return inner
718
738
  .filter((t) => !cameToNothing(t))
719
739
  .map((t) => ({ denotation: t.denotation, viaTrail: [via, ...t.viaTrail], identity: t.identity }));
@@ -726,7 +746,11 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
726
746
  if (lastArg === undefined) {
727
747
  return [indeterminateTerminal()];
728
748
  }
729
- const inner = walkInner(graph, fileGraph, lastArg, argMap, guard);
749
+ // A data-method callback and a loader holding an `import()` the lazy arm
750
+ // declined are called, not rendered, so they walk in value position.
751
+ const dataMethod = isDataMethodRenderCall(type, graph, fileGraph, guard);
752
+ const called = dataMethod || isLoader(lastArg, graph, fileGraph, guard);
753
+ const inner = walkInner(graph, fileGraph, lastArg, argMap, guard, undefined, called ? "value" : position);
730
754
  // A data-method render call (`items.map(render)`) is not a
731
755
  // wrapper — its result IS the callback's rendered JSX, not a component
732
756
  // received as a prop. Return the callback's terminals AS-IS: no
@@ -734,14 +758,14 @@ function foldCall(graph, fileGraph, type, argMap, guard) {
734
758
  // (the declaration being walked, e.g. `List`) wins rather than whatever
735
759
  // the callback itself happened to resolve to. Import-backed MemberOf
736
760
  // callees (`Sentry.withProfiler`) fall through to the fold+hop below.
737
- if (isDataMethodRenderCall(type, graph, fileGraph, guard)) {
761
+ if (dataMethod) {
738
762
  const nonUnknown = inner.filter((t) => !cameToNothing(t));
739
763
  if (nonUnknown.length > 0) {
740
764
  return nonUnknown.map((t) => ({ denotation: t.denotation, viaTrail: t.viaTrail, identity: null }));
741
765
  }
742
766
  return [indeterminateTerminal()];
743
767
  }
744
- return foldToWrappedArg(inner, buildHocVia(type, lastArg, fileGraph));
768
+ return foldToWrappedArg(inner, buildHocVia(graph, type, lastArg, fileGraph));
745
769
  }
746
770
  /**
747
771
  * Index of the argument an identity-preserving wrapper returns, or null when
@@ -792,7 +816,7 @@ function passThroughParameterIndex(graph, fileGraph, type, argMap, guard) {
792
816
  * `enclosingBinding.file`). Returns null when no callee matches the shape so
793
817
  * the caller falls through to the pure algebra path.
794
818
  */
795
- function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
819
+ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard, position) {
796
820
  const calleeFns = resolveToFunctions(graph, fileGraph, type.callee, argMap, guard);
797
821
  const hasDispatcher = calleeFns.some(({ fn }) => fn.kind === "Function" && fn.returns.some(isDynamicMemberReturn));
798
822
  if (!hasDispatcher)
@@ -806,7 +830,7 @@ function walkDispatcherReturns(graph, fileGraph, type, argMap, guard) {
806
830
  argMap.bind(bindingRef, type.args);
807
831
  try {
808
832
  for (const ret of fn.returns) {
809
- folded.push(...walkInner(graph, fnFileGraph, ret, argMap, guard));
833
+ folded.push(...walkInner(graph, fnFileGraph, ret, argMap, guard, undefined, position));
810
834
  }
811
835
  }
812
836
  finally {
@@ -886,7 +910,7 @@ function findFirstDynamicImport(t, fileGraph, depthBudget) {
886
910
  * reached through `MAP[k]` re-enters this arm forever without it. No
887
911
  * `foldLeaf` path here, so no early pop is needed.
888
912
  */
889
- function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
913
+ function walkDynamicMember(graph, fileGraph, type, argMap, guard, position) {
890
914
  if (guard.pushNode(type) === "cycle") {
891
915
  return [indeterminateTerminal()];
892
916
  }
@@ -906,7 +930,7 @@ function walkDynamicMember(graph, fileGraph, type, argMap, guard) {
906
930
  for (const propValue of Object.values(obj.props)) {
907
931
  // Recurse via walkInner so identity is derived from each branch's
908
932
  // leaf TypeOf (Foo/Bar resolve to their respective bindings).
909
- const inner = walkInner(graph, fileGraph, propValue, argMap, guard);
933
+ const inner = walkInner(graph, fileGraph, propValue, argMap, guard, undefined, position);
910
934
  const via = { kind: "dynamic-map", mapName, mapLoc };
911
935
  for (const t of inner) {
912
936
  if (cameToNothing(t))
@@ -954,16 +978,22 @@ function deriveHocCallee(callee) {
954
978
  }
955
979
  /**
956
980
  * Find an import record that the last arg's outermost TypeOf points at.
957
- * Returns null when the last arg isn't a TypeOf or isn't an imported binding.
981
+ * Returns null when the last arg isn't a TypeOf, isn't an imported binding,
982
+ * or binds to a declaration in this file's scope that shadows the import.
958
983
  */
959
- function findImportForLastArg(lastArg, fileGraph) {
984
+ function findImportForLastArg(graph, lastArg, fileGraph) {
960
985
  let cur = lastArg;
961
986
  while (cur.kind === "ReturnTypeOf")
962
987
  cur = cur.callee;
963
988
  if (cur.kind !== "TypeOf")
964
989
  return null;
965
990
  const imp = fileGraph.importsByLocal.get(cur.ref.symbol);
966
- return imp ? { specifier: imp.specifier, imported: imp.imported } : null;
991
+ if (!imp)
992
+ return null;
993
+ const binding = resolveBinding(graph, fileGraph, cur.ref);
994
+ if (binding.kind === "declaration" && binding.inScope && binding.file === fileGraph.filePath)
995
+ return null;
996
+ return { specifier: imp.specifier, imported: imp.imported };
967
997
  }
968
998
  /**
969
999
  * Resolve an anonymous lastArg (no leaf TypeOf, no import record) to a
@@ -1030,185 +1060,20 @@ function typeContainsRef(haystack, needle) {
1030
1060
  }
1031
1061
  }
1032
1062
  /**
1033
- * Resolve an import specifier to its repo-relative POSIX target file via the
1034
- * graph's moduleResolver + resolveToGraphKey. Returns null for unresolvable
1035
- * specifiers or paths outside the repo root (e.g. node_modules siblings) —
1036
- * callers fall back to the raw specifier in those cases.
1037
- *
1038
- * When `imported` is provided, follows re-export chains (both
1039
- * `{kind: "named", from, fromImported}` and `{kind: "star", from}`) to the
1040
- * canonical source file via `followReExportChain` (star re-exports are
1041
- * first-wins). Mirrors what `resolveExport` in resolve-reference.ts
1042
- * does for value-flow purposes,
1043
- * applied here at identity-stamping time. So `import { X } from './barrel'`
1044
- * where `./barrel` re-exports X from `./source` attributes the component's
1045
- * identity to `./source.tsx`, not `./barrel.tsx`.
1046
- *
1047
- * Used by `deriveIdentity` so `TerminalIdentity.resolvedFile` carries the
1048
- * canonical repo-relative path. Downstream `componentIdFromIdentity` uses
1049
- * that canonical path so `'./page'` and `'../page'` from different callers
1050
- * collapse to the same identity.
1051
- */
1052
- function resolveImportedFile(graph, fileGraph, specifier, imported) {
1053
- const abs = graph.moduleResolver(fileGraph.filePath, specifier);
1054
- if (!abs)
1055
- return null;
1056
- const key = graph.resolveToGraphKey ? graph.resolveToGraphKey(abs) : abs;
1057
- if (!key || !imported)
1058
- return key;
1059
- return followReExportChain(graph, key, imported)?.file ?? key;
1060
- }
1061
- /**
1062
- * When a relative-spec import resolves into the parsed graph (possibly via a
1063
- * re-export chain) AND the canonical source file has a local declaration for
1064
- * the exported name, promote the identity to `kind: "local"` directly. This
1065
- * matches what the local-index seeds emit (keyed on `{filePath, exportName}`)
1066
- * so the engine occurrence hashes to the same componentId as the seed —
1067
- * preventing the barrel-vs-source phantom split.
1068
- *
1069
- * Symmetric with `workspaceSiblingIdentity` but for relative imports.
1070
- */
1071
- function relativeChainLocalIdentity(graph, fileGraph, specifier, imported) {
1072
- if (!graph)
1073
- return null;
1074
- if (packageNameFromSpecifier(specifier))
1075
- return null; // bare package — workspace-sibling path handles it
1076
- const abs = graph.moduleResolver(fileGraph.filePath, specifier);
1077
- if (!abs)
1078
- return null;
1079
- const key = graph.resolveToGraphKey ? graph.resolveToGraphKey(abs) : abs;
1080
- if (!key || !graph.files.has(key))
1081
- return null;
1082
- const chained = followReExportChain(graph, key, imported);
1083
- if (!chained)
1084
- return null;
1085
- return { kind: "local", filePath: chained.file, export: chained.localExport };
1086
- }
1087
- /**
1088
- * Workspace-sibling detection. A bare-package import (`@org/foo`) that
1089
- * resolves to a file inside the parsed graph is a workspace sibling — the
1090
- * package lives in this repo as a sibling workspace, not as a node_modules
1091
- * dependency. Treat it as a local identity so JSX edges land on the actual
1092
- * sibling file rather than a phantom external stub.
1093
- *
1094
- * Node_modules entries aren't in `graph.files` (excluded by default scan
1095
- * globs), so they stay external naturally — no extra check needed.
1096
- *
1097
- * The file and export name are the ones `followReExportChain` lands on, so a
1098
- * barrel that renames (`export { FooImpl as Foo } from "./foo"`) names the
1099
- * binding `FooImpl` in `./foo`, the same identity the local-index seed hashes.
1100
- */
1101
- function workspaceSiblingIdentity(graph, fileGraph, specifier, imported) {
1102
- if (!graph)
1103
- return null;
1104
- if (!packageNameFromSpecifier(specifier))
1105
- return null;
1106
- const abs = graph.moduleResolver(fileGraph.filePath, specifier);
1107
- const key = abs && (graph.resolveToGraphKey ? graph.resolveToGraphKey(abs) : abs);
1108
- if (!key)
1109
- return null;
1110
- const chained = followReExportChain(graph, key, imported);
1111
- const file = chained?.file ?? key;
1112
- if (!graph.files.has(file))
1113
- return null;
1114
- return { kind: "local", filePath: file, export: chained?.localExport ?? imported };
1115
- }
1116
- /**
1117
- * Membership fallback for composition-path identity. When a specifier
1118
- * resolves to an absolute target that is NOT in the parsed graph (outside
1119
- * the scan globs, or reached through an unrealpathed node_modules symlink)
1120
- * but IS owned by a workspace member, promote to a LOCAL identity pinned via
1121
- * the shared unparsed-member seam — mirroring the direct-import branch in
1122
- * engine/index.ts so both paths mint the SAME identity. The absolute
1123
- * filePath is normalised downstream (CLI `normaliseEngineLocalId`). Runs
1124
- * after the in-graph promotions (workspaceSiblingIdentity /
1125
- * relativeChainLocalIdentity), so parsed targets never reach it with a
1126
- * different answer.
1127
- */
1128
- function memberLocalIdentity(graph, fileGraph, specifier, imported) {
1129
- if (graph?.workspaceMembership === undefined)
1130
- return null;
1131
- const abs = graph.moduleResolver(fileGraph.filePath, specifier);
1132
- if (!abs)
1133
- return null;
1134
- const key = graph.resolveToGraphKey ? graph.resolveToGraphKey(abs) : abs;
1135
- if (key !== null && graph.files.has(key))
1136
- return null;
1137
- const member = unparsedMemberDefinition(graph, abs, imported);
1138
- if (member === null)
1139
- return null;
1140
- return { kind: "local", filePath: member.absFile, export: member.exportName };
1141
- }
1142
- /** Append a residual member chain to a derived identity's export name. */
1143
- function withResidual(identity, residual) {
1144
- if (identity === null || residual.length === 0)
1145
- return identity;
1146
- return identity.kind === "local"
1147
- ? { ...identity, export: compoundExportName(identity.export, residual) }
1148
- : { ...identity, imported: compoundExportName(identity.imported, residual) };
1149
- }
1150
- /**
1151
- * Resolve the input type's leaf TypeOf ref (or DynamicImport) to either an
1152
- * imported or a local identity. Any other shape — a `ReturnTypeOf` included —
1063
+ * The identity of the binding a `TypeOf` reference or a `DynamicImport` names
1064
+ * (`bindingIdentity`), with a directly reached workspace member pinned to its
1065
+ * definition (`pinUnparsed`). Any other shape — a `ReturnTypeOf` included —
1153
1066
  * has no identity of its own and yields null.
1154
- *
1155
- * Switches FileGraph context via `ref.originFile` when set, so refs that have
1156
- * travelled across module boundaries (e.g. cross-module function returns) are
1157
- * derived in their authoring file — where the import / declaration tables
1158
- * that match the ref actually live.
1159
1067
  */
1160
1068
  export function deriveIdentity(type, fileGraph, graph) {
1161
1069
  // A ReturnTypeOf never derives identity from its callee:
1162
1070
  // a resolvable factory call's identity is the declaration holding the call,
1163
1071
  // which every engine push site already supplies as its fallback. Returning
1164
1072
  // null here is what makes that fallback win.
1165
- const cur = type;
1166
- if (cur.kind === "DynamicImport") {
1167
- return dynamicImportIdentity(graph, fileGraph, cur);
1168
- }
1169
- if (cur.kind === "TypeOf") {
1170
- const symbol = cur.ref.symbol;
1171
- const lookup = graph ? fileGraphForRef(graph, cur.ref, fileGraph) : fileGraph;
1172
- const imp = lookup.importsByLocal.get(symbol);
1173
- if (imp) {
1174
- // Namespace-import members canonicalise to the member name,
1175
- // matching the direct-import path's `effectiveImported`
1176
- // (engine/index.ts) so both paths mint the same identity. The residual chain
1177
- // is appended AFTER every root lookup, so `Dialog.Popup` on an unparsed
1178
- // package and `NS.Inline` on a workspace member both mirror the direct
1179
- // path.
1180
- const imported = effectiveExportName(imp.imported, cur.ref.memberChain);
1181
- const residual = residualMemberChain(imp.imported, cur.ref.memberChain);
1182
- const resolvedFile = graph ? resolveImportedFile(graph, lookup, imp.specifier, imported) : null;
1183
- const workspaceLocal = workspaceSiblingIdentity(graph, lookup, imp.specifier, imported);
1184
- if (workspaceLocal)
1185
- return withResidual(workspaceLocal, residual);
1186
- const relativeLocal = relativeChainLocalIdentity(graph, lookup, imp.specifier, imported);
1187
- if (relativeLocal)
1188
- return withResidual(relativeLocal, residual);
1189
- const memberLocal = memberLocalIdentity(graph, lookup, imp.specifier, imported);
1190
- if (memberLocal)
1191
- return withResidual(memberLocal, residual);
1192
- return withResidual({
1193
- kind: "imported",
1194
- specifier: imp.specifier,
1195
- imported,
1196
- resolvedFile,
1197
- }, residual);
1198
- }
1199
- // Check local declarations. A same-file symbol is never a namespace
1200
- // import, so the whole member chain is residual (mirror of
1201
- // `pushLocalOccurrence`'s fallback) — an empty chain is a no-op.
1202
- for (const [, decl] of lookup.declarations) {
1203
- if (decl && decl.symbol === symbol) {
1204
- return withResidual({
1205
- kind: "local",
1206
- filePath: lookup.filePath,
1207
- export: decl.exportedAs ?? symbol,
1208
- }, cur.ref.memberChain);
1209
- }
1210
- }
1211
- }
1073
+ if (type.kind === "DynamicImport")
1074
+ return dynamicImportIdentity(graph, fileGraph, type);
1075
+ if (type.kind === "TypeOf")
1076
+ return bindingIdentity(pinUnparsed(graph, resolveBinding(graph, fileGraph, type.ref)));
1212
1077
  return null;
1213
1078
  }
1214
1079
  //# sourceMappingURL=wrapper-folding.js.map