@suss/adapter-typescript 0.21.0 → 0.22.0

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/index.cjs CHANGED
@@ -32,26 +32,24 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ADAPTER_VERSION: () => ADAPTER_VERSION,
34
34
  ResolutionStore: () => ResolutionStore,
35
- WhySession: () => WhySession,
35
+ TypeScriptWhySession: () => TypeScriptWhySession,
36
36
  accessContextFor: () => accessContextFor,
37
37
  buildProjectHelperIndex: () => buildProjectHelperIndex,
38
38
  callOpsFor: () => callOpsFor,
39
39
  collectAncestorBranches: () => collectAncestorBranches,
40
40
  collectClientFieldAccesses: () => collectClientFieldAccesses,
41
41
  computeAdapterPacksDigest: () => computeAdapterPacksDigest,
42
- computeContentHash: () => computeContentHash,
42
+ computeContentHash: () => import_extractor10.computeContentHash,
43
43
  createProjectWithoutTsconfig: () => createProjectWithoutTsconfig,
44
44
  createTsDiscoveryContext: () => createTsDiscoveryContext,
45
45
  createTsSubUnitContext: () => createTsSubUnitContext,
46
46
  createTypeScriptAdapter: () => createTypeScriptAdapter,
47
47
  discoverUnits: () => discoverUnits,
48
- evaluatePackHealth: () => evaluatePackHealth,
49
48
  exportedDeclarationsOf: () => exportedDeclarationsOf,
50
49
  extractCodeStructure: () => extractCodeStructure,
51
50
  extractRawBranches: () => extractRawBranches,
52
51
  findNearestTsconfig: () => findNearestTsconfig,
53
52
  findTerminals: () => findTerminals,
54
- formatPackHealth: () => formatPackHealth,
55
53
  functionTargetOf: () => functionTargetOf,
56
54
  importDeclarationsOf: () => importDeclarationsOf,
57
55
  importedNamesOf: () => importedNamesOf,
@@ -75,15 +73,15 @@ __export(index_exports, {
75
73
  module.exports = __toCommonJS(index_exports);
76
74
 
77
75
  // src/adapter.ts
78
- var import_node_path11 = __toESM(require("path"), 1);
76
+ var import_node_path10 = __toESM(require("path"), 1);
79
77
  var import_ts_morph66 = require("ts-morph");
80
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
78
+ var import_behavioral_ir9 = require("@suss/behavioral-ir");
81
79
  var import_datalog6 = require("@suss/datalog");
82
- var import_extractor7 = require("@suss/extractor");
80
+ var import_extractor11 = require("@suss/extractor");
83
81
 
84
82
  // src/assembly.ts
85
83
  var import_ts_morph54 = require("ts-morph");
86
- var import_extractor4 = require("@suss/extractor");
84
+ var import_extractor5 = require("@suss/extractor");
87
85
 
88
86
  // src/conditions.ts
89
87
  var import_ts_morph6 = require("ts-morph");
@@ -95,6 +93,7 @@ var import_ts_morph5 = require("ts-morph");
95
93
  var import_ts_morph2 = require("ts-morph");
96
94
 
97
95
  // src/walk/nodeKeys.ts
96
+ var import_behavioral_ir = require("@suss/behavioral-ir");
98
97
  function offsetKeyOf(node) {
99
98
  return offsetKeyFor(node.getSourceFile().getFilePath(), {
100
99
  start: node.getStart(),
@@ -102,7 +101,7 @@ function offsetKeyOf(node) {
102
101
  });
103
102
  }
104
103
  function offsetKeyFor(file, span) {
105
- return `${file}:${span.start}-${span.end}`;
104
+ return (0, import_behavioral_ir.declarationKey)(file, span);
106
105
  }
107
106
  function lineRangeKey(file, startLine, endLine) {
108
107
  return `${file}:${startLine}-${endLine}`;
@@ -1542,7 +1541,7 @@ function degradedResult(func, terminalNodes, reason) {
1542
1541
 
1543
1542
  // src/resolve/invocationEffects.ts
1544
1543
  var import_ts_morph43 = require("ts-morph");
1545
- var import_extractor3 = require("@suss/extractor");
1544
+ var import_extractor4 = require("@suss/extractor");
1546
1545
 
1547
1546
  // src/lines.ts
1548
1547
  function startLineOf(node) {
@@ -1910,8 +1909,8 @@ function loadImportGraphsDepthFirstFromPaths(project, rootPaths) {
1910
1909
  stack.pop();
1911
1910
  loadOrder.push(top.path);
1912
1911
  let deepest = 0;
1913
- for (const path15 of top.deps) {
1914
- deepest = Math.max(deepest, chainDepth.get(path15) ?? 0);
1912
+ for (const path14 of top.deps) {
1913
+ deepest = Math.max(deepest, chainDepth.get(path14) ?? 0);
1915
1914
  }
1916
1915
  chainDepth.set(top.path, deepest + 1);
1917
1916
  }
@@ -1958,6 +1957,20 @@ function importedFilePathsOf(project, filePath) {
1958
1957
  cache.set(filePath, paths);
1959
1958
  return paths;
1960
1959
  }
1960
+ function resolutionCacheFor(project, options) {
1961
+ const existing = resolutionCachesByProject.get(project);
1962
+ if (existing !== void 0) {
1963
+ return existing;
1964
+ }
1965
+ const fresh = import_ts_morph10.ts.createModuleResolutionCache(
1966
+ project.getFileSystem().getCurrentDirectory(),
1967
+ (name) => name,
1968
+ options
1969
+ );
1970
+ resolutionCachesByProject.set(project, fresh);
1971
+ return fresh;
1972
+ }
1973
+ var resolutionCachesByProject = /* @__PURE__ */ new WeakMap();
1961
1974
  function specifierCacheFor(project) {
1962
1975
  const existing = specifiersByProject.get(project);
1963
1976
  if (existing !== void 0) {
@@ -1984,13 +1997,15 @@ function readSpecifierPaths(project, filePath, options, host) {
1984
1997
  return [];
1985
1998
  }
1986
1999
  const scanned = import_ts_morph10.ts.preProcessFile(text, true, true);
2000
+ const cache = resolutionCacheFor(project, options);
1987
2001
  const paths = [];
1988
2002
  for (const imported of scanned.importedFiles) {
1989
2003
  const resolved = import_ts_morph10.ts.resolveModuleName(
1990
2004
  imported.fileName,
1991
2005
  filePath,
1992
2006
  options,
1993
- host
2007
+ host,
2008
+ cache
1994
2009
  ).resolvedModule?.resolvedFileName;
1995
2010
  if (resolved !== void 0 && !resolved.includes("/node_modules/")) {
1996
2011
  paths.push(resolved);
@@ -2247,11 +2262,11 @@ function walkAndWarm(root, reach) {
2247
2262
  if (warmedFiles.get(target) !== void 0) {
2248
2263
  continue;
2249
2264
  }
2250
- const path15 = target.getFilePath();
2251
- if (visited.has(path15)) {
2265
+ const path14 = target.getFilePath();
2266
+ if (visited.has(path14)) {
2252
2267
  continue;
2253
2268
  }
2254
- visited.add(path15);
2269
+ visited.add(path14);
2255
2270
  stack.push(frameOf(target, reach));
2256
2271
  continue;
2257
2272
  }
@@ -2268,7 +2283,7 @@ function sitsOnWarmChains(file) {
2268
2283
  return false;
2269
2284
  }
2270
2285
  const imported = importedFilePathsOf(project, file.getFilePath());
2271
- return imported.length > 0 && imported.every((path15) => warmed.has(path15));
2286
+ return imported.length > 0 && imported.every((path14) => warmed.has(path14));
2272
2287
  }
2273
2288
  function markWarmed(file) {
2274
2289
  warmedFiles.set(file, true);
@@ -2563,6 +2578,32 @@ function writesToBindingUncached(declaration) {
2563
2578
  }
2564
2579
  return { values, inOrder: writesRunInOrder(declaration, writes) };
2565
2580
  }
2581
+ function sameConstructionAcrossWrites(values) {
2582
+ const candidates = values.filter((value) => !isNotYetPlaceholder(value));
2583
+ if (candidates.length === 0) {
2584
+ return null;
2585
+ }
2586
+ let construction = null;
2587
+ for (const value of candidates) {
2588
+ if (!isConstruction(value)) {
2589
+ return null;
2590
+ }
2591
+ if (construction !== null && sourceOf(value) !== sourceOf(construction)) {
2592
+ return null;
2593
+ }
2594
+ construction = value;
2595
+ }
2596
+ return construction;
2597
+ }
2598
+ function isNotYetPlaceholder(value) {
2599
+ return value.getKind() === import_ts_morph14.SyntaxKind.NullKeyword || import_ts_morph14.Node.isIdentifier(value) && value.getText() === "undefined";
2600
+ }
2601
+ function isConstruction(value) {
2602
+ return import_ts_morph14.Node.isCallExpression(value) || import_ts_morph14.Node.isNewExpression(value);
2603
+ }
2604
+ function sourceOf(node) {
2605
+ return node.getText().replace(/\s+/g, " ").trim();
2606
+ }
2566
2607
  function writesToField(declaration) {
2567
2608
  const remembered = byField.get(declaration);
2568
2609
  if (remembered !== void 0) {
@@ -2653,6 +2694,9 @@ function assignmentsInFile(sourceFile) {
2653
2694
  function isAssignmentOperator(kind) {
2654
2695
  return kind >= import_ts_morph14.SyntaxKind.FirstAssignment && kind <= import_ts_morph14.SyntaxKind.LastAssignment;
2655
2696
  }
2697
+ function writesItsWholeValue(kind) {
2698
+ return kind === import_ts_morph14.SyntaxKind.EqualsToken || kind === import_ts_morph14.SyntaxKind.QuestionQuestionEqualsToken || kind === import_ts_morph14.SyntaxKind.BarBarEqualsToken;
2699
+ }
2656
2700
  function writeAt(node) {
2657
2701
  if (import_ts_morph14.Node.isBinaryExpression(node)) {
2658
2702
  const operator = node.getOperatorToken().getKind();
@@ -2660,7 +2704,7 @@ function writeAt(node) {
2660
2704
  if (!isAssignmentOperator(operator) || !(import_ts_morph14.Node.isIdentifier(target) || import_ts_morph14.Node.isPropertyAccessExpression(target))) {
2661
2705
  return null;
2662
2706
  }
2663
- const value = operator === import_ts_morph14.SyntaxKind.EqualsToken ? node.getRight() : null;
2707
+ const value = writesItsWholeValue(operator) ? node.getRight() : null;
2664
2708
  return { target, write: { value, node } };
2665
2709
  }
2666
2710
  if (import_ts_morph14.Node.isPrefixUnaryExpression(node) || import_ts_morph14.Node.isPostfixUnaryExpression(node)) {
@@ -3051,11 +3095,16 @@ function emitBindingValues(db, table, declaration) {
3051
3095
  }
3052
3096
  const { values, inOrder } = writesToBinding(declaration);
3053
3097
  const last = values[values.length - 1];
3054
- if (!inOrder || last === void 0) {
3055
- reassignedUnstated.add(declarationId);
3098
+ if (inOrder && last !== void 0) {
3099
+ fact(db, "endsHolding", declarationId, emitValue(db, table, last));
3100
+ return;
3101
+ }
3102
+ const construction = sameConstructionAcrossWrites(values);
3103
+ if (construction !== null) {
3104
+ fact(db, "endsHolding", declarationId, emitValue(db, table, construction));
3056
3105
  return;
3057
3106
  }
3058
- fact(db, "endsHolding", declarationId, emitValue(db, table, last));
3107
+ reassignedUnstated.add(declarationId);
3059
3108
  }
3060
3109
  var reassignedUnstated = /* @__PURE__ */ new Set();
3061
3110
  function reassignedNamesUnstated() {
@@ -3067,23 +3116,31 @@ function forgetReassignedNamesUnstated() {
3067
3116
  function emitFieldValues(db, table, declaration) {
3068
3117
  const { values, inOrder } = writesToField(declaration);
3069
3118
  const last = values[values.length - 1];
3070
- if (!inOrder || last === void 0 || !import_ts_morph15.Node.isExpression(last)) {
3119
+ if (inOrder && last !== void 0 && import_ts_morph15.Node.isExpression(last)) {
3120
+ fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
3071
3121
  return;
3072
3122
  }
3073
- fact(db, "binds", nodeId(declaration), emitValue(db, table, last));
3123
+ const construction = sameConstructionAcrossWrites(values);
3124
+ if (construction !== null) {
3125
+ fact(db, "binds", nodeId(declaration), emitValue(db, table, construction));
3126
+ }
3074
3127
  }
3075
3128
  function emitParameterPropertyRead(db, table, referenceId, declaration) {
3076
3129
  const { values, inOrder } = writesToField(declaration);
3077
3130
  const last = values[values.length - 1];
3078
- if (!inOrder || last === void 0) {
3131
+ if (inOrder && last !== void 0 && import_ts_morph15.Node.isExpression(last)) {
3132
+ fact(db, "binds", referenceId, emitValue(db, table, last));
3079
3133
  return;
3080
3134
  }
3081
- if (!import_ts_morph15.Node.isExpression(last)) {
3135
+ if (inOrder && last !== void 0) {
3082
3136
  table.byId.set(nodeId(last), last);
3083
3137
  fact(db, "binds", referenceId, nodeId(last));
3084
3138
  return;
3085
3139
  }
3086
- fact(db, "binds", referenceId, emitValue(db, table, last));
3140
+ const construction = sameConstructionAcrossWrites(values);
3141
+ if (construction !== null) {
3142
+ fact(db, "binds", referenceId, emitValue(db, table, construction));
3143
+ }
3087
3144
  }
3088
3145
  function emitBindingElementFacts(db, table, element) {
3089
3146
  const id = nodeId(element);
@@ -3547,7 +3604,8 @@ var JS_RULES = [
3547
3604
  ];
3548
3605
  var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
3549
3606
  rules: [...import_resolution2.RESOLUTION_RULES, ...JS_RULES, ...import_resolution2.RESOLUTION_QUESTIONS],
3550
- demandDriven: []
3607
+ demandDriven: [],
3608
+ demands: []
3551
3609
  } : (0, import_datalog2.deriveOnDemand)(
3552
3610
  [...import_resolution2.RESOLUTION_RULES, ...JS_RULES, ...import_resolution2.RESOLUTION_QUESTIONS],
3553
3611
  import_resolution2.ANSWER_RELATIONS
@@ -3574,7 +3632,9 @@ var QUERY_FACTS = RESOLUTION_PROGRAM.demandDriven.length === 0 ? [] : [
3574
3632
  "wantedAnchor",
3575
3633
  "wantedSubject"
3576
3634
  ];
3577
- var MAX_MODULE_HOPS = 6;
3635
+ var MODULE_DEMANDS = RESOLUTION_PROGRAM.demands.filter(
3636
+ (one) => (one.relation === "moduleExport" || one.relation === "moduleForwards") && one.bound[0] === true
3637
+ ).map((one) => one.demand);
3578
3638
  var ResolutionStore = class {
3579
3639
  db = new import_datalog2.Database();
3580
3640
  table = createNodeTable();
@@ -3590,7 +3650,7 @@ var ResolutionStore = class {
3590
3650
  * extraction elsewhere cannot change an entry.
3591
3651
  */
3592
3652
  exportTables = /* @__PURE__ */ new Map();
3593
- /** Files the most recent wave walk entered, for the memo to keep. */
3653
+ /** Files the most recent query read, for the memo to keep. */
3594
3654
  lastQueryWalked = [];
3595
3655
  declarations = /* @__PURE__ */ new Map();
3596
3656
  graph = new ModuleGraph();
@@ -3621,19 +3681,19 @@ var ResolutionStore = class {
3621
3681
  */
3622
3682
  resolveCallableSources(value, alsoFrom) {
3623
3683
  const target = factKeyOf(value);
3624
- return this.resolveByWaves(
3684
+ return this.askAbout(
3625
3685
  target,
3626
3686
  "wanted",
3627
3687
  () => this.lookupSources(target),
3628
3688
  alsoFrom
3629
- ) ?? [];
3689
+ );
3630
3690
  }
3631
3691
  /**
3632
3692
  * The witness proof behind `resolveCallable`'s answer. Resolves the
3633
- * value first, which walks files and extracts facts in the usual
3634
- * waves, then re-evaluates the rules over those base facts under the
3635
- * witness algebra and rebuilds the proof of the answer. Null when
3636
- * the value does not resolve at all.
3693
+ * value first, which extracts the files the question demands, then
3694
+ * re-evaluates the rules over those base facts under the witness
3695
+ * algebra and rebuilds the proof of the answer. Null when the value
3696
+ * does not resolve at all.
3637
3697
  */
3638
3698
  explainCallable(value, options = {}) {
3639
3699
  const resolved = this.resolveCallable(value, options.alsoFrom);
@@ -3670,11 +3730,7 @@ var ResolutionStore = class {
3670
3730
  }
3671
3731
  resolveObject(value) {
3672
3732
  const target = factKeyOf(value);
3673
- return this.resolveByWaves(
3674
- target,
3675
- "wanted",
3676
- () => this.lookupObject(target)
3677
- );
3733
+ return this.askAbout(target, "wanted", () => this.lookupObject(target));
3678
3734
  }
3679
3735
  /**
3680
3736
  * For a value that is neither a function nor an object, such as a
@@ -3690,17 +3746,17 @@ var ResolutionStore = class {
3690
3746
  }
3691
3747
  return cached.written;
3692
3748
  }
3693
- const settled2 = this.resolveByWaves(
3749
+ const written = this.askAbout(
3694
3750
  target,
3695
3751
  "wanted",
3696
3752
  () => this.lookupWritten(target)
3697
3753
  );
3698
3754
  this.writtenValues.set(key, {
3699
- written: settled2?.written ?? null,
3755
+ written,
3700
3756
  walked: [...this.lastQueryWalked],
3701
3757
  extractedAt: this.fullyExtracted.size
3702
3758
  });
3703
- return settled2?.written ?? null;
3759
+ return written;
3704
3760
  }
3705
3761
  /**
3706
3762
  * The construction behind a registration subject: the call or `new`
@@ -3720,18 +3776,29 @@ var ResolutionStore = class {
3720
3776
  }
3721
3777
  return cached.construction;
3722
3778
  }
3723
- const settled2 = this.resolveByWaves(
3779
+ const settled2 = this.askAbout(
3724
3780
  target,
3725
3781
  "wantedSubject",
3726
3782
  () => this.lookupSubjectConstruction(target, importModule, importName)
3727
3783
  );
3784
+ const walked = [...this.lastQueryWalked];
3785
+ let resolved = settled2;
3786
+ if (settled2.construction === null && settled2.candidate !== null && import_ts_morph16.Node.isCallExpression(settled2.candidate) && this.resolveCallable(settled2.candidate.getExpression()) !== null) {
3787
+ const call = settled2.candidate;
3788
+ resolved = this.askAbout(
3789
+ call,
3790
+ "wantedSubject",
3791
+ () => this.lookupSubjectConstruction(call, importModule, importName)
3792
+ );
3793
+ walked.push(...this.lastQueryWalked);
3794
+ }
3728
3795
  this.subjectConstructions.set(key, {
3729
- construction: settled2?.construction ?? null,
3730
- candidates: settled2?.candidates ?? 0,
3731
- walked: [...this.lastQueryWalked],
3796
+ construction: resolved.construction,
3797
+ candidates: resolved.candidates,
3798
+ walked,
3732
3799
  extractedAt: this.fullyExtracted.size
3733
3800
  });
3734
- return settled2?.construction ?? null;
3801
+ return resolved.construction;
3735
3802
  }
3736
3803
  /**
3737
3804
  * How many constructions this value was written as, which is what
@@ -3745,10 +3812,8 @@ var ResolutionStore = class {
3745
3812
  )?.candidates ?? 0;
3746
3813
  }
3747
3814
  /**
3748
- * The single-answer policy over the written-value walk, then the
3749
- * origin check on the one candidate. A candidate failing the check
3750
- * is settled refusal rather than a reason to widen: its own file's
3751
- * import facts are already in, and more files only add candidates.
3815
+ * The single-answer policy over the written-value chain, then the
3816
+ * origin check on the one candidate.
3752
3817
  */
3753
3818
  lookupSubjectConstruction(value, importModule, importName) {
3754
3819
  this.derive();
@@ -3764,6 +3829,7 @@ var ResolutionStore = class {
3764
3829
  if (candidates.size > 1) {
3765
3830
  return {
3766
3831
  construction: null,
3832
+ candidate: null,
3767
3833
  candidates: [...candidates].filter(
3768
3834
  (one) => this.constructionComesFrom(valueId, one, importModule, importName)
3769
3835
  ).length
@@ -3771,7 +3837,7 @@ var ResolutionStore = class {
3771
3837
  }
3772
3838
  const single = [...candidates][0];
3773
3839
  if (single === void 0) {
3774
- return neverWritable(value) ? { construction: null, candidates: 0 } : null;
3840
+ return { construction: null, candidate: null, candidates: 0 };
3775
3841
  }
3776
3842
  return {
3777
3843
  construction: this.constructionComesFrom(
@@ -3780,6 +3846,7 @@ var ResolutionStore = class {
3780
3846
  importModule,
3781
3847
  importName
3782
3848
  ) ? single : null,
3849
+ candidate: single,
3783
3850
  candidates: 1
3784
3851
  };
3785
3852
  }
@@ -3839,11 +3906,11 @@ var ResolutionStore = class {
3839
3906
  return cached.names;
3840
3907
  }
3841
3908
  const target = factKeyOf(value);
3842
- const found = this.resolveByWaves(
3909
+ const found = this.askAbout(
3843
3910
  target,
3844
3911
  "wantedOrigin",
3845
3912
  () => this.lookupImportedNames(target, modules)
3846
- ) ?? [];
3913
+ );
3847
3914
  this.importedNames.set(declaration, {
3848
3915
  names: found,
3849
3916
  walked: [...this.lastQueryWalked]
@@ -3862,8 +3929,8 @@ var ResolutionStore = class {
3862
3929
  return this.importOriginsOfMany([value], modules).get(value) ?? [];
3863
3930
  }
3864
3931
  /**
3865
- * The batched form: one demand set, one derivation, one widening
3866
- * walk shared by every value. A discovery pass asks about every
3932
+ * The batched form: one demand set, one derivation, one round of
3933
+ * extraction shared by every value. A discovery pass asks about every
3867
3934
  * callee in a file, and per-value queries would re-pay demand
3868
3935
  * clearing and re-derivation once per call site.
3869
3936
  */
@@ -3891,49 +3958,12 @@ var ResolutionStore = class {
3891
3958
  this.wantValue("wantedCallOrigin", one.target);
3892
3959
  this.seedValue(one.target);
3893
3960
  }
3894
- const walked = /* @__PURE__ */ new Set();
3895
- this.lastQueryWalked = [];
3896
- let frontier = [];
3961
+ this.extractDemanded(pending.map((one) => one.target.getSourceFile()));
3897
3962
  for (const one of pending) {
3898
- const file = one.target.getSourceFile();
3899
- if (!walked.has(file.getFilePath()) && !frontier.includes(file)) {
3900
- frontier.push(file);
3901
- }
3902
- }
3903
- const open = new Set(pending);
3904
- for (let hop = 0; hop <= MAX_MODULE_HOPS && open.size > 0; hop++) {
3905
- const next = [];
3906
- for (const sourceFile of frontier) {
3907
- if (walked.has(sourceFile.getFilePath())) {
3908
- continue;
3909
- }
3910
- walked.add(sourceFile.getFilePath());
3911
- this.lastQueryWalked.push(sourceFile.getFilePath());
3912
- recordFileDependency(sourceFile.getFilePath());
3913
- this.extractFile(sourceFile);
3914
- next.push(...this.graph.importedFilesOf(sourceFile));
3915
- }
3916
- for (const one of [...open]) {
3917
- const found = this.lookupImportOrigins(one.target, modules);
3918
- if (found !== null) {
3919
- results.set(one.value, found);
3920
- this.importOrigins.set(one.key, {
3921
- origins: found,
3922
- walked: [...this.lastQueryWalked],
3923
- extractedAt: this.fullyExtracted.size
3924
- });
3925
- open.delete(one);
3926
- }
3927
- }
3928
- if (next.length === 0) {
3929
- break;
3930
- }
3931
- frontier = next;
3932
- }
3933
- for (const one of open) {
3934
- results.set(one.value, []);
3963
+ const origins = this.lookupImportOrigins(one.target, modules);
3964
+ results.set(one.value, origins);
3935
3965
  this.importOrigins.set(one.key, {
3936
- origins: [],
3966
+ origins,
3937
3967
  walked: [...this.lastQueryWalked],
3938
3968
  extractedAt: this.fullyExtracted.size
3939
3969
  });
@@ -3992,67 +4022,89 @@ var ResolutionStore = class {
3992
4022
  const refined = new Set(
3993
4023
  [...byPath.values()].filter((one) => one.path.length > 1).map((one) => (0, import_datalog2.tupleKey)(one.path.slice(0, -1)))
3994
4024
  );
3995
- const origins = [...byPath.values()].filter((one) => !refined.has((0, import_datalog2.tupleKey)(one.path))).sort((a, b) => a.path.join(".").localeCompare(b.path.join(".")));
3996
- if (origins.length > 0) {
3997
- return origins;
3998
- }
3999
- return all.some((one) => namesAPackage(one.module)) ? [] : null;
4025
+ return [...byPath.values()].filter((one) => !refined.has((0, import_datalog2.tupleKey)(one.path))).sort((a, b) => a.path.join(".").localeCompare(b.path.join(".")));
4000
4026
  }
4001
- /**
4002
- * Null tells the walk to keep widening. A value that landed in a
4003
- * package returns empty instead, because a library's own body is not
4004
- * here to read and widening would never find anything.
4005
- */
4006
4027
  lookupImportedNames(value, modules) {
4007
4028
  this.derive();
4008
- const origins = this.answerPairsFor("wantedComesFrom", nodeId(value));
4009
- const reached = new Set(origins);
4029
+ const reached = new Set(
4030
+ this.answerPairsFor("wantedComesFrom", nodeId(value))
4031
+ );
4010
4032
  for (const target of this.answersFor("wantedComesTo", nodeId(value))) {
4011
4033
  for (const entry of this.answerPairsFor("wantedCallsInto", target)) {
4012
4034
  reached.add(entry);
4013
4035
  }
4014
4036
  }
4015
- const names = namesFrom([...reached], modules);
4016
- if (names.length > 0) {
4017
- return names;
4018
- }
4019
- return origins.some((pair) => namesAPackage(pairHalves(pair).module)) ? [] : null;
4037
+ return namesFrom([...reached], modules);
4020
4038
  }
4021
- resolveByWaves(value, question, ask, alsoFrom) {
4039
+ askAbout(value, question, read2, alsoFrom) {
4022
4040
  try {
4023
- return this.walkForAnswer(value, question, ask, alsoFrom);
4041
+ this.wantValue(question, value);
4042
+ this.seedValue(value);
4043
+ this.extractDemanded(
4044
+ alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom]
4045
+ );
4046
+ return read2();
4024
4047
  } finally {
4025
4048
  this.forgetQuery();
4026
4049
  }
4027
4050
  }
4028
- walkForAnswer(value, question, ask, alsoFrom) {
4029
- this.wantValue(question, value);
4030
- this.seedValue(value);
4031
- const walked = /* @__PURE__ */ new Set();
4051
+ /**
4052
+ * Extract the seed files, then every module the rules ask for, until
4053
+ * they ask for nothing new. The files a question reads are then fixed
4054
+ * by the question alone, so its answer does not depend on what an
4055
+ * earlier question left in the store.
4056
+ */
4057
+ extractDemanded(seeds) {
4058
+ const project = seeds[0]?.getProject();
4059
+ if (project === void 0) {
4060
+ return;
4061
+ }
4062
+ const read2 = /* @__PURE__ */ new Set();
4063
+ const considered = /* @__PURE__ */ new Set();
4032
4064
  this.lastQueryWalked = [];
4033
- let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
4034
- for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
4035
- const next = [];
4036
- for (const sourceFile of frontier) {
4037
- if (walked.has(sourceFile.getFilePath())) {
4065
+ let pending = [...seeds];
4066
+ while (pending.length > 0) {
4067
+ const readThisRound = [];
4068
+ for (const sourceFile of pending) {
4069
+ const filePath = sourceFile.getFilePath();
4070
+ if (read2.has(filePath)) {
4038
4071
  continue;
4039
4072
  }
4040
- walked.add(sourceFile.getFilePath());
4041
- this.lastQueryWalked.push(sourceFile.getFilePath());
4042
- recordFileDependency(sourceFile.getFilePath());
4073
+ read2.add(filePath);
4074
+ readThisRound.push(filePath);
4075
+ this.lastQueryWalked.push(filePath);
4076
+ recordFileDependency(filePath);
4043
4077
  this.extractFile(sourceFile);
4044
- next.push(...this.graph.importedFilesOf(sourceFile));
4045
- }
4046
- const found = ask();
4047
- if (found !== null) {
4048
- return found;
4049
4078
  }
4050
- if (next.length === 0) {
4051
- return null;
4079
+ this.derive();
4080
+ pending = [];
4081
+ for (const moduleKey of this.demandedModules(readThisRound)) {
4082
+ if (considered.has(moduleKey)) {
4083
+ continue;
4084
+ }
4085
+ considered.add(moduleKey);
4086
+ const sourceFile = sourceFileFor(project, moduleKey);
4087
+ if (sourceFile !== void 0 && !read2.has(sourceFile.getFilePath())) {
4088
+ pending.push(sourceFile);
4089
+ }
4052
4090
  }
4053
- frontier = next;
4054
4091
  }
4055
- return null;
4092
+ }
4093
+ /**
4094
+ * Module keys the rules are waiting on. Demand facts are cleared
4095
+ * between queries, so the demand relations contain this query's
4096
+ * alone; the unrestricted program has none, and follows the imports
4097
+ * of the files read this round instead.
4098
+ */
4099
+ demandedModules(readThisRound) {
4100
+ if (RESOLUTION_PROGRAM.demands.length === 0) {
4101
+ return readThisRound.flatMap(
4102
+ (filePath) => this.db.lookup("importsModule", 0, filePath).map((tuple) => String(tuple[1]))
4103
+ );
4104
+ }
4105
+ return MODULE_DEMANDS.flatMap(
4106
+ (relation) => this.db.facts(relation).map((tuple) => String(tuple[0]))
4107
+ );
4056
4108
  }
4057
4109
  /**
4058
4110
  * The question and the chain that settled it are dropped. The files
@@ -4071,6 +4123,27 @@ var ResolutionStore = class {
4071
4123
  this.stale = true;
4072
4124
  }
4073
4125
  }
4126
+ wantExportsOf(filePath) {
4127
+ if (this.db.add("wantedExportsOf", [filePath]) === "added") {
4128
+ this.stale = true;
4129
+ }
4130
+ }
4131
+ /**
4132
+ * Discovery walks the table in order, and the rounds derive it in
4133
+ * the order files arrived. Deriving it again from the demand alone,
4134
+ * over the loaded closure, orders it by the file's own statements.
4135
+ */
4136
+ rederiveExportsOf(filePath) {
4137
+ if (QUERY_FACTS.length === 0) {
4138
+ return;
4139
+ }
4140
+ this.db.retract("wantedModuleExport", [
4141
+ ...this.db.lookup("wantedModuleExport", 0, filePath)
4142
+ ]);
4143
+ this.forgetQuery();
4144
+ this.wantExportsOf(filePath);
4145
+ this.derive();
4146
+ }
4074
4147
  /**
4075
4148
  * File extraction only reaches values hanging off exports, and a query
4076
4149
  * can be rooted anywhere, so the queried value gets its own facts.
@@ -4120,24 +4193,18 @@ var ResolutionStore = class {
4120
4193
  }
4121
4194
  candidates.add(node);
4122
4195
  }
4123
- if (candidates.size === 1) {
4124
- return { written: [...candidates][0] };
4125
- }
4126
- if (candidates.size > 1) {
4127
- return { written: null };
4128
- }
4129
- if (neverWritable(value)) {
4130
- return { written: null };
4196
+ if (candidates.size !== 1) {
4197
+ return null;
4131
4198
  }
4132
- return null;
4199
+ return [...candidates][0];
4133
4200
  }
4134
4201
  /**
4135
4202
  * Read these files' facts now, before anything asks a question.
4136
4203
  *
4137
- * A query widens along the imports of the file it starts in, which
4138
- * is where a value's definition is. What a caller passed is the other
4204
+ * A query follows the imports of the file it starts in, which is
4205
+ * where a value's definition is. What a caller passed is the other
4139
4206
  * direction, so a question about a parameter is answered only by
4140
- * files the walk never reaches on its own.
4207
+ * files the query never reaches on its own.
4141
4208
  */
4142
4209
  extractFiles(sourceFiles) {
4143
4210
  for (const sourceFile of sourceFiles) {
@@ -4153,15 +4220,14 @@ var ResolutionStore = class {
4153
4220
  return this.graph.filesReachingAnyPackage(fileSets);
4154
4221
  }
4155
4222
  /**
4156
- * The calls behind a receiver that `matches` accepts, walked out in
4157
- * waves the way every question is. The wave ends as soon as one file
4158
- * yields a match, and the caller applies the single-answer policy to
4159
- * the set; the resolution README's anchor section says why the rules
4160
- * cannot rank a nearer call above a farther one.
4223
+ * The calls behind a receiver that `matches` accepts. The caller
4224
+ * applies the single-answer policy to the set; the resolution
4225
+ * README's anchor section says why the rules cannot rank a nearer
4226
+ * call above a farther one.
4161
4227
  */
4162
4228
  anchorCallsOf(value, matches) {
4163
4229
  const target = factKeyOf(value);
4164
- return this.resolveByWaves(target, "wantedAnchor", () => {
4230
+ return this.askAbout(target, "wantedAnchor", () => {
4165
4231
  this.derive();
4166
4232
  const found = [];
4167
4233
  for (const id of this.answersFor("wantedAnchorCall", nodeId(target))) {
@@ -4170,12 +4236,12 @@ var ResolutionStore = class {
4170
4236
  found.push(node);
4171
4237
  }
4172
4238
  }
4173
- return found.length === 0 ? null : found;
4174
- }) ?? [];
4239
+ return found;
4240
+ });
4175
4241
  }
4176
4242
  /**
4177
4243
  * Every name a module exports and the values behind each, with
4178
- * re-export chains flattened by the rules. The frontier follows
4244
+ * re-export chains flattened by the rules. The demand follows
4179
4245
  * re-export targets only, so a barrel of barrels extracts its own
4180
4246
  * chain and nothing beside it, to any depth.
4181
4247
  */
@@ -4195,33 +4261,10 @@ var ResolutionStore = class {
4195
4261
  }
4196
4262
  }
4197
4263
  collectExports(sourceFile) {
4198
- const project = sourceFile.getProject();
4199
- const walked = /* @__PURE__ */ new Set();
4200
- let frontier = [sourceFile];
4201
- while (frontier.length > 0) {
4202
- const next = [];
4203
- for (const file of frontier) {
4204
- const filePath2 = file.getFilePath();
4205
- if (walked.has(filePath2)) {
4206
- continue;
4207
- }
4208
- walked.add(filePath2);
4209
- recordFileDependency(filePath2);
4210
- this.extractFile(file);
4211
- for (const target of this.reExportTargetsOf(filePath2)) {
4212
- const targetFile = sourceFileFor(project, target);
4213
- if (targetFile !== void 0) {
4214
- next.push(targetFile);
4215
- }
4216
- }
4217
- }
4218
- frontier = next;
4219
- }
4220
4264
  const filePath = sourceFile.getFilePath();
4221
- if (this.db.add("wantedExportsOf", [filePath]) === "added") {
4222
- this.stale = true;
4223
- }
4224
- this.derive();
4265
+ this.wantExportsOf(filePath);
4266
+ this.extractDemanded([sourceFile]);
4267
+ this.rederiveExportsOf(filePath);
4225
4268
  const exports2 = /* @__PURE__ */ new Map();
4226
4269
  for (const tuple of this.db.lookup("wantedModuleExport", 0, filePath)) {
4227
4270
  const node = this.table.byId.get(String(tuple[2]));
@@ -4238,22 +4281,9 @@ var ResolutionStore = class {
4238
4281
  }
4239
4282
  return exports2;
4240
4283
  }
4241
- /** Module keys this file re-exports from, read off its own facts. */
4242
- reExportTargetsOf(filePath) {
4243
- const targets = /* @__PURE__ */ new Set();
4244
- for (const tuple of this.db.lookup("reExports", 0, filePath)) {
4245
- targets.add(String(tuple[2]));
4246
- }
4247
- for (const tuple of this.db.lookup("reExportsAll", 0, filePath)) {
4248
- targets.add(String(tuple[1]));
4249
- }
4250
- return [...targets];
4251
- }
4252
4284
  /**
4253
- * Null keeps the wave walk widening; any candidate at all ends it,
4254
- * since more files can only add candidates, never take one away. The
4255
- * single-answer policy stays with the callers: picking one of two
4256
- * would make the result depend on the order the facts came in.
4285
+ * The single-answer policy stays with the callers: a value with two
4286
+ * sources is something to say at the site, not something to pick from.
4257
4287
  */
4258
4288
  lookupSources(value) {
4259
4289
  this.derive();
@@ -4267,7 +4297,7 @@ var ResolutionStore = class {
4267
4297
  candidates.add(resolved);
4268
4298
  }
4269
4299
  }
4270
- return candidates.size === 0 ? null : [...candidates];
4300
+ return [...candidates];
4271
4301
  }
4272
4302
  derive() {
4273
4303
  if (!this.stale) {
@@ -4326,18 +4356,6 @@ function pairHalves(pair) {
4326
4356
  const [module2 = "", name = ""] = (0, import_datalog2.tupleKeyParts)(pair);
4327
4357
  return { module: module2, name };
4328
4358
  }
4329
- function namesAPackage(moduleKey) {
4330
- return !moduleKey.startsWith("/") || moduleKey.includes("/node_modules/");
4331
- }
4332
- function neverWritable(value) {
4333
- if (!import_ts_morph16.Node.isIdentifier(value)) {
4334
- return false;
4335
- }
4336
- const declarations = value.getSymbol()?.getDeclarations() ?? [];
4337
- return declarations.some(
4338
- (declaration) => declaration.getSourceFile().getFilePath().includes("/node_modules/")
4339
- );
4340
- }
4341
4359
  function namesPackage2(moduleKey, packages) {
4342
4360
  return namesAnyPackage(
4343
4361
  [moduleKey, ...packagesDeclaring(moduleKey)],
@@ -4358,35 +4376,7 @@ var import_node_path3 = __toESM(require("path"), 1);
4358
4376
  // src/diagnostics.ts
4359
4377
  var import_node_path2 = __toESM(require("path"), 1);
4360
4378
  var import_ts_morph17 = require("ts-morph");
4361
- var import_behavioral_ir = require("@suss/behavioral-ir");
4362
- var emptyTally = () => ({
4363
- candidateFiles: 0,
4364
- unitsDiscovered: 0,
4365
- unitsInGatedFiles: 0,
4366
- effectsRecognized: 0,
4367
- unitsClaimed: 0,
4368
- selfCollisions: 0,
4369
- summariesProduced: 0,
4370
- failures: [],
4371
- helpersMatched: /* @__PURE__ */ new Set()
4372
- });
4373
- function recordPackFailure(tally, failure) {
4374
- const message = failure.error instanceof Error ? failure.error.message : String(failure.error);
4375
- tally?.failures.push({
4376
- hook: failure.hook,
4377
- file: failure.file,
4378
- message
4379
- });
4380
- return `[suss] pack "${failure.pack}" threw from ${failure.hook} while reading ${failure.file}: ${message}
4381
- `;
4382
- }
4383
- function createPackTallies(packs) {
4384
- const tallies = /* @__PURE__ */ new Map();
4385
- for (const pack of packs) {
4386
- tallies.set(pack.name, emptyTally());
4387
- }
4388
- return tallies;
4389
- }
4379
+ var import_extractor3 = require("@suss/extractor");
4390
4380
  function commonDirectoryOf(files) {
4391
4381
  const absolute = files.filter((f) => import_node_path2.default.isAbsolute(f));
4392
4382
  const first = absolute[0];
@@ -4463,30 +4453,6 @@ function unresolvedGatesFor(gates, args) {
4463
4453
  }
4464
4454
  return unresolved;
4465
4455
  }
4466
- var emptyCounts = () => ({
4467
- bound: 0,
4468
- providers: 0,
4469
- withBehavior: 0
4470
- });
4471
- function summaryCountsByPack(summaries) {
4472
- const counts = /* @__PURE__ */ new Map();
4473
- for (const summary of summaries) {
4474
- const recognition = summary.identity.boundaryBinding?.recognition;
4475
- if (recognition === void 0 || recognition === null) {
4476
- continue;
4477
- }
4478
- const entry = counts.get(recognition) ?? emptyCounts();
4479
- entry.bound += 1;
4480
- if (import_behavioral_ir.BOUNDARY_ROLE[summary.kind] === "provider") {
4481
- entry.providers += 1;
4482
- }
4483
- if (summary.transitions.length > 0) {
4484
- entry.withBehavior += 1;
4485
- }
4486
- counts.set(recognition, entry);
4487
- }
4488
- return counts;
4489
- }
4490
4456
  function helpersWithNoCall(patterns, matched, projectRoot) {
4491
4457
  const missing = [];
4492
4458
  for (const pattern of patterns) {
@@ -4507,11 +4473,15 @@ function shortModule(module2, projectRoot) {
4507
4473
  return import_node_path2.default.relative(projectRoot, module2);
4508
4474
  }
4509
4475
  function buildExtractionReport(args) {
4510
- const bySummary = summaryCountsByPack(args.summaries);
4476
+ const bySummary = (0, import_extractor3.summaryCountsByPack)(args.summaries);
4511
4477
  const packFunnels = args.packs.map((pack) => {
4512
4478
  const gates = packIsUngated(pack) ? [] : collectPackGates(pack);
4513
- const tally = args.tallies.get(pack.name) ?? emptyTally();
4514
- const counted = bySummary.get(pack.name) ?? emptyCounts();
4479
+ const tally = args.tallies.get(pack.name) ?? (0, import_extractor3.emptyTally)();
4480
+ const counted = bySummary.get(pack.name) ?? {
4481
+ bound: 0,
4482
+ providers: 0,
4483
+ withBehavior: 0
4484
+ };
4515
4485
  return {
4516
4486
  pack: pack.name,
4517
4487
  version: pack.version ?? null,
@@ -4740,13 +4710,26 @@ function propertiesReached(object, resolution, seen) {
4740
4710
  }
4741
4711
  function writtenNodeOf(value, resolution) {
4742
4712
  const written = factKeyOf(value);
4743
- if (import_ts_morph18.Node.isCallExpression(written) || import_ts_morph18.Node.isNewExpression(written)) {
4713
+ if (import_ts_morph18.Node.isNewExpression(written)) {
4744
4714
  return written;
4745
4715
  }
4716
+ if (import_ts_morph18.Node.isCallExpression(written)) {
4717
+ return writtenThroughCall(written, resolution);
4718
+ }
4746
4719
  if (resolution === void 0 || !couldNameAValue(written)) {
4747
4720
  return null;
4748
4721
  }
4749
- return resolution.resolveWrittenValue(written);
4722
+ const resolved = resolution.resolveWrittenValue(written);
4723
+ return resolved !== null && import_ts_morph18.Node.isCallExpression(resolved) ? writtenThroughCall(resolved, resolution) : resolved;
4724
+ }
4725
+ function writtenThroughCall(call, resolution) {
4726
+ if (resolution !== void 0 && resolution.resolveCallable(call.getExpression()) !== null) {
4727
+ const resolved = resolution.resolveWrittenValue(call);
4728
+ if (resolved !== null) {
4729
+ return resolved;
4730
+ }
4731
+ }
4732
+ return call;
4750
4733
  }
4751
4734
  function stringValueOf(value, resolution) {
4752
4735
  const written = factKeyOf(value);
@@ -4808,7 +4791,7 @@ function discoverClientCalls(sourceFile, match, kind, resolution) {
4808
4791
  }
4809
4792
  } else if (import_ts_morph19.Node.isPropertyAccessExpression(callee)) {
4810
4793
  const subject = callee.getExpression();
4811
- if (import_ts_morph19.Node.isIdentifier(subject) && (isClientImport(subject, match, resolution, true) || clientVarNames.has(subject.getText()) || resolvesToKnownInstance(subject, match, resolution))) {
4794
+ if (import_ts_morph19.Node.isIdentifier(subject) && (isClientImport(subject, match, resolution, true) || clientVarNames.has(subject.getText())) || resolvesToKnownInstance(subject, match, resolution)) {
4812
4795
  methodName = callee.getName();
4813
4796
  if (methodFilter === null || methodFilter.has(methodName)) {
4814
4797
  matched = true;
@@ -6412,14 +6395,14 @@ function unitForJsxRoute(node, match, routeNames, kind, resolution) {
6412
6395
  }
6413
6396
  return routeUnit(node, kind, match.method, resolved.path, target, resolution);
6414
6397
  }
6415
- function routeUnit(at, kind, method, path15, target, resolution) {
6416
- const routeInfo = { method, path: path15 };
6398
+ function routeUnit(at, kind, method, path14, target, resolution) {
6399
+ const routeInfo = { method, path: path14 };
6417
6400
  if (target.kind === "unreadable") {
6418
6401
  return {
6419
6402
  func: null,
6420
6403
  announcedAt: at,
6421
6404
  kind,
6422
- name: path15,
6405
+ name: path14,
6423
6406
  nameKind: "label",
6424
6407
  routeInfo
6425
6408
  };
@@ -6618,7 +6601,6 @@ function labelOfExpression(node) {
6618
6601
  }
6619
6602
 
6620
6603
  // src/discovery/packageExports.ts
6621
- var import_node_fs3 = __toESM(require("fs"), 1);
6622
6604
  var import_ts_morph31 = require("ts-morph");
6623
6605
 
6624
6606
  // src/packageExports.ts
@@ -6647,7 +6629,8 @@ function resolvePackageExports(packageJsonPath, opts = {}) {
6647
6629
  packageName,
6648
6630
  subPath,
6649
6631
  exportPathPrefix: subPathToPrefix(subPath),
6650
- sourceFile: src
6632
+ sourceFile: src,
6633
+ publishedFile: import_node_path5.default.resolve(pkgDir, distPath)
6651
6634
  });
6652
6635
  };
6653
6636
  const exportsField = pkg.exports;
@@ -6667,6 +6650,53 @@ function resolvePackageExports(packageJsonPath, opts = {}) {
6667
6650
  }
6668
6651
  return { packageName, entries, warnings };
6669
6652
  }
6653
+ var packageExportsCache = /* @__PURE__ */ new Map();
6654
+ var PATH_STAMP_SEPARATOR = "";
6655
+ function packageJsonStamp(packageJsonPath) {
6656
+ try {
6657
+ const stat = import_node_fs2.default.statSync(packageJsonPath);
6658
+ return `${stat.mtimeMs}:${stat.size}`;
6659
+ } catch {
6660
+ return "unreadable";
6661
+ }
6662
+ }
6663
+ function resolvePackageExportsCached(packageJsonPath) {
6664
+ const stamp2 = packageJsonStamp(packageJsonPath);
6665
+ const key = `${packageJsonPath}${PATH_STAMP_SEPARATOR}${stamp2}`;
6666
+ const cached = packageExportsCache.get(key);
6667
+ if (cached !== void 0) {
6668
+ return cached;
6669
+ }
6670
+ const fresh = resolvePackageExports(packageJsonPath);
6671
+ packageExportsCache.set(key, fresh);
6672
+ return fresh;
6673
+ }
6674
+ function namedPackageDirAbove(start) {
6675
+ let at = import_node_path5.default.resolve(start);
6676
+ for (let up = 0; up < 12; up += 1) {
6677
+ if (packageNameAt(at) !== null) {
6678
+ return at;
6679
+ }
6680
+ const parent = import_node_path5.default.dirname(at);
6681
+ if (parent === at) {
6682
+ break;
6683
+ }
6684
+ at = parent;
6685
+ }
6686
+ return null;
6687
+ }
6688
+ function packageNameAt(dir) {
6689
+ try {
6690
+ const manifest = JSON.parse(
6691
+ import_node_fs2.default.readFileSync(import_node_path5.default.join(dir, "package.json"), "utf8")
6692
+ );
6693
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
6694
+ return manifest.name;
6695
+ }
6696
+ } catch {
6697
+ }
6698
+ return null;
6699
+ }
6670
6700
  function pickConditional(value, warnings, key) {
6671
6701
  if (value === void 0 || value === null) {
6672
6702
  if (value === null) {
@@ -6716,8 +6746,55 @@ function resolveSourceFor(pkgDir, distPath, srcRoot) {
6716
6746
  return null;
6717
6747
  }
6718
6748
 
6749
+ // src/resolve/sourceDeclaration.ts
6750
+ var import_node_path6 = __toESM(require("path"), 1);
6751
+ function sourceDeclarationsBehind(decl, resolution, visited = /* @__PURE__ */ new Set()) {
6752
+ const file = decl.getSourceFile();
6753
+ if (!file.isDeclarationFile() || file.isInNodeModules()) {
6754
+ return [decl];
6755
+ }
6756
+ const packageDir = namedPackageDirAbove(import_node_path6.default.dirname(file.getFilePath()));
6757
+ if (packageDir === null || visited.has(packageDir)) {
6758
+ return [decl];
6759
+ }
6760
+ visited.add(packageDir);
6761
+ const project = file.getProject();
6762
+ const { entries } = resolvePackageExportsCached(
6763
+ import_node_path6.default.join(packageDir, "package.json")
6764
+ );
6765
+ const found = [];
6766
+ for (const entry of entries) {
6767
+ const published = sourceFileFor(project, entry.publishedFile);
6768
+ const source = sourceFileFor(project, entry.sourceFile);
6769
+ if (published === void 0 || source === void 0) {
6770
+ continue;
6771
+ }
6772
+ for (const [name, nodes] of resolution.exportsOf(published)) {
6773
+ if (!nodes.some((node) => sameDeclaration(node, decl))) {
6774
+ continue;
6775
+ }
6776
+ for (const behind of resolution.exportsOf(source).get(name) ?? []) {
6777
+ for (const candidate of sourceDeclarationsBehind(
6778
+ behind,
6779
+ resolution,
6780
+ visited
6781
+ )) {
6782
+ if (!found.includes(candidate)) {
6783
+ found.push(candidate);
6784
+ }
6785
+ }
6786
+ }
6787
+ }
6788
+ }
6789
+ return found.length > 0 ? found : [decl];
6790
+ }
6791
+ function sameDeclaration(a, b) {
6792
+ return a === b || a.getSourceFile().getFilePath() === b.getSourceFile().getFilePath() && a.getStart() === b.getStart() && a.getEnd() === b.getEnd();
6793
+ }
6794
+
6719
6795
  // src/discovery/factorySurface.ts
6720
6796
  var import_ts_morph30 = require("ts-morph");
6797
+ var MAX_BUILDER_HOPS = 2;
6721
6798
  function surfaceMethods(decl) {
6722
6799
  if (import_ts_morph30.Node.isClassDeclaration(decl)) {
6723
6800
  return surfaceClassMethods(decl);
@@ -6757,16 +6834,20 @@ function surfaceClassMethods(cls) {
6757
6834
  function surfaceFactoryReturnMethods(fn) {
6758
6835
  const out = [];
6759
6836
  const seen = /* @__PURE__ */ new Set();
6837
+ collectFromFunctionReturns(fn, out, seen, 0);
6838
+ return out;
6839
+ }
6840
+ function collectFromFunctionReturns(fn, out, seen, depth) {
6760
6841
  if (import_ts_morph30.Node.isArrowFunction(fn)) {
6761
6842
  const body2 = fn.getBody();
6762
6843
  if (import_ts_morph30.Node.isExpression(body2)) {
6763
- collectFromObjectLiteral(body2, out, seen);
6764
- return out;
6844
+ collectFromReturnValue(body2, out, seen, depth);
6845
+ return;
6765
6846
  }
6766
6847
  }
6767
6848
  const body = fn.getBody?.();
6768
6849
  if (body === void 0) {
6769
- return out;
6850
+ return;
6770
6851
  }
6771
6852
  const returns = [];
6772
6853
  body.forEachDescendant((node, traversal) => {
@@ -6783,9 +6864,53 @@ function surfaceFactoryReturnMethods(fn) {
6783
6864
  if (expr === void 0) {
6784
6865
  continue;
6785
6866
  }
6867
+ collectFromReturnValue(expr, out, seen, depth);
6868
+ }
6869
+ }
6870
+ function collectFromReturnValue(node, out, seen, depth) {
6871
+ const expr = peelParens(node);
6872
+ if (import_ts_morph30.Node.isObjectLiteralExpression(expr)) {
6786
6873
  collectFromObjectLiteral(expr, out, seen);
6874
+ return;
6787
6875
  }
6788
- return out;
6876
+ if (depth >= MAX_BUILDER_HOPS || !import_ts_morph30.Node.isCallExpression(expr)) {
6877
+ return;
6878
+ }
6879
+ const callee = expr.getExpression();
6880
+ if (!import_ts_morph30.Node.isIdentifier(callee)) {
6881
+ return;
6882
+ }
6883
+ const helper = sameFileFunctionBehind(callee);
6884
+ if (helper !== null) {
6885
+ collectFromFunctionReturns(helper, out, seen, depth + 1);
6886
+ }
6887
+ }
6888
+ function sameFileFunctionBehind(id) {
6889
+ const symbol = id.getSymbol();
6890
+ if (symbol === void 0) {
6891
+ return null;
6892
+ }
6893
+ return functionAmong(
6894
+ symbol.getDeclarations(),
6895
+ id.getSourceFile().getFilePath()
6896
+ );
6897
+ }
6898
+ function functionAmong(declarations, filePath) {
6899
+ for (const decl of declarations) {
6900
+ if (decl.getSourceFile().getFilePath() !== filePath) {
6901
+ continue;
6902
+ }
6903
+ if (import_ts_morph30.Node.isFunctionDeclaration(decl)) {
6904
+ return decl;
6905
+ }
6906
+ if (import_ts_morph30.Node.isVariableDeclaration(decl)) {
6907
+ const init = decl.getInitializer();
6908
+ if (init !== void 0 && (import_ts_morph30.Node.isArrowFunction(init) || import_ts_morph30.Node.isFunctionExpression(init))) {
6909
+ return init;
6910
+ }
6911
+ }
6912
+ }
6913
+ return null;
6789
6914
  }
6790
6915
  function collectFromObjectLiteral(node, out, seen) {
6791
6916
  const e = peelParens(node);
@@ -6820,31 +6945,31 @@ function collectFromObjectLiteral(node, out, seen) {
6820
6945
  out.push({ func: v3, name });
6821
6946
  }
6822
6947
  }
6948
+ if (import_ts_morph30.Node.isShorthandPropertyAssignment(prop)) {
6949
+ collectShorthandFunction(prop, out, seen);
6950
+ }
6951
+ }
6952
+ }
6953
+ function collectShorthandFunction(prop, out, seen) {
6954
+ const name = prop.getName();
6955
+ if (seen.has(name)) {
6956
+ return;
6957
+ }
6958
+ const symbol = prop.getValueSymbol();
6959
+ if (symbol === void 0) {
6960
+ return;
6961
+ }
6962
+ const fn = functionAmong(
6963
+ symbol.getDeclarations(),
6964
+ prop.getSourceFile().getFilePath()
6965
+ );
6966
+ if (fn !== null) {
6967
+ seen.add(name);
6968
+ out.push({ func: fn, name });
6823
6969
  }
6824
6970
  }
6825
6971
 
6826
6972
  // src/discovery/packageExports.ts
6827
- var packageExportsCache = /* @__PURE__ */ new Map();
6828
- var PATH_STAMP_SEPARATOR = "";
6829
- function packageJsonStamp(packageJsonPath) {
6830
- try {
6831
- const stat = import_node_fs3.default.statSync(packageJsonPath);
6832
- return `${stat.mtimeMs}:${stat.size}`;
6833
- } catch {
6834
- return "unreadable";
6835
- }
6836
- }
6837
- function resolvePackageExportsCached(packageJsonPath) {
6838
- const stamp = packageJsonStamp(packageJsonPath);
6839
- const key = `${packageJsonPath}${PATH_STAMP_SEPARATOR}${stamp}`;
6840
- const cached = packageExportsCache.get(key);
6841
- if (cached !== void 0) {
6842
- return cached;
6843
- }
6844
- const fresh = resolvePackageExports(packageJsonPath);
6845
- packageExportsCache.set(key, fresh);
6846
- return fresh;
6847
- }
6848
6973
  function discoverPackageExports(sourceFile, match, kind, resolution) {
6849
6974
  if (match.packageJsonPath === void 0) {
6850
6975
  return [];
@@ -6879,7 +7004,10 @@ function discoverPackageExports(sourceFile, match, kind, resolution) {
6879
7004
  if (seenNames.has(key)) {
6880
7005
  continue;
6881
7006
  }
6882
- for (const decl of decls) {
7007
+ const candidates = decls.flatMap(
7008
+ (decl) => sourceDeclarationsBehind(decl, resolution)
7009
+ );
7010
+ for (const decl of candidates) {
6883
7011
  if (import_ts_morph31.Node.isVariableDeclaration(decl)) {
6884
7012
  const init = decl.getInitializer();
6885
7013
  if (init !== void 0 && (import_ts_morph31.Node.isArrowFunction(init) || import_ts_morph31.Node.isFunctionExpression(init))) {
@@ -7032,7 +7160,7 @@ function attributedCalls(sourceFile, packages, resolution) {
7032
7160
  return null;
7033
7161
  }
7034
7162
  if (!methodComesFromSource(callee)) {
7035
- return subject;
7163
+ return null;
7036
7164
  }
7037
7165
  return {
7038
7166
  packageName: subject.packageName,
@@ -7146,12 +7274,12 @@ function originEndOf(flattened) {
7146
7274
  const slash = flattened.indexOf("/", opener[0].length);
7147
7275
  return slash === -1 ? flattened.length : slash;
7148
7276
  }
7149
- function appendPathText(path15, text) {
7277
+ function appendPathText(path14, text) {
7150
7278
  const idx = text.search(/[?#]/);
7151
7279
  if (idx === -1) {
7152
- return { path: path15 + text, stop: false };
7280
+ return { path: path14 + text, stop: false };
7153
7281
  }
7154
- return { path: path15 + text.slice(0, idx), stop: true };
7282
+ return { path: path14 + text.slice(0, idx), stop: true };
7155
7283
  }
7156
7284
  function parseAbsoluteUrl(text) {
7157
7285
  try {
@@ -7175,8 +7303,8 @@ function pathnameOfAbsoluteLiteral(text) {
7175
7303
  return stripQueryAndFragment(stripOriginManually(text));
7176
7304
  }
7177
7305
  function pathFromLiteralUrl(text) {
7178
- const path15 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
7179
- return path15 === "" ? void 0 : path15;
7306
+ const path14 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
7307
+ return path14 === "" ? void 0 : path14;
7180
7308
  }
7181
7309
  function pathFromTemplateUrl(arg, resolution) {
7182
7310
  const headText = arg.getHead().getLiteralText();
@@ -7184,7 +7312,7 @@ function pathFromTemplateUrl(arg, resolution) {
7184
7312
  const tails = spans.map((span) => span.getLiteral().getLiteralText());
7185
7313
  const originEnd = originEndOf([headText, ...tails].join(SUBSTITUTION));
7186
7314
  const head = appendPathText("", headText.slice(originEnd));
7187
- let path15 = head.path;
7315
+ let path14 = head.path;
7188
7316
  let stop = head.stop;
7189
7317
  let at = headText.length;
7190
7318
  for (let i = 0; i < spans.length && !stop; i++) {
@@ -7194,18 +7322,18 @@ function pathFromTemplateUrl(arg, resolution) {
7194
7322
  continue;
7195
7323
  }
7196
7324
  if (at >= originEnd) {
7197
- path15 += holeText(span.getExpression(), resolution);
7325
+ path14 += holeText(span.getExpression(), resolution);
7198
7326
  }
7199
7327
  at += SUBSTITUTION.length;
7200
7328
  const appended = appendPathText(
7201
- path15,
7329
+ path14,
7202
7330
  tailText.slice(Math.max(0, originEnd - at))
7203
7331
  );
7204
- path15 = appended.path;
7332
+ path14 = appended.path;
7205
7333
  stop = appended.stop;
7206
7334
  at += tailText.length;
7207
7335
  }
7208
- return path15 === "" ? void 0 : path15;
7336
+ return path14 === "" ? void 0 : path14;
7209
7337
  }
7210
7338
  function pathFromUrlNode(node, resolution) {
7211
7339
  if (import_ts_morph33.Node.isStringLiteral(node)) {
@@ -7275,12 +7403,12 @@ function discoverRegistrationCalls(sourceFile, match, kind, bindingExtraction, r
7275
7403
  match.importName,
7276
7404
  resolution
7277
7405
  );
7278
- if (registrationSubjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution)) {
7279
- return results;
7280
- }
7281
7406
  const registrationMethods = match.registrationChain.map(
7282
7407
  (c) => c.startsWith(".") ? c.slice(1) : c
7283
7408
  );
7409
+ if (registrationSubjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution, registrationMethods)) {
7410
+ return results;
7411
+ }
7284
7412
  sourceFile.forEachDescendant((node) => {
7285
7413
  if (!import_ts_morph34.Node.isCallExpression(node)) {
7286
7414
  return;
@@ -7474,8 +7602,54 @@ function receiverOf(subject, subjects, match, resolution) {
7474
7602
  );
7475
7603
  return candidates > 1 ? { kind: "several", candidates } : NOT_A_ROUTABLE;
7476
7604
  }
7477
- function storeCanFindSubjects(sourceFile, match, resolution) {
7478
- return resolution !== void 0 && importDeclarationsOf(sourceFile, [match.importModule]).length > 0;
7605
+ function storeCanFindSubjects(sourceFile, match, resolution, methods = []) {
7606
+ if (resolution === void 0) {
7607
+ return false;
7608
+ }
7609
+ if (importDeclarationsOf(sourceFile, [match.importModule]).length > 0) {
7610
+ return true;
7611
+ }
7612
+ return methods.length > 0 && callsOnCallResult(sourceFile, methods);
7613
+ }
7614
+ function callsOnCallResult(sourceFile, methods) {
7615
+ let called = methodsCalledOnCallResults.get(sourceFile);
7616
+ if (called === void 0) {
7617
+ called = collectMethodsCalledOnCallResults(sourceFile);
7618
+ methodsCalledOnCallResults.set(sourceFile, called);
7619
+ }
7620
+ return methods.some((method) => called.has(method));
7621
+ }
7622
+ var methodsCalledOnCallResults = /* @__PURE__ */ new WeakMap();
7623
+ function collectMethodsCalledOnCallResults(sourceFile) {
7624
+ const setToCalls = /* @__PURE__ */ new Set();
7625
+ const calledOn = /* @__PURE__ */ new Map();
7626
+ sourceFile.forEachDescendant((node) => {
7627
+ if (import_ts_morph34.Node.isVariableDeclaration(node)) {
7628
+ const init = node.getInitializer();
7629
+ if (init !== void 0 && import_ts_morph34.Node.isCallExpression(init)) {
7630
+ setToCalls.add(node.getName());
7631
+ }
7632
+ return;
7633
+ }
7634
+ if (!import_ts_morph34.Node.isCallExpression(node)) {
7635
+ return;
7636
+ }
7637
+ const callee = node.getExpression();
7638
+ if (!import_ts_morph34.Node.isPropertyAccessExpression(callee) || !import_ts_morph34.Node.isIdentifier(callee.getExpression())) {
7639
+ return;
7640
+ }
7641
+ const receiver = callee.getExpression().getText();
7642
+ const methods = calledOn.get(receiver) ?? /* @__PURE__ */ new Set();
7643
+ methods.add(callee.getName());
7644
+ calledOn.set(receiver, methods);
7645
+ });
7646
+ const called = /* @__PURE__ */ new Set();
7647
+ for (const name of setToCalls) {
7648
+ for (const method of calledOn.get(name) ?? []) {
7649
+ called.add(method);
7650
+ }
7651
+ }
7652
+ return called;
7479
7653
  }
7480
7654
  function registrationSubjectsOf(sourceFile, importModule, importName, resolution) {
7481
7655
  const importedLocalName = resolveImportedLocalName(
@@ -7540,9 +7714,33 @@ function registrationSubjectIdsOf(sourceFile, matches, resolution) {
7540
7714
  ).values()) {
7541
7715
  ids.add(nodeId(node));
7542
7716
  }
7717
+ for (const node of unboundConstructionsOf(
7718
+ sourceFile,
7719
+ match.importModule,
7720
+ match.importName
7721
+ )) {
7722
+ ids.add(nodeId(node));
7723
+ }
7543
7724
  }
7544
7725
  return ids;
7545
7726
  }
7727
+ function unboundConstructionsOf(sourceFile, importModule, importName) {
7728
+ const importedLocalName = resolveImportedLocalName(
7729
+ sourceFile,
7730
+ importModule,
7731
+ importName
7732
+ );
7733
+ if (importedLocalName === null) {
7734
+ return [];
7735
+ }
7736
+ const constructions = [];
7737
+ sourceFile.forEachDescendant((node) => {
7738
+ if ((import_ts_morph34.Node.isCallExpression(node) || import_ts_morph34.Node.isNewExpression(node)) && node.getExpression().getText() === importedLocalName && !import_ts_morph34.Node.isVariableDeclaration(node.getParent())) {
7739
+ constructions.push(node);
7740
+ }
7741
+ });
7742
+ return constructions;
7743
+ }
7546
7744
  function discoverMountEdges(sourceFile, match, mount, knownSubjectIds, resolution) {
7547
7745
  const subjects = registrationSubjectsOf(
7548
7746
  sourceFile,
@@ -7550,7 +7748,7 @@ function discoverMountEdges(sourceFile, match, mount, knownSubjectIds, resolutio
7550
7748
  match.importName,
7551
7749
  resolution
7552
7750
  );
7553
- if (subjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution)) {
7751
+ if (subjects.size === 0 && !storeCanFindSubjects(sourceFile, match, resolution, [mount.method])) {
7554
7752
  return [];
7555
7753
  }
7556
7754
  const edges = [];
@@ -7631,11 +7829,11 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
7631
7829
  return null;
7632
7830
  }
7633
7831
  const method2 = stringProperty(routeObject, binding.method.property);
7634
- const path16 = stringProperty(routeObject, binding.path.property);
7635
- if (method2 === null || path16 === null) {
7832
+ const path15 = stringProperty(routeObject, binding.path.property);
7833
+ if (method2 === null || path15 === null) {
7636
7834
  return null;
7637
7835
  }
7638
- return { method: method2.toUpperCase(), path: path16 };
7836
+ return { method: method2.toUpperCase(), path: path15 };
7639
7837
  }
7640
7838
  if (binding.method.type !== "fromRegistration" || binding.path.type !== "fromArgument") {
7641
7839
  return null;
@@ -7652,8 +7850,8 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
7652
7850
  method = arg.getLiteralValue().toUpperCase();
7653
7851
  }
7654
7852
  const pathArg = call.getArguments()[binding.path.position];
7655
- const path15 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
7656
- return path15 === void 0 ? null : { method, path: path15 };
7853
+ const path14 = pathArg === void 0 ? void 0 : pathFromArgument(pathArg, resolution);
7854
+ return path14 === void 0 ? null : { method, path: path14 };
7657
7855
  }
7658
7856
 
7659
7857
  // src/discovery/registrationLoop.ts
@@ -7785,7 +7983,7 @@ function bodyCallsOnRoutable(loop, guard, resolution) {
7785
7983
  }
7786
7984
  function readRouteSpec(element, shape, resolution) {
7787
7985
  let method = null;
7788
- let path15 = null;
7986
+ let path14 = null;
7789
7987
  let handler = null;
7790
7988
  for (const prop of element.getProperties()) {
7791
7989
  if (import_ts_morph35.Node.isMethodDeclaration(prop) && prop.getName() === shape.handlerKey) {
@@ -7803,16 +8001,16 @@ function readRouteSpec(element, shape, resolution) {
7803
8001
  if (name === shape.methodKey) {
7804
8002
  method = readStringLiteralValue(init);
7805
8003
  } else if (name === shape.pathKey) {
7806
- path15 = readStringLiteralValue(init);
8004
+ path14 = readStringLiteralValue(init);
7807
8005
  } else if (name === shape.handlerKey) {
7808
8006
  const func = functionValueOf(init, resolution);
7809
8007
  handler = func === null ? null : { func, name: handlerName(init) };
7810
8008
  }
7811
8009
  }
7812
- if (method === null || path15 === null || handler === null) {
8010
+ if (method === null || path14 === null || handler === null) {
7813
8011
  return null;
7814
8012
  }
7815
- return { method, path: path15, handler: handler.func, handlerName: handler.name };
8013
+ return { method, path: path14, handler: handler.func, handlerName: handler.name };
7816
8014
  }
7817
8015
  function readStringLiteralValue(node) {
7818
8016
  if (import_ts_morph35.Node.isStringLiteral(node) || import_ts_morph35.Node.isNoSubstitutionTemplateLiteral(node)) {
@@ -7825,12 +8023,13 @@ function handlerName(value) {
7825
8023
  }
7826
8024
 
7827
8025
  // src/discovery/registrationTemplate.ts
7828
- var import_node_path6 = __toESM(require("path"), 1);
8026
+ var import_node_path7 = __toESM(require("path"), 1);
7829
8027
  var import_ts_morph37 = require("ts-morph");
7830
8028
  var import_resolution4 = require("@suss/resolution");
7831
8029
 
7832
8030
  // src/resolve/unfollowedCall.ts
7833
8031
  var import_ts_morph36 = require("ts-morph");
8032
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
7834
8033
  function isInExternalCode(sourceFile) {
7835
8034
  if (sourceFile.isDeclarationFile()) {
7836
8035
  return true;
@@ -7884,6 +8083,19 @@ function declarationsBehind(symbol) {
7884
8083
  function isParameterOf(declaration, scanning) {
7885
8084
  return import_ts_morph36.Node.isParameterDeclaration(declaration) && declaration.getParent() === scanning;
7886
8085
  }
8086
+ function parameterIndexOf(declarations, scanning) {
8087
+ if (scanning === void 0) {
8088
+ return void 0;
8089
+ }
8090
+ const params = scanning.getParameters();
8091
+ for (const declaration of declarations) {
8092
+ const index = params.findIndex((param) => param === declaration);
8093
+ if (index !== -1) {
8094
+ return index;
8095
+ }
8096
+ }
8097
+ return void 0;
8098
+ }
7887
8099
  function classifyStop(declarations, scanning) {
7888
8100
  if (declarations.length === 0) {
7889
8101
  return "noDeclaration";
@@ -7904,36 +8116,6 @@ function classifyStop(declarations, scanning) {
7904
8116
  }
7905
8117
  return "unsettledValue";
7906
8118
  }
7907
- var RECORDED = {
7908
- noBody: true,
7909
- unsettledValue: true,
7910
- multipleSources: true,
7911
- outsideRun: false,
7912
- noDeclaration: false,
7913
- callerSupplied: false,
7914
- multipleReceivers: true
7915
- };
7916
- function worthRecording(reason) {
7917
- return RECORDED[reason];
7918
- }
7919
- var STOP_SENTENCE = {
7920
- noBody: ({ callee }) => `The call to ${callee} lands on a declaration with no body, so whatever runs there is missing from this summary`,
7921
- unsettledValue: ({ callee }) => `The call to ${callee} goes through a value this run could not settle, so whatever runs there is missing from this summary`,
7922
- multipleSources: ({ callee }) => `The call to ${callee} reaches a value with more than one possible source, so whatever runs there is missing from this summary`,
7923
- outsideRun: ({ callee }) => `The call to ${callee} lands in a package whose source is not in this run, so whatever runs there is missing from this summary`,
7924
- noDeclaration: ({ callee }) => `The call to ${callee} has no declaration this run could find, so whatever runs there is missing from this summary`,
7925
- callerSupplied: ({ callee }) => `The call to ${callee} runs the function this unit's caller passed in, so what happens there is decided at the call site`,
7926
- multipleReceivers: ({ callee, candidates }) => `The call to ${callee} is made on a receiver this run reads as ${candidates ?? "several"} different values, so nothing says which one it registers on and the registration is left out`
7927
- };
7928
- function unfollowedCallGap(stop) {
7929
- return {
7930
- type: "unfollowedCall",
7931
- conditions: [],
7932
- consequence: "unknown",
7933
- description: STOP_SENTENCE[stop.reason](stop),
7934
- callee: stop.callee
7935
- };
7936
- }
7937
8119
 
7938
8120
  // src/discovery/registrationTemplate.ts
7939
8121
  function discoverRegistrationTemplates(sourceFile, match, kind, resolution, mountPrefixes) {
@@ -7943,8 +8125,8 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution, moun
7943
8125
  const subject = subjectOfCall(sourceFile, args, match, resolution);
7944
8126
  const prefix = subject === void 0 ? "" : mountPrefixes?.effectivePrefixFor(subject) ?? "";
7945
8127
  for (const reg of match.registrations) {
7946
- const path15 = substitutePath(reg.pathTemplate, args);
7947
- if (path15 === null) {
8128
+ const path14 = substitutePath(reg.pathTemplate, args);
8129
+ if (path14 === null) {
7948
8130
  continue;
7949
8131
  }
7950
8132
  const handler = resolveHandler(reg.handlerArg, args, resolution);
@@ -7958,7 +8140,7 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution, moun
7958
8140
  ...subject === void 0 ? {} : { registrationSubjectId: nodeId(subject) },
7959
8141
  routeInfo: {
7960
8142
  method: reg.method.toUpperCase(),
7961
- path: prefix === "" ? path15 : (0, import_resolution4.joinMountedPath)(prefix, path15)
8143
+ path: prefix === "" ? path14 : (0, import_resolution4.joinMountedPath)(prefix, path14)
7962
8144
  }
7963
8145
  });
7964
8146
  }
@@ -7991,7 +8173,7 @@ function subjectOfCall(sourceFile, args, match, resolution) {
7991
8173
  }
7992
8174
  function helperCallsOf(sourceFile, match, resolution) {
7993
8175
  const module2 = match.importModule;
7994
- if (module2 !== void 0 && !import_node_path6.default.isAbsolute(module2)) {
8176
+ if (module2 !== void 0 && !import_node_path7.default.isAbsolute(module2)) {
7995
8177
  const store = resolution ?? new ResolutionStore();
7996
8178
  return callsResolvingTo(sourceFile, store, {
7997
8179
  module: module2,
@@ -8437,24 +8619,24 @@ function declName(decl) {
8437
8619
 
8438
8620
  // src/parameterReads.ts
8439
8621
  function pathAbove(reference) {
8440
- const path15 = [];
8622
+ const path14 = [];
8441
8623
  let current2 = reference;
8442
8624
  for (; ; ) {
8443
8625
  const parent = current2.getParent();
8444
8626
  if (parent !== void 0 && import_ts_morph40.Node.isPropertyAccessExpression(parent) && parent.getExpression() === current2) {
8445
- path15.push(parent.getName());
8627
+ path14.push(parent.getName());
8446
8628
  current2 = parent;
8447
8629
  continue;
8448
8630
  }
8449
8631
  if (parent !== void 0 && import_ts_morph40.Node.isElementAccessExpression(parent) && parent.getExpression() === current2) {
8450
8632
  const argument = parent.getArgumentExpression();
8451
8633
  if (argument !== void 0 && import_ts_morph40.Node.isStringLiteral(argument)) {
8452
- path15.push(argument.getLiteralValue());
8634
+ path14.push(argument.getLiteralValue());
8453
8635
  current2 = parent;
8454
8636
  continue;
8455
8637
  }
8456
8638
  }
8457
- return path15;
8639
+ return path14;
8458
8640
  }
8459
8641
  }
8460
8642
  function declaresParameterOf(node, func) {
@@ -8502,7 +8684,7 @@ function parameterReads(func, parameterNames2, barriers = NO_BARRIERS) {
8502
8684
 
8503
8685
  // src/resolve/readName.ts
8504
8686
  var import_ts_morph41 = require("ts-morph");
8505
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
8687
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
8506
8688
  var MAX_HELPER_HOPS = 2;
8507
8689
  var UNNAMED_HOLE = "param";
8508
8690
  function readName(expr, options = {}) {
@@ -8517,7 +8699,7 @@ function readName(expr, options = {}) {
8517
8699
  return name;
8518
8700
  }
8519
8701
  const reference = referenceIn(expr);
8520
- return reference === null ? null : (0, import_behavioral_ir3.referenceName)(reference);
8702
+ return reference === null ? null : (0, import_behavioral_ir4.referenceName)(reference);
8521
8703
  }
8522
8704
  function referenceIn(expr) {
8523
8705
  const fromParameter = parameterPath(expr);
@@ -8766,7 +8948,7 @@ function boundOnce(expr, ctx) {
8766
8948
  }
8767
8949
  function readOrHole(expr, ctx) {
8768
8950
  const name = read(expr, ctx);
8769
- return name ?? (0, import_behavioral_ir3.patternHole)(holeName(expr, ctx));
8951
+ return name ?? (0, import_behavioral_ir4.patternHole)(holeName(expr, ctx));
8770
8952
  }
8771
8953
  function holeName(expr, ctx) {
8772
8954
  const bound = boundOnce(expr, ctx);
@@ -9153,142 +9335,40 @@ function importSpecifierMatches(decl, expectedModule) {
9153
9335
  return import_ts_morph43.Node.isImportDeclaration(importDecl) && importDecl.getModuleSpecifierValue() === expectedModule;
9154
9336
  }
9155
9337
  function extractInvocationEffects(func, barriers = NO_BARRIERS) {
9156
- const results = [];
9338
+ const calls = [];
9157
9339
  func.forEachDescendant((node, traversal) => {
9158
- if (isDescentStop(node, func, barriers)) {
9340
+ if (isDescentStop(node, func, barriers) || import_ts_morph43.Node.isDecorator(node)) {
9159
9341
  traversal.skip();
9160
9342
  return;
9161
9343
  }
9162
- if (import_ts_morph43.Node.isExpressionStatement(node)) {
9163
- const { call, async } = unwrapCall(node.getExpression());
9164
- if (call !== null) {
9165
- const preconditions = collectPreconditions(node, func);
9166
- results.push({
9167
- effect: {
9168
- type: "invocation",
9169
- callee: call.getExpression().getText(),
9170
- args: extractArgs(call),
9171
- async,
9172
- ...preconditions.length > 0 ? { preconditions } : {}
9173
- },
9174
- line: startLineOf(node),
9175
- neverTerminal: false,
9176
- alwaysRuns: runsOnEveryPath(node, func)
9177
- });
9178
- }
9179
- return;
9180
- }
9181
- if (import_ts_morph43.Node.isSpreadElement(node)) {
9182
- const parent = node.getParent();
9183
- if (parent !== void 0 && (import_ts_morph43.Node.isArrayLiteralExpression(parent) || import_ts_morph43.Node.isObjectLiteralExpression(parent))) {
9184
- const { call, async } = unwrapCall(node.getExpression());
9185
- if (call !== null) {
9186
- const preconditions = collectPreconditions(node, func);
9187
- results.push({
9188
- effect: {
9189
- type: "invocation",
9190
- callee: call.getExpression().getText(),
9191
- args: extractArgs(call),
9192
- async,
9193
- ...preconditions.length > 0 ? { preconditions } : {}
9194
- },
9195
- line: enclosingStatementLine(node),
9196
- neverTerminal: true,
9197
- alwaysRuns: runsOnEveryPath(node, func)
9198
- });
9199
- }
9200
- }
9201
- return;
9202
- }
9203
- if (import_ts_morph43.Node.isVariableDeclaration(node)) {
9204
- const initializer = node.getInitializer();
9205
- if (initializer !== void 0) {
9206
- const { call, async } = unwrapCall(initializer);
9207
- if (call !== null) {
9208
- const preconditions = collectPreconditions(node, func);
9209
- results.push({
9210
- effect: {
9211
- type: "invocation",
9212
- callee: call.getExpression().getText(),
9213
- args: extractArgs(call),
9214
- async,
9215
- ...preconditions.length > 0 ? { preconditions } : {}
9216
- },
9217
- line: enclosingStatementLine(node),
9218
- neverTerminal: true,
9219
- alwaysRuns: runsOnEveryPath(node, func)
9220
- });
9221
- }
9222
- }
9223
- return;
9224
- }
9225
- if (import_ts_morph43.Node.isReturnStatement(node)) {
9226
- const returned = node.getExpression();
9227
- const { call, async } = returned === void 0 ? { call: null, async: false } : unwrapCall(returned);
9228
- if (call !== null) {
9229
- const preconditions = collectPreconditions(node, func);
9230
- results.push({
9231
- effect: {
9232
- type: "invocation",
9233
- callee: call.getExpression().getText(),
9234
- args: extractArgs(call),
9235
- async,
9236
- ...preconditions.length > 0 ? { preconditions } : {}
9237
- },
9238
- line: startLineOf(node),
9239
- neverTerminal: false,
9240
- alwaysRuns: runsOnEveryPath(node, func),
9241
- node: call
9242
- });
9243
- }
9244
- return;
9245
- }
9246
- if (import_ts_morph43.Node.isArrowFunction(node)) {
9247
- const body = node.getBody();
9248
- const { call, async } = import_ts_morph43.Node.isExpression(body) ? unwrapCall(body) : { call: null, async: false };
9249
- if (call !== null) {
9250
- const preconditions = collectPreconditions(node, func);
9251
- results.push({
9252
- effect: {
9253
- type: "invocation",
9254
- callee: call.getExpression().getText(),
9255
- args: extractArgs(call),
9256
- async: async || node.isAsync(),
9257
- ...preconditions.length > 0 ? { preconditions } : {}
9258
- },
9259
- line: startLineOf(node),
9260
- neverTerminal: false,
9261
- alwaysRuns: runsOnEveryPath(node, func),
9262
- node: call
9263
- });
9264
- }
9265
- return;
9266
- }
9267
9344
  if (import_ts_morph43.Node.isCallExpression(node)) {
9268
- const parent = node.getParent();
9269
- if (parent === void 0) {
9270
- return;
9271
- }
9272
- const isArrayElement = import_ts_morph43.Node.isArrayLiteralExpression(parent);
9273
- const isPropertyValue = import_ts_morph43.Node.isPropertyAssignment(parent) && parent.getInitializer() === node;
9274
- if (isArrayElement || isPropertyValue) {
9275
- const preconditions = collectPreconditions(node, func);
9276
- results.push({
9277
- effect: {
9278
- type: "invocation",
9279
- callee: node.getExpression().getText(),
9280
- args: extractArgs(node),
9281
- async: false,
9282
- ...preconditions.length > 0 ? { preconditions } : {}
9283
- },
9284
- line: enclosingStatementLine(node),
9285
- neverTerminal: true,
9286
- alwaysRuns: runsOnEveryPath(node, func)
9287
- });
9288
- }
9345
+ calls.push(node);
9289
9346
  }
9290
9347
  });
9291
- return results;
9348
+ calls.sort((a, b) => a.getEnd() - b.getEnd());
9349
+ return calls.map((call) => invocationAt(call, func));
9350
+ }
9351
+ function invocationAt(call, func) {
9352
+ const preconditions = collectPreconditions(call, func);
9353
+ return {
9354
+ effect: {
9355
+ type: "invocation",
9356
+ callee: call.getExpression().getText(),
9357
+ args: extractArgs(call),
9358
+ async: isAwaited(call),
9359
+ ...preconditions.length > 0 ? { preconditions } : {}
9360
+ },
9361
+ node: call,
9362
+ line: enclosingStatementLine(call),
9363
+ alwaysRuns: runsOnEveryPath(call, func)
9364
+ };
9365
+ }
9366
+ function isAwaited(call) {
9367
+ let current2 = call.getParent();
9368
+ while (current2 !== void 0 && import_ts_morph43.Node.isParenthesizedExpression(current2)) {
9369
+ current2 = current2.getParent();
9370
+ }
9371
+ return current2 !== void 0 && import_ts_morph43.Node.isAwaitExpression(current2);
9292
9372
  }
9293
9373
  function runInvocationRecognizers(func, recognizers, barriers = NO_BARRIERS, resolveWrittenValue, originatesFrom, anchorCallsOf) {
9294
9374
  if (recognizers.length === 0) {
@@ -9574,7 +9654,7 @@ function withPreconditions(effect, preconditions) {
9574
9654
  }
9575
9655
  return {
9576
9656
  ...effect,
9577
- preconditions: preconditions.map(import_extractor3.rawConditionToPredicate)
9657
+ preconditions: preconditions.map(import_extractor4.rawConditionToPredicate)
9578
9658
  };
9579
9659
  }
9580
9660
  function inlineModuleBinding(ident, depth) {
@@ -9631,22 +9711,6 @@ function enclosingStatementLine(node) {
9631
9711
  }
9632
9712
  return startLineOf(node);
9633
9713
  }
9634
- function unwrapCall(node) {
9635
- if (import_ts_morph43.Node.isAwaitExpression(node)) {
9636
- const inner = node.getExpression();
9637
- if (import_ts_morph43.Node.isCallExpression(inner)) {
9638
- return { call: inner, async: true };
9639
- }
9640
- return { call: null, async: false };
9641
- }
9642
- if (import_ts_morph43.Node.isParenthesizedExpression(node)) {
9643
- return unwrapCall(node.getExpression());
9644
- }
9645
- if (import_ts_morph43.Node.isCallExpression(node)) {
9646
- return { call: node, async: false };
9647
- }
9648
- return { call: null, async: false };
9649
- }
9650
9714
 
9651
9715
  // src/terminals/index.ts
9652
9716
  var import_ts_morph53 = require("ts-morph");
@@ -11676,10 +11740,26 @@ function makeFallthroughTerminal(func) {
11676
11740
  // src/assembly.ts
11677
11741
  var preconditionsOf = (effect) => effect.type === "invocation" ? effect.preconditions : void 0;
11678
11742
  function firesOn(site, branch) {
11679
- if (!(0, import_extractor4.guardsHoldOn)(site.preconditions, branch.conditions)) {
11743
+ if (!(0, import_extractor5.guardsHoldOn)(site.preconditions, branch.conditions)) {
11680
11744
  return false;
11681
11745
  }
11682
- return site.alwaysRuns || (0, import_extractor4.runsBefore)(site.line, branch.terminal.location.end);
11746
+ return site.alwaysRuns || (0, import_extractor5.runsBefore)(site.line, branch.terminal.location.end);
11747
+ }
11748
+ function writesTerminal(call, terminalNodes) {
11749
+ let current2 = call;
11750
+ for (; ; ) {
11751
+ if (terminalNodes.has(current2)) {
11752
+ return true;
11753
+ }
11754
+ const parent = current2.getParent();
11755
+ if (parent === void 0 || !isReceiverOf(current2, parent)) {
11756
+ return false;
11757
+ }
11758
+ current2 = parent;
11759
+ }
11760
+ }
11761
+ function isReceiverOf(node, parent) {
11762
+ return (import_ts_morph54.Node.isPropertyAccessExpression(parent) || import_ts_morph54.Node.isElementAccessExpression(parent) || import_ts_morph54.Node.isCallExpression(parent)) && parent.getExpression() === node;
11683
11763
  }
11684
11764
  var isDefaultConditionList = (conditions) => conditions.length === 0 || conditions.every(
11685
11765
  (c) => c.source === "earlyReturn" || c.source === "earlyThrow"
@@ -11819,11 +11899,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
11819
11899
  });
11820
11900
  if (invocations.length > 0) {
11821
11901
  const terminalNodes = new Set(terminals.map(({ node }) => node));
11822
- const terminalLines = new Set(
11823
- distinctBranches.map((b) => b.terminal.location.start)
11824
- );
11825
11902
  const sideEffects = invocations.filter(
11826
- (i) => i.node !== void 0 ? !terminalNodes.has(i.node) : i.neverTerminal || !terminalLines.has(i.line)
11903
+ (i) => !writesTerminal(i.node, terminalNodes)
11827
11904
  );
11828
11905
  for (const branch of distinctBranches) {
11829
11906
  branch.effects = sideEffects.filter(
@@ -11869,9 +11946,9 @@ function createSourceFileLookup(project) {
11869
11946
  if (sf === null) {
11870
11947
  return null;
11871
11948
  }
11872
- const path15 = sf.getFilePath();
11873
- const index = functionsByFile.get(path15) ?? indexFunctions(sf);
11874
- functionsByFile.set(path15, index);
11949
+ const path14 = sf.getFilePath();
11950
+ const index = functionsByFile.get(path14) ?? indexFunctions(sf);
11951
+ functionsByFile.set(path14, index);
11875
11952
  if (location.span !== void 0) {
11876
11953
  const bySpan = index.get(spanKey(location.span));
11877
11954
  if (bySpan !== void 0) {
@@ -11916,390 +11993,40 @@ function indexFunctions(sf) {
11916
11993
  return index;
11917
11994
  }
11918
11995
 
11919
- // src/cache.ts
11920
- var import_node_crypto2 = require("crypto");
11921
- var import_promises2 = __toESM(require("fs/promises"), 1);
11922
- var import_node_path7 = __toESM(require("path"), 1);
11923
- var SCHEMA_VERSION = "6";
11924
- var MAX_ENTRIES = 2;
11925
- var ENTRY_PREFIX = "key-";
11926
- var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
11927
- function createCacheLayer(cacheDir) {
11928
- if (cacheDir === null) {
11929
- return {
11930
- tryHit: async () => null,
11931
- lookup: async () => ({
11932
- kind: "miss",
11933
- diagnostic: { kind: "miss", missReason: "no-manifest" }
11934
- }),
11935
- plan: async () => null,
11936
- write: async () => {
11937
- }
11938
- };
11996
+ // src/contract.ts
11997
+ var import_ts_morph56 = require("ts-morph");
11998
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
11999
+ function extractDeclaredBody(node) {
12000
+ if (node === void 0) {
12001
+ return null;
11939
12002
  }
11940
- return {
11941
- async tryHit(input) {
11942
- const result = await this.lookup(input);
11943
- return result.kind === "hit" ? result.summaries : null;
11944
- },
11945
- async lookup(input) {
11946
- const entryDir = entryDirFor(cacheDir, input);
11947
- const manifest = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11948
- if (manifest === null) {
11949
- return missDiag(await describeAbsentEntry(cacheDir));
11950
- }
11951
- const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
11952
- if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
11953
- return missDiag("tsconfig-changed");
11954
- }
11955
- const currentFiles = await resolveFileStamps(input);
11956
- if (!fileStampsEqual(manifest.files, currentFiles)) {
11957
- return missDiag("files-changed");
11958
- }
11959
- await markUsed(entryDir);
11960
- return {
11961
- kind: "hit",
11962
- summaries: manifest.summaries,
11963
- diagnostic: { kind: "hit" }
11964
- };
11965
- },
11966
- async plan(input) {
11967
- const entryDir = entryDirFor(cacheDir, input);
11968
- const manifest = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11969
- if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
11970
- return null;
11971
- }
11972
- const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
11973
- if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
11974
- return null;
11975
- }
11976
- await markUsed(entryDir);
11977
- return buildPlan(manifest, await resolveFileStamps(input));
11978
- },
11979
- async write(input, summaries, attribution) {
11980
- const entryDir = entryDirFor(cacheDir, input);
11981
- const previous = await readManifest(import_node_path7.default.join(entryDir, "manifest.json"));
11982
- const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
11983
- const files = await hashStamps(await resolveFileStamps(input), previous);
11984
- const manifest = {
11985
- schemaVersion: SCHEMA_VERSION,
11986
- adapterPacksDigest: input.adapterPacksDigest,
11987
- tsconfigStamp,
11988
- files,
11989
- summaries,
11990
- ...attribution === void 0 ? {} : encodeAttribution(attribution)
11991
- };
11992
- await import_promises2.default.mkdir(entryDir, { recursive: true });
11993
- await import_promises2.default.writeFile(
11994
- import_node_path7.default.join(entryDir, "manifest.json"),
11995
- JSON.stringify(manifest)
11996
- );
11997
- await evictOldEntries(cacheDir, entryDir);
11998
- }
11999
- };
12000
- }
12001
- async function buildPlan(manifest, currentStamps) {
12002
- const stored = new Map(manifest.files.map((f) => [f.path, f]));
12003
- const current2 = new Map(currentStamps.map((f) => [f.path, f]));
12004
- const changed = /* @__PURE__ */ new Set();
12005
- const removed = /* @__PURE__ */ new Set();
12006
- for (const p of stored.keys()) {
12007
- if (!current2.has(p)) {
12008
- removed.add(p);
12009
- }
12010
- }
12011
- const toVerify = [];
12012
- for (const [p, stamp] of current2) {
12013
- const before = stored.get(p);
12014
- if (before === void 0) {
12015
- changed.add(p);
12016
- } else if (!fileStampEquals(before, stamp)) {
12017
- if (before.contentHash === void 0) {
12018
- changed.add(p);
12019
- } else {
12020
- toVerify.push(p);
12021
- }
12022
- }
12003
+ if (!import_ts_morph56.Node.isCallExpression(node)) {
12004
+ return null;
12023
12005
  }
12024
- await Promise.all(
12025
- toVerify.map(async (p) => {
12026
- const hash = await hashFile(p);
12027
- if (hash === null || hash !== stored.get(p)?.contentHash) {
12028
- changed.add(p);
12029
- }
12030
- })
12031
- );
12032
- const roots = /* @__PURE__ */ new Map();
12033
- const rootNames = manifest.roots ?? [];
12034
- const depPaths = manifest.depPaths ?? [];
12035
- let rootsDeclined = 0;
12036
- rootNames.forEach((rootPath, i) => {
12037
- const meta = manifest.rootMeta?.[i];
12038
- if (meta === void 0) {
12039
- return;
12040
- }
12041
- if (!meta.cacheable) {
12042
- rootsDeclined += 1;
12043
- }
12044
- roots.set(rootPath, {
12045
- path: rootPath,
12046
- cacheable: meta.cacheable,
12047
- deps: meta.deps.flatMap((d) => {
12048
- const p = depPaths[d];
12049
- return p === void 0 ? [] : [p];
12050
- }),
12051
- claims: meta.claims,
12052
- mountPrefixes: meta.mountPrefixes,
12053
- packs: meta.packs
12054
- });
12055
- });
12056
- const validRoots = /* @__PURE__ */ new Set();
12057
- for (const [rootPath, record2] of roots) {
12058
- if (!record2.cacheable || !current2.has(rootPath)) {
12059
- continue;
12060
- }
12061
- if (changed.has(rootPath)) {
12062
- continue;
12063
- }
12064
- const depMoved = record2.deps.some((d) => changed.has(d) || removed.has(d));
12065
- if (!depMoved) {
12066
- validRoots.add(rootPath);
12067
- }
12006
+ const typeArgs = node.getTypeArguments();
12007
+ if (typeArgs.length === 0) {
12008
+ return null;
12068
12009
  }
12069
- const owners = manifest.owners ?? [];
12070
- return {
12071
- changed,
12072
- removed,
12073
- roots,
12074
- validRoots,
12075
- rootsDeclined,
12076
- reuse(valid) {
12077
- const summaries = [];
12078
- const reusedOwners = [];
12079
- manifest.summaries.forEach((summary, i) => {
12080
- const ownerPaths = (owners[i] ?? []).flatMap((o) => {
12081
- const p = rootNames[o];
12082
- return p === void 0 ? [] : [p];
12083
- });
12084
- if (ownerPaths.some((p) => valid.has(p))) {
12085
- summaries.push(summary);
12086
- reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
12087
- }
12088
- });
12089
- return { summaries, owners: reusedOwners };
12090
- },
12091
- allSummaries() {
12092
- return manifest.summaries;
12093
- },
12094
- attribution() {
12095
- return {
12096
- roots: [...roots.values()],
12097
- owners: owners.map(
12098
- (ownerIds) => ownerIds.flatMap((o) => {
12099
- const p = rootNames[o];
12100
- return p === void 0 ? [] : [p];
12101
- })
12102
- )
12103
- };
12104
- }
12105
- };
12106
- }
12107
- function encodeAttribution(attribution) {
12108
- const roots = attribution.roots.map((r) => r.path);
12109
- const rootIndex = new Map(roots.map((p, i) => [p, i]));
12110
- const depIndex = /* @__PURE__ */ new Map();
12111
- const depPaths = [];
12112
- const depIdOf = (p) => {
12113
- const existing = depIndex.get(p);
12114
- if (existing !== void 0) {
12115
- return existing;
12116
- }
12117
- const id = depPaths.length;
12118
- depPaths.push(p);
12119
- depIndex.set(p, id);
12120
- return id;
12121
- };
12122
- const rootMeta = attribution.roots.map((r) => ({
12123
- cacheable: r.cacheable,
12124
- deps: r.deps.map(depIdOf),
12125
- claims: r.claims,
12126
- mountPrefixes: r.mountPrefixes,
12127
- packs: r.packs
12128
- }));
12129
- const owners = attribution.owners.map(
12130
- (ownerPaths) => ownerPaths.flatMap((p) => {
12131
- const i = rootIndex.get(p);
12132
- return i === void 0 ? [] : [i];
12133
- })
12134
- );
12135
- return { roots, rootMeta, depPaths, owners };
12136
- }
12137
- async function hashStamps(stamps, previous) {
12138
- const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
12139
- return Promise.all(
12140
- stamps.map(async (stamp) => {
12141
- const prior = before.get(stamp.path);
12142
- if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
12143
- return { ...stamp, contentHash: prior.contentHash };
12144
- }
12145
- const contentHash = await hashFile(stamp.path);
12146
- return contentHash === null ? stamp : { ...stamp, contentHash };
12147
- })
12148
- );
12010
+ return shapeFromNodeType(typeArgs[0]);
12149
12011
  }
12150
- async function hashFile(filePath) {
12151
- try {
12152
- const content = await import_promises2.default.readFile(filePath);
12153
- return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
12154
- } catch {
12012
+ function resolveHandlerPropNode(func) {
12013
+ const parent = func.getParent();
12014
+ if (parent === void 0) {
12155
12015
  return null;
12156
12016
  }
12157
- }
12158
- async function describeAbsentEntry(cacheDir) {
12159
- try {
12160
- const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
12161
- return entries.some((e) => e.isDirectory() && isEntryDir(e.name)) ? "key-changed" : "no-manifest";
12162
- } catch {
12163
- return "no-manifest";
12017
+ if (import_ts_morph56.Node.isPropertyAssignment(parent)) {
12018
+ return parent;
12164
12019
  }
12165
- }
12166
- function entryDirFor(cacheDir, input) {
12167
- const key = [
12168
- SCHEMA_VERSION,
12169
- input.adapterPacksDigest,
12170
- input.tsconfigPath ?? ""
12171
- ].join(" ");
12172
- const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
12173
- return import_node_path7.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
12174
- }
12175
- function isEntryDir(name) {
12176
- return ENTRY_DIR_NAME.test(name);
12177
- }
12178
- async function markUsed(entryDir) {
12179
- const now = /* @__PURE__ */ new Date();
12180
- try {
12181
- await import_promises2.default.utimes(entryDir, now, now);
12182
- } catch {
12020
+ if (import_ts_morph56.Node.isMethodDeclaration(func)) {
12021
+ return func;
12183
12022
  }
12023
+ return null;
12184
12024
  }
12185
- async function evictOldEntries(cacheDir, keepDir) {
12186
- try {
12187
- await import_promises2.default.rm(import_node_path7.default.join(cacheDir, "manifest.json"), { force: true });
12188
- const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
12189
- const dirs = await Promise.all(
12190
- entries.filter((e) => e.isDirectory() && isEntryDir(e.name)).map(async (e) => {
12191
- const dir = import_node_path7.default.join(cacheDir, e.name);
12192
- const stat = await import_promises2.default.stat(dir);
12193
- return { dir, mtimeMs: stat.mtimeMs };
12194
- })
12195
- );
12196
- const doomed = dirs.filter((d) => d.dir !== keepDir).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(MAX_ENTRIES - 1);
12197
- await Promise.all(
12198
- doomed.map((d) => import_promises2.default.rm(d.dir, { recursive: true, force: true }))
12199
- );
12200
- } catch {
12201
- }
12202
- }
12203
- function missDiag(reason) {
12204
- return {
12205
- kind: "miss",
12206
- diagnostic: { kind: "miss", missReason: reason }
12207
- };
12208
- }
12209
- async function readManifest(manifestPath) {
12210
- try {
12211
- const raw = await import_promises2.default.readFile(manifestPath, "utf-8");
12212
- return JSON.parse(raw);
12213
- } catch {
12214
- return null;
12215
- }
12216
- }
12217
- async function stampTsconfig(tsconfigPath) {
12218
- if (tsconfigPath === void 0) {
12219
- return null;
12220
- }
12221
- try {
12222
- const stat = await import_promises2.default.stat(tsconfigPath);
12223
- return { path: tsconfigPath, mtimeMs: stat.mtimeMs, size: stat.size };
12224
- } catch {
12225
- return null;
12226
- }
12227
- }
12228
- async function resolveFileStamps(input) {
12229
- if (input.files !== void 0) {
12230
- return stampPaths(input.files);
12231
- }
12232
- if (input.project !== void 0) {
12233
- return stampProjectFiles(input.project);
12234
- }
12235
- return [];
12236
- }
12237
- async function stampProjectFiles(project) {
12238
- const files = project.getSourceFiles().filter((sf) => !sf.isDeclarationFile()).map((sf) => sf.getFilePath());
12239
- return stampPaths(files);
12240
- }
12241
- async function stampPaths(paths) {
12242
- const files = paths;
12243
- const stamped = await Promise.all(
12244
- files.map(async (p) => {
12245
- try {
12246
- const stat = await import_promises2.default.stat(p);
12247
- return { path: p, mtimeMs: stat.mtimeMs, size: stat.size };
12248
- } catch {
12249
- return { path: p, mtimeMs: -1, size: -1 };
12250
- }
12251
- })
12252
- );
12253
- stamped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
12254
- return stamped;
12255
- }
12256
- function fileStampsEqual(a, b) {
12257
- return a.length === b.length && a.every((s, i) => fileStampEquals(s, b[i]));
12258
- }
12259
- function fileStampEquals(a, b) {
12260
- if (a === null && b === null) {
12261
- return true;
12262
- }
12263
- if (a === null || b === null) {
12264
- return false;
12265
- }
12266
- return a.path === b.path && a.mtimeMs === b.mtimeMs && a.size === b.size;
12267
- }
12268
-
12269
- // src/contract.ts
12270
- var import_ts_morph56 = require("ts-morph");
12271
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
12272
- function extractDeclaredBody(node) {
12273
- if (node === void 0) {
12274
- return null;
12275
- }
12276
- if (!import_ts_morph56.Node.isCallExpression(node)) {
12277
- return null;
12278
- }
12279
- const typeArgs = node.getTypeArguments();
12280
- if (typeArgs.length === 0) {
12281
- return null;
12282
- }
12283
- return shapeFromNodeType(typeArgs[0]);
12284
- }
12285
- function resolveHandlerPropNode(func) {
12286
- const parent = func.getParent();
12287
- if (parent === void 0) {
12288
- return null;
12289
- }
12290
- if (import_ts_morph56.Node.isPropertyAssignment(parent)) {
12291
- return parent;
12292
- }
12293
- if (import_ts_morph56.Node.isMethodDeclaration(func)) {
12294
- return func;
12295
- }
12296
- return null;
12297
- }
12298
- function findRouterCall(unit) {
12299
- const func = unit.func;
12300
- const propNode = func === null ? null : resolveHandlerPropNode(func);
12301
- if (propNode === null) {
12302
- return null;
12025
+ function findRouterCall(unit) {
12026
+ const func = unit.func;
12027
+ const propNode = func === null ? null : resolveHandlerPropNode(func);
12028
+ if (propNode === null) {
12029
+ return null;
12303
12030
  }
12304
12031
  const handlersObj = propNode.getParent();
12305
12032
  if (handlersObj === void 0 || !import_ts_morph56.Node.isObjectLiteralExpression(handlersObj)) {
@@ -12408,7 +12135,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12408
12135
  }
12409
12136
  const responses = [];
12410
12137
  let method;
12411
- let path15;
12138
+ let path14;
12412
12139
  for (const prop of endpointNode.getProperties()) {
12413
12140
  if (!import_ts_morph56.Node.isPropertyAssignment(prop)) {
12414
12141
  continue;
@@ -12423,7 +12150,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12423
12150
  if (propName === pattern.pathProperty) {
12424
12151
  const val = prop.getInitializer();
12425
12152
  if (val !== void 0 && import_ts_morph56.Node.isStringLiteral(val)) {
12426
- path15 = val.getLiteralValue();
12153
+ path14 = val.getLiteralValue();
12427
12154
  }
12428
12155
  }
12429
12156
  if (propName === pattern.responseExtraction.property) {
@@ -12447,10 +12174,10 @@ function extractEndpointContract(endpointNode, pattern, framework) {
12447
12174
  if (responses.length === 0) {
12448
12175
  return null;
12449
12176
  }
12450
- const boundaryBinding = method !== void 0 || path15 !== void 0 ? (0, import_behavioral_ir4.restBinding)({
12177
+ const boundaryBinding = method !== void 0 || path14 !== void 0 ? (0, import_behavioral_ir5.restBinding)({
12451
12178
  transport: "http",
12452
12179
  method: method ?? null,
12453
- path: path15 ?? null,
12180
+ path: path14 ?? null,
12454
12181
  recognition: framework
12455
12182
  }) : null;
12456
12183
  return {
@@ -12648,7 +12375,7 @@ function parameterName(segment, convention) {
12648
12375
  // src/discovery/graphqlClientConstruction.ts
12649
12376
  var import_picomatch2 = __toESM(require("picomatch"), 1);
12650
12377
  var import_ts_morph57 = require("ts-morph");
12651
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
12378
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
12652
12379
  function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12653
12380
  const scopes = compileOperationScopes(packs);
12654
12381
  const sole = soleGraphqlClientRef(
@@ -12658,10 +12385,10 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12658
12385
  const soleClient = sole !== null ? workspace !== null ? { ...sole, workspace } : sole : null;
12659
12386
  let registry = null;
12660
12387
  for (const summary of summaries) {
12661
- if (!(0, import_behavioral_ir5.isGraphqlOperationBinding)(summary.identity.boundaryBinding)) {
12388
+ if (!(0, import_behavioral_ir6.isGraphqlOperationBinding)(summary.identity.boundaryBinding)) {
12662
12389
  continue;
12663
12390
  }
12664
- const existing = (0, import_behavioral_ir5.readGraphqlMetadata)(summary) ?? {};
12391
+ const existing = (0, import_behavioral_ir6.readGraphqlMetadata)(summary) ?? {};
12665
12392
  const scoped = scopedWorkspaceFor(summary.location.file, scopes);
12666
12393
  const client = scoped !== null ? { uri: null, uriRef: null, workspace: scoped } : soleClient;
12667
12394
  const dangling = (existing.unresolvedFragments?.length ?? 0) > 0;
@@ -12671,7 +12398,7 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
12671
12398
  if (dangling && registry === null) {
12672
12399
  registry = fragmentRegistryStatus(sourceFiles, packs);
12673
12400
  }
12674
- summary.metadata = (0, import_behavioral_ir5.withGraphqlMetadata)(summary.metadata, {
12401
+ summary.metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(summary.metadata, {
12675
12402
  ...existing,
12676
12403
  ...client !== null ? { client } : {},
12677
12404
  ...dangling && registry !== null ? { fragmentRegistry: registry } : {}
@@ -13083,8 +12810,8 @@ function noteExpanded(expanded, readings, discovery) {
13083
12810
  const paths = new Set(match.registrations.map((one) => one.pathTemplate));
13084
12811
  const methods = expanded.get(reading.declarationId) ?? /* @__PURE__ */ new Set();
13085
12812
  for (const sink of reading.helper.sinks) {
13086
- const path15 = sink.arguments[0];
13087
- if (sink.method !== null && path15?.as === "text" && paths.has(path15.text)) {
12813
+ const path14 = sink.arguments[0];
12814
+ if (sink.method !== null && path14?.as === "text" && paths.has(path14.text)) {
13088
12815
  methods.add(sink.method);
13089
12816
  }
13090
12817
  }
@@ -13673,7 +13400,7 @@ function scalarOrRef(node) {
13673
13400
  }
13674
13401
 
13675
13402
  // src/moduleInit.ts
13676
- var import_extractor5 = require("@suss/extractor");
13403
+ var import_extractor6 = require("@suss/extractor");
13677
13404
  function moduleInitName(sourceFile) {
13678
13405
  return sourceFile.getBaseName();
13679
13406
  }
@@ -13681,44 +13408,15 @@ function moduleInitSummary(sourceFile, effects, options) {
13681
13408
  if (effects.length === 0) {
13682
13409
  return null;
13683
13410
  }
13684
- const end = sourceFile.getEnd();
13685
- const raw = {
13686
- identity: {
13411
+ return (0, import_extractor6.assembleSummary)(
13412
+ (0, import_extractor6.moduleInitStructure)({
13687
13413
  name: moduleInitName(sourceFile),
13688
- kind: "module-init",
13689
13414
  file: sourceFile.getFilePath(),
13690
- range: { start: 0, end },
13691
- exportName: null,
13692
- exportPath: null
13693
- },
13694
- boundaryBinding: null,
13695
- parameters: [],
13696
- branches: [
13697
- {
13698
- conditions: [],
13699
- terminal: {
13700
- kind: "void",
13701
- statusCode: null,
13702
- body: null,
13703
- exceptionType: null,
13704
- message: null,
13705
- component: null,
13706
- renderTree: null,
13707
- delegateTarget: null,
13708
- emitEvent: null,
13709
- location: { start: 0, end }
13710
- },
13711
- effects: [],
13712
- extraEffects: effects,
13713
- location: { start: 0, end },
13714
- isDefault: true
13715
- }
13716
- ],
13717
- unmatchedReturns: 0,
13718
- dependencyCalls: [],
13719
- declaredContract: null
13720
- };
13721
- return (0, import_extractor5.assembleSummary)(raw, options);
13415
+ range: { start: 0, end: sourceFile.getEnd() },
13416
+ effects
13417
+ }),
13418
+ options
13419
+ );
13722
13420
  }
13723
13421
 
13724
13422
  // src/referencedFiles.ts
@@ -13866,9 +13564,9 @@ function deriveBoundaryEffects(summaries, facts) {
13866
13564
 
13867
13565
  // src/resolve/reachableClosure.ts
13868
13566
  var import_ts_morph62 = require("ts-morph");
13869
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
13567
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
13870
13568
  var import_datalog4 = require("@suss/datalog");
13871
- var import_extractor6 = require("@suss/extractor");
13569
+ var import_extractor7 = require("@suss/extractor");
13872
13570
  var reachablePack = {
13873
13571
  name: "reachable",
13874
13572
  languages: ["typescript"],
@@ -13890,6 +13588,12 @@ var reachablePack = {
13890
13588
  function nodeKey(func) {
13891
13589
  return offsetKeyOf(func);
13892
13590
  }
13591
+ function targetOf(node) {
13592
+ return {
13593
+ file: node.getSourceFile().getFilePath(),
13594
+ span: { start: node.getStart(), end: node.getEnd() }
13595
+ };
13596
+ }
13893
13597
  function resolveCallee(call, calleeName, scan) {
13894
13598
  if (!import_ts_morph62.Node.isCallExpression(call)) {
13895
13599
  return null;
@@ -13914,19 +13618,42 @@ function resolveCallee(call, calleeName, scan) {
13914
13618
  if (sources.length > 1) {
13915
13619
  return {
13916
13620
  kind: "stopped",
13917
- stop: { callee: calleeName, reason: "multipleSources" }
13621
+ stop: { callee: calleeName, reason: "multipleSources" },
13622
+ declaration: declarationToReport(declarations, calleeName, scan)
13918
13623
  };
13919
13624
  }
13920
13625
  }
13626
+ const reason = classifyStop(declarations, scan.scanning);
13627
+ const parameterIndex = reason === "callerSupplied" ? parameterIndexOf(declarations, scan.scanning) : void 0;
13921
13628
  return {
13922
13629
  kind: "stopped",
13923
- stop: {
13924
- callee: calleeName,
13925
- reason: classifyStop(declarationsBehind(symbol), scan.scanning)
13926
- }
13630
+ stop: { callee: calleeName, reason },
13631
+ declaration: declarationToReport(declarations, calleeName, scan),
13632
+ ...parameterIndex === void 0 ? {} : { parameterIndex }
13927
13633
  };
13928
13634
  }
13929
- function resolveJsxReference(node) {
13635
+ function declarationToReport(declarations, calleeName, scan) {
13636
+ const first = declarations[0];
13637
+ if (first === void 0) {
13638
+ return null;
13639
+ }
13640
+ const sources = scan.sourceDeclarationsBehind?.(first) ?? [first];
13641
+ const only = sources.length === 1 ? sources[0] : void 0;
13642
+ if (only === void 0 || only === first) {
13643
+ return first;
13644
+ }
13645
+ return resolveDecl(only, calleeName)?.func ?? only;
13646
+ }
13647
+ function declaredAtOf(outcome) {
13648
+ if (outcome.kind === "followed") {
13649
+ return targetOf(outcome.candidate.func);
13650
+ }
13651
+ return outcome.declaration === null ? null : targetOf(outcome.declaration);
13652
+ }
13653
+ function passedPositionKey(target, position) {
13654
+ return `${offsetKeyFor(target.file, target.span)}#${position}`;
13655
+ }
13656
+ function resolveJsxReference(node, scan) {
13930
13657
  if (!import_ts_morph62.Node.isJsxOpeningElement(node) && !import_ts_morph62.Node.isJsxSelfClosingElement(node)) {
13931
13658
  return null;
13932
13659
  }
@@ -13946,7 +13673,8 @@ function resolveJsxReference(node) {
13946
13673
  tag: name,
13947
13674
  outcome: {
13948
13675
  kind: "stopped",
13949
- stop: { callee: name, reason: classifyStop(declarations) }
13676
+ stop: { callee: name, reason: classifyStop(declarations) },
13677
+ declaration: declarationToReport(declarations, name, scan)
13950
13678
  }
13951
13679
  };
13952
13680
  }
@@ -13954,11 +13682,15 @@ function collectReachable(func, scan) {
13954
13682
  const inFunc = { ...scan, scanning: func };
13955
13683
  const candidates = [];
13956
13684
  const stops = [];
13685
+ const placements = new import_behavioral_ir7.TargetPlacements();
13686
+ const parameterCalls = [];
13687
+ const passedPositions = /* @__PURE__ */ new Set();
13957
13688
  const seen = /* @__PURE__ */ new Set();
13689
+ const parameterCallsSeen = /* @__PURE__ */ new Set();
13958
13690
  const record2 = (outcome) => {
13959
13691
  if (outcome.kind === "stopped") {
13960
13692
  const stopKey = `${outcome.stop.reason}:${outcome.stop.callee}`;
13961
- if (!seen.has(stopKey) && worthRecording(outcome.stop.reason)) {
13693
+ if (!seen.has(stopKey) && (0, import_behavioral_ir3.worthRecording)(outcome.stop.reason)) {
13962
13694
  seen.add(stopKey);
13963
13695
  stops.push(outcome.stop);
13964
13696
  }
@@ -13971,8 +13703,28 @@ function collectReachable(func, scan) {
13971
13703
  seen.add(key);
13972
13704
  candidates.push(outcome.candidate);
13973
13705
  };
13706
+ const recordPassedArgs = (call, calleeText, calleeOutcome) => {
13707
+ if (!import_ts_morph62.Node.isCallExpression(call)) {
13708
+ return;
13709
+ }
13710
+ const calleeTarget = declaredAtOf(calleeOutcome);
13711
+ call.getArguments().forEach((arg, position) => {
13712
+ if (!import_ts_morph62.Node.isIdentifier(arg)) {
13713
+ return;
13714
+ }
13715
+ const resolved = functionTargetOf(arg);
13716
+ if (resolved === null) {
13717
+ return;
13718
+ }
13719
+ record2({ kind: "followed", candidate: resolved });
13720
+ placements.placeArg(calleeText, position, targetOf(resolved.func));
13721
+ if (calleeTarget !== null) {
13722
+ passedPositions.add(passedPositionKey(calleeTarget, position));
13723
+ }
13724
+ });
13725
+ };
13974
13726
  func.forEachDescendant((node) => {
13975
- const jsx = resolveJsxReference(node);
13727
+ const jsx = resolveJsxReference(node, inFunc);
13976
13728
  if (jsx !== null) {
13977
13729
  record2(jsx.outcome);
13978
13730
  return;
@@ -13986,8 +13738,24 @@ function collectReachable(func, scan) {
13986
13738
  return;
13987
13739
  }
13988
13740
  record2(outcome);
13741
+ placements.place(calleeText, declaredAtOf(outcome));
13742
+ recordPassedArgs(node, calleeText, outcome);
13743
+ if (outcome.kind === "stopped" && outcome.stop.reason === "callerSupplied" && outcome.parameterIndex !== void 0 && !parameterCallsSeen.has(calleeText)) {
13744
+ parameterCallsSeen.add(calleeText);
13745
+ parameterCalls.push({
13746
+ callee: calleeText,
13747
+ parameterIndex: outcome.parameterIndex
13748
+ });
13749
+ }
13989
13750
  });
13990
- return { candidates, stops };
13751
+ return {
13752
+ candidates,
13753
+ stops,
13754
+ targets: placements.targets,
13755
+ argTargets: placements.argTargets,
13756
+ parameterCalls,
13757
+ passedPositions
13758
+ };
13991
13759
  }
13992
13760
  function normalizeCallee(text) {
13993
13761
  return text.replace(/\s+/g, " ").trim();
@@ -14006,11 +13774,11 @@ function extractReachableSummary(candidate, options, recognizers) {
14006
13774
  void 0,
14007
13775
  recognizers.resolveWrittenValue
14008
13776
  );
14009
- raw.boundaryBinding = (0, import_behavioral_ir6.functionCallBinding)({
13777
+ raw.boundaryBinding = (0, import_behavioral_ir7.functionCallBinding)({
14010
13778
  transport: "in-process",
14011
13779
  recognition: "reachable"
14012
13780
  });
14013
- return (0, import_extractor6.assembleSummary)(raw, options);
13781
+ return (0, import_extractor7.assembleSummary)(raw, options);
14014
13782
  }
14015
13783
  var REACHABLE_RULES = [
14016
13784
  (0, import_datalog4.rule)("reachable", [(0, import_datalog4.variable)("f")], [(0, import_datalog4.lit)("entry", (0, import_datalog4.variable)("f"))]),
@@ -14062,6 +13830,10 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14062
13830
  const seedKeys = /* @__PURE__ */ new Set();
14063
13831
  const scanned = /* @__PURE__ */ new Set();
14064
13832
  const stopsByKey = /* @__PURE__ */ new Map();
13833
+ const targetsByKey = /* @__PURE__ */ new Map();
13834
+ const argTargetsByKey = /* @__PURE__ */ new Map();
13835
+ const parameterCallsByKey = /* @__PURE__ */ new Map();
13836
+ const passedPositions = /* @__PURE__ */ new Set();
14065
13837
  const summariesByKey = /* @__PURE__ */ new Map();
14066
13838
  const reachedFrom = /* @__PURE__ */ new Map();
14067
13839
  for (const seed of seeds) {
@@ -14106,9 +13878,17 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14106
13878
  const cameFrom = reachedFrom.get(key);
14107
13879
  const scan = {
14108
13880
  ...recognizers.resolveCallableSources === void 0 ? {} : { resolveCallableSources: recognizers.resolveCallableSources },
13881
+ ...recognizers.sourceDeclarationsBehind === void 0 ? {} : { sourceDeclarationsBehind: recognizers.sourceDeclarationsBehind },
14109
13882
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
14110
13883
  };
14111
- const { candidates, stops } = scanWithRecording(
13884
+ const {
13885
+ candidates,
13886
+ stops,
13887
+ targets,
13888
+ argTargets,
13889
+ parameterCalls,
13890
+ passedPositions: scanPassedPositions
13891
+ } = scanWithRecording(
14112
13892
  key,
14113
13893
  facts,
14114
13894
  () => collectReachable(source.func, scan)
@@ -14116,6 +13896,14 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14116
13896
  if (stops.length > 0) {
14117
13897
  stopsByKey.set(key, stops);
14118
13898
  }
13899
+ targetsByKey.set(key, targets);
13900
+ argTargetsByKey.set(key, argTargets);
13901
+ if (parameterCalls.length > 0) {
13902
+ parameterCallsByKey.set(key, parameterCalls);
13903
+ }
13904
+ for (const position of scanPassedPositions) {
13905
+ passedPositions.add(position);
13906
+ }
14119
13907
  for (const candidate of candidates) {
14120
13908
  const calleeKey = nodeKey(candidate.func);
14121
13909
  if (!functionByKey.has(calleeKey)) {
@@ -14155,6 +13943,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
14155
13943
  reached.push(summary);
14156
13944
  }
14157
13945
  recordStops(stopsByKey, summariesByKey, options);
13946
+ recordTargets(targetsByKey, summariesByKey);
13947
+ recordArgTargets(argTargetsByKey, summariesByKey);
13948
+ recordCalleeParameters(parameterCallsByKey, summariesByKey);
13949
+ recordParameterGaps(
13950
+ parameterCallsByKey,
13951
+ summariesByKey,
13952
+ passedPositions,
13953
+ options
13954
+ );
14158
13955
  return [...seeds, ...reached];
14159
13956
  }
14160
13957
  function scanWithRecording(key, facts, fn) {
@@ -14182,7 +13979,91 @@ function recordStops(stopsByKey, summariesByKey, options) {
14182
13979
  }
14183
13980
  for (const [key, stops] of stopsByKey) {
14184
13981
  for (const summary of summariesByKey.get(key) ?? []) {
14185
- summary.gaps.push(...stops.map(unfollowedCallGap));
13982
+ summary.gaps.push(...stops.map(import_behavioral_ir3.unfollowedCallGap));
13983
+ }
13984
+ }
13985
+ }
13986
+ function recordTargets(targetsByKey, summariesByKey) {
13987
+ for (const [key, targets] of targetsByKey) {
13988
+ for (const summary of summariesByKey.get(key) ?? []) {
13989
+ for (const transition of summary.transitions) {
13990
+ for (const effect of transition.effects) {
13991
+ if (effect.type !== "invocation") {
13992
+ continue;
13993
+ }
13994
+ const target = targets.get(normalizeCallee(effect.callee));
13995
+ if (target !== void 0) {
13996
+ effect.declaredAt = target;
13997
+ }
13998
+ }
13999
+ }
14000
+ }
14001
+ }
14002
+ }
14003
+ function recordArgTargets(argTargetsByKey, summariesByKey) {
14004
+ for (const [key, argTargets] of argTargetsByKey) {
14005
+ for (const summary of summariesByKey.get(key) ?? []) {
14006
+ for (const transition of summary.transitions) {
14007
+ for (const effect of transition.effects) {
14008
+ if (effect.type !== "invocation") {
14009
+ continue;
14010
+ }
14011
+ const byPosition = argTargets.get(normalizeCallee(effect.callee));
14012
+ if (byPosition === void 0) {
14013
+ continue;
14014
+ }
14015
+ const argsDeclaredAt = {};
14016
+ for (const [position, target] of byPosition) {
14017
+ argsDeclaredAt[position] = target;
14018
+ }
14019
+ if (Object.keys(argsDeclaredAt).length > 0) {
14020
+ effect.argsDeclaredAt = argsDeclaredAt;
14021
+ }
14022
+ }
14023
+ }
14024
+ }
14025
+ }
14026
+ }
14027
+ function recordCalleeParameters(parameterCallsByKey, summariesByKey) {
14028
+ for (const [key, parameterCalls] of parameterCallsByKey) {
14029
+ const byCallee = new Map(
14030
+ parameterCalls.map(({ callee, parameterIndex }) => [
14031
+ callee,
14032
+ parameterIndex
14033
+ ])
14034
+ );
14035
+ for (const summary of summariesByKey.get(key) ?? []) {
14036
+ for (const transition of summary.transitions) {
14037
+ for (const effect of transition.effects) {
14038
+ if (effect.type !== "invocation") {
14039
+ continue;
14040
+ }
14041
+ const parameterIndex = byCallee.get(normalizeCallee(effect.callee));
14042
+ if (parameterIndex !== void 0) {
14043
+ effect.calleeParameter = parameterIndex;
14044
+ }
14045
+ }
14046
+ }
14047
+ }
14048
+ }
14049
+ }
14050
+ function recordParameterGaps(parameterCallsByKey, summariesByKey, passedPositions, options) {
14051
+ if (options?.gapHandling === "silent") {
14052
+ return;
14053
+ }
14054
+ for (const [key, parameterCalls] of parameterCallsByKey) {
14055
+ const unbound = parameterCalls.filter(
14056
+ ({ parameterIndex }) => !passedPositions.has(`${key}#${parameterIndex}`)
14057
+ );
14058
+ if (unbound.length === 0) {
14059
+ continue;
14060
+ }
14061
+ for (const summary of summariesByKey.get(key) ?? []) {
14062
+ summary.gaps.push(
14063
+ ...unbound.map(
14064
+ ({ callee }) => (0, import_behavioral_ir3.unfollowedCallGap)({ callee, reason: "unboundParameter" })
14065
+ )
14066
+ );
14186
14067
  }
14187
14068
  }
14188
14069
  }
@@ -14917,9 +14798,8 @@ function readArrayLiteralText(node) {
14917
14798
  }
14918
14799
 
14919
14800
  // src/summaryIdentity.ts
14920
- var import_node_fs4 = __toESM(require("fs"), 1);
14921
14801
  var import_node_path8 = __toESM(require("path"), 1);
14922
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
14802
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
14923
14803
  function workspaceRootFor(anchor) {
14924
14804
  return namedPackageDirAbove(anchor) ?? import_node_path8.default.resolve(anchor);
14925
14805
  }
@@ -14934,32 +14814,6 @@ function workspaceNameFor(root) {
14934
14814
  const folder = import_node_path8.default.basename(import_node_path8.default.resolve(root));
14935
14815
  return folder.length > 0 ? folder : null;
14936
14816
  }
14937
- function namedPackageDirAbove(start) {
14938
- let at = import_node_path8.default.resolve(start);
14939
- for (let up = 0; up < 12; up += 1) {
14940
- if (packageNameAt(at) !== null) {
14941
- return at;
14942
- }
14943
- const parent = import_node_path8.default.dirname(at);
14944
- if (parent === at) {
14945
- break;
14946
- }
14947
- at = parent;
14948
- }
14949
- return null;
14950
- }
14951
- function packageNameAt(dir) {
14952
- try {
14953
- const manifest = JSON.parse(
14954
- import_node_fs4.default.readFileSync(import_node_path8.default.join(dir, "package.json"), "utf8")
14955
- );
14956
- if (typeof manifest.name === "string" && manifest.name.length > 0) {
14957
- return manifest.name;
14958
- }
14959
- } catch {
14960
- }
14961
- return null;
14962
- }
14963
14817
  function nameSummaries(summaries, args) {
14964
14818
  for (const summary of summaries) {
14965
14819
  const file = relativeFile(summary.location.file, args.projectRoot);
@@ -14968,8 +14822,8 @@ function nameSummaries(summaries, args) {
14968
14822
  }
14969
14823
  summary.identity.id = idFor(args.workspace, file, summary);
14970
14824
  }
14971
- (0, import_behavioral_ir7.disambiguateSummaryIds)(summaries);
14972
- nameWhatEachCallReaches(summaries);
14825
+ (0, import_behavioral_ir8.disambiguateSummaryIds)(summaries);
14826
+ (0, import_behavioral_ir8.linkCallsToSummaries)(summaries);
14973
14827
  }
14974
14828
  function relativeFile(file, projectRoot) {
14975
14829
  if (projectRoot === void 0 || !import_node_path8.default.isAbsolute(file)) {
@@ -14979,193 +14833,39 @@ function relativeFile(file, projectRoot) {
14979
14833
  return relative.startsWith("..") ? file : relative;
14980
14834
  }
14981
14835
  function idFor(workspace, file, summary) {
14982
- return (0, import_behavioral_ir7.summaryIdFromParts)({
14836
+ return (0, import_behavioral_ir8.summaryIdFromParts)({
14983
14837
  workspace: workspace ?? void 0,
14984
14838
  file,
14985
14839
  name: summary.identity.name,
14986
14840
  exportPath: summary.identity.exportPath
14987
14841
  });
14988
14842
  }
14989
- function nameWhatEachCallReaches(summaries) {
14990
- const byName = /* @__PURE__ */ new Map();
14991
- const byFileAndName = /* @__PURE__ */ new Map();
14992
- const remember = (index, key, summary) => {
14993
- const found = index.get(key);
14994
- if (found === void 0) {
14995
- index.set(key, [summary]);
14996
- return;
14997
- }
14998
- found.push(summary);
14999
- };
15000
- for (const summary of summaries) {
15001
- if (summary.identity.nameKind === "label") {
15002
- continue;
15003
- }
15004
- remember(byName, summary.identity.name, summary);
15005
- remember(
15006
- byFileAndName,
15007
- `${summary.location.file}::${summary.identity.name}`,
15008
- summary
15009
- );
15010
- }
15011
- const onlyAnswer = (index, key) => {
15012
- const found = index.get(key) ?? [];
15013
- return found.length === 1 ? found[0] : null;
15014
- };
15015
- for (const summary of summaries) {
15016
- for (const transition of summary.transitions) {
15017
- for (const effect of transition.effects) {
15018
- if (effect.type !== "invocation") {
15019
- continue;
15020
- }
15021
- const called = effect.callee.split(".").pop() ?? effect.callee;
15022
- const reached = onlyAnswer(byFileAndName, `${summary.location.file}::${called}`) ?? onlyAnswer(byName, called);
15023
- if (reached?.identity.id !== void 0) {
15024
- effect.summary = reached.identity.id;
15025
- }
15026
- }
15027
- }
15028
- }
15029
- }
15030
14843
 
15031
14844
  // src/timing.ts
15032
- function createTimer() {
15033
- const phases = /* @__PURE__ */ new Map();
15034
- const start = performance.now();
15035
- function record2(label, deltaMs) {
15036
- const existing = phases.get(label);
15037
- if (existing === void 0) {
15038
- phases.set(label, { durationMs: deltaMs, calls: 1 });
15039
- } else {
15040
- existing.durationMs += deltaMs;
15041
- existing.calls += 1;
15042
- }
15043
- }
15044
- return {
15045
- time(label, fn) {
15046
- const t0 = performance.now();
15047
- try {
15048
- return fn();
15049
- } finally {
15050
- record2(label, performance.now() - t0);
15051
- }
15052
- },
15053
- async timeAsync(label, fn) {
15054
- const t0 = performance.now();
15055
- try {
15056
- return await fn();
15057
- } finally {
15058
- record2(label, performance.now() - t0);
15059
- }
15060
- },
15061
- report() {
15062
- const totalMs = performance.now() - start;
15063
- const ordered = [...phases.entries()].map(([label, s]) => ({
15064
- label,
15065
- durationMs: s.durationMs,
15066
- calls: s.calls
15067
- })).sort((a, b) => b.durationMs - a.durationMs);
15068
- return { totalMs, phases: ordered };
15069
- }
15070
- };
15071
- }
14845
+ var import_extractor8 = require("@suss/extractor");
15072
14846
 
15073
14847
  // src/version.ts
15074
- var import_node_crypto3 = require("crypto");
15075
- var import_node_fs5 = __toESM(require("fs"), 1);
15076
- var import_node_module = require("module");
15077
- var import_node_path9 = __toESM(require("path"), 1);
15078
- var import_node_url = require("url");
14848
+ var import_extractor9 = require("@suss/extractor");
14849
+ var import_extractor10 = require("@suss/extractor");
15079
14850
  var import_meta = {};
15080
14851
  var ADAPTER_VERSION = "0.2.1";
15081
- var ANALYSIS_PACKAGES = [
15082
- "@suss/extractor",
15083
- "@suss/resolution",
15084
- "@suss/datalog",
15085
- "@suss/behavioral-ir"
15086
- ];
15087
- var SOURCE_STAMP = { kind: "source" };
15088
- var cachedCodeStamp = null;
15089
- function adapterCodeStamp() {
15090
- if (cachedCodeStamp !== null) {
15091
- return cachedCodeStamp;
15092
- }
15093
- cachedCodeStamp = readAdapterCodeStamp();
15094
- return cachedCodeStamp;
15095
- }
15096
- function readAdapterCodeStamp() {
15097
- try {
15098
- const selfPath = (0, import_node_url.fileURLToPath)(import_meta.url);
15099
- const hash = computeDistHashFrom(import_node_path9.default.dirname(selfPath));
15100
- return hash.length > 0 ? { kind: "bundle", hash } : SOURCE_STAMP;
15101
- } catch {
15102
- return SOURCE_STAMP;
15103
- }
15104
- }
15105
- function computeDistHashFrom(dir) {
15106
- const candidates = [import_node_path9.default.join(dir, "index.js"), import_node_path9.default.join(dir, "index.cjs")];
15107
- for (const candidate of candidates) {
15108
- if (!import_node_fs5.default.existsSync(candidate)) {
15109
- continue;
15110
- }
15111
- return computeContentHash([candidate, ...analysisBundles()]);
15112
- }
15113
- return "";
15114
- }
15115
- function computeContentHash(paths) {
15116
- if (paths.length === 0) {
15117
- return "";
15118
- }
15119
- const hash = (0, import_node_crypto3.createHash)("sha256");
15120
- for (const file of paths) {
15121
- try {
15122
- hash.update(import_node_fs5.default.readFileSync(file));
15123
- } catch {
15124
- return "";
15125
- }
15126
- }
15127
- return hash.digest("hex").slice(0, 16);
15128
- }
15129
- function projectFileStamp(paths) {
15130
- if (paths.length === 0) {
15131
- return "none";
15132
- }
15133
- const hash = (0, import_node_crypto3.createHash)("sha256");
15134
- for (const file of [...paths].sort()) {
15135
- hash.update(file);
15136
- hash.update("\0");
15137
- try {
15138
- hash.update(import_node_fs5.default.readFileSync(file));
15139
- } catch {
15140
- hash.update("absent");
15141
- }
15142
- }
15143
- return hash.digest("hex").slice(0, 16);
15144
- }
15145
- function analysisBundles() {
15146
- const require2 = (0, import_node_module.createRequire)(import_meta.url);
15147
- const found = [];
15148
- for (const name of ANALYSIS_PACKAGES) {
15149
- try {
15150
- found.push(require2.resolve(name));
15151
- } catch {
15152
- }
15153
- }
15154
- return found;
15155
- }
14852
+ var stamp = (0, import_extractor9.createAdapterStamp)({
14853
+ moduleUrl: import_meta.url,
14854
+ version: ADAPTER_VERSION
14855
+ });
15156
14856
  function computeAdapterPacksDigest(packVersions) {
15157
- const sortedPacks = [...packVersions].map((p) => `${p.name}@${p.version ?? "unset"}`).sort();
15158
- const stamp = adapterCodeStamp();
15159
- const adapterStamp = stamp.kind === "bundle" ? `adapter@${ADAPTER_VERSION}+${stamp.hash}` : `adapter@${ADAPTER_VERSION}+source`;
15160
- return `${adapterStamp}|${sortedPacks.join(",")}`;
14857
+ return stamp.packsDigest(packVersions);
14858
+ }
14859
+ function declineWhenRunFromSource(cacheDir) {
14860
+ return stamp.declineWhenRunFromSource(cacheDir);
15161
14861
  }
15162
14862
 
15163
14863
  // src/workspacePatterns.ts
15164
- var import_node_fs6 = __toESM(require("fs"), 1);
15165
- var import_node_path10 = __toESM(require("path"), 1);
14864
+ var import_node_fs3 = __toESM(require("fs"), 1);
14865
+ var import_node_path9 = __toESM(require("path"), 1);
15166
14866
  function readJson(filePath) {
15167
14867
  try {
15168
- return JSON.parse(import_node_fs6.default.readFileSync(filePath, "utf8"));
14868
+ return JSON.parse(import_node_fs3.default.readFileSync(filePath, "utf8"));
15169
14869
  } catch {
15170
14870
  return void 0;
15171
14871
  }
@@ -15192,7 +14892,7 @@ function pnpmWorkspaceGlobs(yamlText) {
15192
14892
  return globs;
15193
14893
  }
15194
14894
  function workspaceGlobsAt(dir) {
15195
- const pkg = readJson(import_node_path10.default.join(dir, "package.json"));
14895
+ const pkg = readJson(import_node_path9.default.join(dir, "package.json"));
15196
14896
  if (typeof pkg === "object" && pkg !== null && "workspaces" in pkg) {
15197
14897
  const ws = pkg.workspaces;
15198
14898
  if (Array.isArray(ws)) {
@@ -15204,10 +14904,10 @@ function workspaceGlobsAt(dir) {
15204
14904
  );
15205
14905
  }
15206
14906
  }
15207
- const pnpmPath = import_node_path10.default.join(dir, "pnpm-workspace.yaml");
15208
- if (import_node_fs6.default.existsSync(pnpmPath)) {
14907
+ const pnpmPath = import_node_path9.default.join(dir, "pnpm-workspace.yaml");
14908
+ if (import_node_fs3.default.existsSync(pnpmPath)) {
15209
14909
  try {
15210
- return pnpmWorkspaceGlobs(import_node_fs6.default.readFileSync(pnpmPath, "utf8"));
14910
+ return pnpmWorkspaceGlobs(import_node_fs3.default.readFileSync(pnpmPath, "utf8"));
15211
14911
  } catch {
15212
14912
  return null;
15213
14913
  }
@@ -15216,14 +14916,14 @@ function workspaceGlobsAt(dir) {
15216
14916
  }
15217
14917
  function isDirectory(candidate) {
15218
14918
  try {
15219
- return import_node_fs6.default.statSync(candidate).isDirectory();
14919
+ return import_node_fs3.default.statSync(candidate).isDirectory();
15220
14920
  } catch {
15221
14921
  return false;
15222
14922
  }
15223
14923
  }
15224
14924
  function subdirectoriesOf(dir) {
15225
14925
  try {
15226
- return import_node_fs6.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && name !== "node_modules");
14926
+ return import_node_fs3.default.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && name !== "node_modules");
15227
14927
  } catch {
15228
14928
  return [];
15229
14929
  }
@@ -15241,13 +14941,13 @@ function directoriesMatchingGlob(root, glob) {
15241
14941
  for (const dir of current2) {
15242
14942
  for (const entry of subdirectoriesOf(dir)) {
15243
14943
  if (pattern.test(entry)) {
15244
- next.push(import_node_path10.default.join(dir, entry));
14944
+ next.push(import_node_path9.default.join(dir, entry));
15245
14945
  }
15246
14946
  }
15247
14947
  }
15248
14948
  } else {
15249
14949
  for (const dir of current2) {
15250
- next.push(import_node_path10.default.join(dir, segment));
14950
+ next.push(import_node_path9.default.join(dir, segment));
15251
14951
  }
15252
14952
  }
15253
14953
  current2 = next.filter(isDirectory);
@@ -15255,7 +14955,7 @@ function directoriesMatchingGlob(root, glob) {
15255
14955
  return current2;
15256
14956
  }
15257
14957
  function packageAt(dir) {
15258
- const packageJsonPath = import_node_path10.default.join(dir, "package.json");
14958
+ const packageJsonPath = import_node_path9.default.join(dir, "package.json");
15259
14959
  const pkg = readJson(packageJsonPath);
15260
14960
  if (typeof pkg === "object" && pkg !== null && typeof pkg.name === "string") {
15261
14961
  return { name: pkg.name, packageJsonPath };
@@ -15263,7 +14963,7 @@ function packageAt(dir) {
15263
14963
  return null;
15264
14964
  }
15265
14965
  function workspacePackagesFrom(anchor) {
15266
- let dir = import_node_path10.default.resolve(anchor);
14966
+ let dir = import_node_path9.default.resolve(anchor);
15267
14967
  for (; ; ) {
15268
14968
  const globs = workspaceGlobsAt(dir);
15269
14969
  if (globs !== null) {
@@ -15283,7 +14983,7 @@ function workspacePackagesFrom(anchor) {
15283
14983
  (a, b) => a.packageJsonPath.localeCompare(b.packageJsonPath)
15284
14984
  );
15285
14985
  }
15286
- const parent = import_node_path10.default.dirname(dir);
14986
+ const parent = import_node_path9.default.dirname(dir);
15287
14987
  if (parent === dir) {
15288
14988
  return [];
15289
14989
  }
@@ -15317,7 +15017,7 @@ function workspaceExpansionStamp(frameworks) {
15317
15017
  const paths = frameworks.flatMap((pack) => pack.discovery).flatMap(
15318
15018
  (pattern) => pattern.match.type === "packageExports" && pattern.match.packageJsonPath !== void 0 ? [pattern.match.packageJsonPath] : []
15319
15019
  );
15320
- return projectFileStamp(paths);
15020
+ return (0, import_extractor10.projectFileStamp)(paths);
15321
15021
  }
15322
15022
  function expandWorkspacePatterns(frameworks, anchor) {
15323
15023
  const marked = frameworks.some(
@@ -15825,11 +15525,11 @@ function extractConsumerBinding(unit, pattern, pack, resolution) {
15825
15525
  return null;
15826
15526
  }
15827
15527
  const method = extractBindingMethod(binding, callSite, pack, resolution);
15828
- const path15 = extractBindingPath(binding, callSite, pack, resolution);
15829
- return (0, import_behavioral_ir8.restBinding)({
15528
+ const path14 = extractBindingPath(binding, callSite, pack, resolution);
15529
+ return (0, import_behavioral_ir9.restBinding)({
15830
15530
  transport: pack.protocol,
15831
15531
  method: method ?? null,
15832
- path: path15 ?? null,
15532
+ path: path14 ?? null,
15833
15533
  recognition: pack.name
15834
15534
  });
15835
15535
  }
@@ -15845,7 +15545,7 @@ function fileRouteBinding(filePath, unit, binding, pack) {
15845
15545
  if (method === void 0) {
15846
15546
  return null;
15847
15547
  }
15848
- return (0, import_behavioral_ir8.restBinding)({
15548
+ return (0, import_behavioral_ir9.restBinding)({
15849
15549
  transport: pack.protocol,
15850
15550
  recognition: pack.name,
15851
15551
  method,
@@ -15975,7 +15675,7 @@ function computeSubUnitBarriers(unit, packs, ctx, tallies, file) {
15975
15675
  }
15976
15676
  } catch (err) {
15977
15677
  process.stderr.write(
15978
- recordPackFailure(tallies?.get(pack.name), {
15678
+ (0, import_extractor3.recordPackFailure)(tallies?.get(pack.name), {
15979
15679
  pack: pack.name,
15980
15680
  hook: "subUnits",
15981
15681
  file,
@@ -16037,7 +15737,7 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16037
15737
  }
16038
15738
  } catch (err) {
16039
15739
  process.stderr.write(
16040
- recordPackFailure(tally, {
15740
+ (0, import_extractor3.recordPackFailure)(tally, {
16041
15741
  pack: pack.name,
16042
15742
  hook: "discoverUnits",
16043
15743
  file: sourceFile.getFilePath(),
@@ -16098,7 +15798,7 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16098
15798
  stampWrappers(raw, unit, wrappers);
16099
15799
  const matchedPattern = unit.pattern ?? pack.discovery.find((d) => d.kind === unit.kind);
16100
15800
  if (unit.resolverInfo !== void 0) {
16101
- raw.boundaryBinding = (0, import_behavioral_ir8.graphqlResolverBinding)({
15801
+ raw.boundaryBinding = (0, import_behavioral_ir9.graphqlResolverBinding)({
16102
15802
  transport: pack.protocol,
16103
15803
  recognition: pack.name,
16104
15804
  typeName: unit.resolverInfo.typeName,
@@ -16125,26 +15825,26 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16125
15825
  }
16126
15826
  }
16127
15827
  } else if (unit.routeInfo !== void 0) {
16128
- raw.boundaryBinding = (0, import_behavioral_ir8.restBinding)({
15828
+ raw.boundaryBinding = (0, import_behavioral_ir9.restBinding)({
16129
15829
  transport: pack.protocol,
16130
15830
  recognition: pack.name,
16131
15831
  method: unit.routeInfo.method,
16132
15832
  path: unit.routeInfo.path
16133
15833
  });
16134
15834
  } else if (unit.channelInfo !== void 0) {
16135
- raw.boundaryBinding = (0, import_behavioral_ir8.messageBusBinding)({
15835
+ raw.boundaryBinding = (0, import_behavioral_ir9.messageBusBinding)({
16136
15836
  recognition: pack.name,
16137
15837
  messageBus: unit.channelInfo.messageBus,
16138
15838
  channel: unit.channelInfo.channel
16139
15839
  });
16140
15840
  } else if (unit.invocationInfo !== void 0) {
16141
- raw.boundaryBinding = (0, import_behavioral_ir8.unitInvocationBinding)({
15841
+ raw.boundaryBinding = (0, import_behavioral_ir9.unitInvocationBinding)({
16142
15842
  recognition: pack.name,
16143
15843
  deploymentTarget: unit.invocationInfo.deploymentTarget,
16144
15844
  instanceName: unit.invocationInfo.instanceName
16145
15845
  });
16146
15846
  } else if (unit.operationInfo !== void 0) {
16147
- raw.boundaryBinding = (0, import_behavioral_ir8.graphqlOperationBinding)({
15847
+ raw.boundaryBinding = (0, import_behavioral_ir9.graphqlOperationBinding)({
16148
15848
  transport: pack.protocol,
16149
15849
  recognition: pack.name,
16150
15850
  operationType: unit.operationInfo.operationType,
@@ -16233,14 +15933,14 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16233
15933
  }
16234
15934
  if (raw.boundaryBinding === null) {
16235
15935
  if (unit.packageExportInfo !== void 0) {
16236
- raw.boundaryBinding = (0, import_behavioral_ir8.packageExportBinding)({
15936
+ raw.boundaryBinding = (0, import_behavioral_ir9.packageExportBinding)({
16237
15937
  transport: pack.protocol,
16238
15938
  recognition: pack.name,
16239
15939
  packageName: unit.packageExportInfo.packageName,
16240
15940
  exportPath: unit.packageExportInfo.exportPath
16241
15941
  });
16242
15942
  } else {
16243
- raw.boundaryBinding = (0, import_behavioral_ir8.functionCallBinding)({
15943
+ raw.boundaryBinding = (0, import_behavioral_ir9.functionCallBinding)({
16244
15944
  transport: pack.protocol,
16245
15945
  recognition: pack.name,
16246
15946
  ...unit.functionCallInfo !== void 0 ? {
@@ -16250,9 +15950,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
16250
15950
  });
16251
15951
  }
16252
15952
  }
16253
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
15953
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
16254
15954
  if (unit.unfollowed !== void 0 && options?.gapHandling !== "silent") {
16255
- summary.gaps.push(unfollowedCallGap(unit.unfollowed));
15955
+ summary.gaps.push((0, import_behavioral_ir3.unfollowedCallGap)(unit.unfollowed));
16256
15956
  }
16257
15957
  if (unit.metadata !== void 0 && Object.keys(unit.metadata).length > 0) {
16258
15958
  summary.metadata = {
@@ -16305,24 +16005,6 @@ function stampWrappers(raw, unit, wrappers) {
16305
16005
  recordFileDependency(wrapper.file);
16306
16006
  }
16307
16007
  }
16308
- function stampModuleImports(summaries, project) {
16309
- const byFile2 = /* @__PURE__ */ new Map();
16310
- for (const summary of summaries) {
16311
- const file = summary.location.file;
16312
- let imports = byFile2.get(file);
16313
- if (imports === void 0) {
16314
- imports = internalImportsOf(project, file);
16315
- byFile2.set(file, imports);
16316
- }
16317
- if (imports.length === 0) {
16318
- continue;
16319
- }
16320
- summary.metadata = {
16321
- ...summary.metadata ?? {},
16322
- moduleImports: imports
16323
- };
16324
- }
16325
- }
16326
16008
  function emitLibraryEnvReadMarkers(summaries, project, frameworks) {
16327
16009
  for (const pack of frameworks) {
16328
16010
  for (const declared of pack.libraryEnvVars ?? []) {
@@ -16350,14 +16032,14 @@ function fileImporting(project, modulePrefix) {
16350
16032
  function liftSchemasOntoDocuments(summaries) {
16351
16033
  const sdlByDocument = /* @__PURE__ */ new Map();
16352
16034
  for (const summary of summaries) {
16353
- const label = (0, import_behavioral_ir8.readSourceDocumentMetadata)(summary)?.label;
16354
- const graphql = (0, import_behavioral_ir8.readGraphqlMetadata)(summary);
16035
+ const label = (0, import_behavioral_ir9.readSourceDocumentMetadata)(summary)?.label;
16036
+ const graphql = (0, import_behavioral_ir9.readGraphqlMetadata)(summary);
16355
16037
  if (label === void 0 || graphql?.schemaSdl === void 0) {
16356
16038
  continue;
16357
16039
  }
16358
16040
  sdlByDocument.set(label, graphql.schemaSdl);
16359
16041
  const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
16360
- summary.metadata = (0, import_behavioral_ir8.withGraphqlMetadata)(summary.metadata, rest);
16042
+ summary.metadata = (0, import_behavioral_ir9.withGraphqlMetadata)(summary.metadata, rest);
16361
16043
  }
16362
16044
  for (const [label, schemaSdl] of sdlByDocument) {
16363
16045
  summaries.push(schemaDocumentSummary(label, schemaSdl));
@@ -16372,8 +16054,8 @@ function schemaDocumentSummary(label, schemaSdl) {
16372
16054
  transitions: [],
16373
16055
  gaps: [],
16374
16056
  confidence: { source: "declared", level: "high" },
16375
- metadata: (0, import_behavioral_ir8.withGraphqlMetadata)(
16376
- (0, import_behavioral_ir8.withSourceDocumentMetadata)(void 0, { label }),
16057
+ metadata: (0, import_behavioral_ir9.withGraphqlMetadata)(
16058
+ (0, import_behavioral_ir9.withSourceDocumentMetadata)(void 0, { label }),
16377
16059
  { schemaSdl }
16378
16060
  )
16379
16061
  };
@@ -16385,7 +16067,7 @@ function libraryEnvReadMarker(pack, declared, importer) {
16385
16067
  identity: {
16386
16068
  name: `${declared.module} env reads`,
16387
16069
  exportPath: null,
16388
- boundaryBinding: (0, import_behavioral_ir8.functionCallBinding)({
16070
+ boundaryBinding: (0, import_behavioral_ir9.functionCallBinding)({
16389
16071
  transport: "in-process",
16390
16072
  recognition: pack.name
16391
16073
  })
@@ -16497,8 +16179,8 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
16497
16179
  if (pathArg === void 0) {
16498
16180
  continue;
16499
16181
  }
16500
- const path15 = pathFromArgument(pathArg, resolution);
16501
- if (path15 === void 0) {
16182
+ const path14 = pathFromArgument(pathArg, resolution);
16183
+ if (path14 === void 0) {
16502
16184
  continue;
16503
16185
  }
16504
16186
  const callerFunc = enclosingFunction(callExpr);
@@ -16510,7 +16192,7 @@ function synthesizeCallerSummaries(wrapper, _project, options, resolution) {
16510
16192
  continue;
16511
16193
  }
16512
16194
  seen.add(dedupKey);
16513
- out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path15, options));
16195
+ out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path14, options));
16514
16196
  }
16515
16197
  return out;
16516
16198
  }
@@ -16567,7 +16249,7 @@ function callerName(func) {
16567
16249
  }
16568
16250
  return "anonymous";
16569
16251
  }
16570
- function buildCallerSummary(wrapper, callerFunc, callExpr, path15, options) {
16252
+ function buildCallerSummary(wrapper, callerFunc, callExpr, path14, options) {
16571
16253
  const wrapperBinding = wrapper.summary.identity.boundaryBinding;
16572
16254
  const wrapperRest = wrapperBinding?.semantics.name === "rest" ? wrapperBinding.semantics : null;
16573
16255
  const syntheticPack = {
@@ -16596,13 +16278,13 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path15, options) {
16596
16278
  }
16597
16279
  };
16598
16280
  const raw = extractCodeStructure(unit, syntheticPack);
16599
- raw.boundaryBinding = (0, import_behavioral_ir8.restBinding)({
16281
+ raw.boundaryBinding = (0, import_behavioral_ir9.restBinding)({
16600
16282
  transport: wrapperBinding?.transport ?? "http",
16601
16283
  method: wrapperRest?.method ?? null,
16602
- path: path15,
16284
+ path: path14,
16603
16285
  recognition: wrapperBinding?.recognition ?? "unknown"
16604
16286
  });
16605
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
16287
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
16606
16288
  summary.metadata = {
16607
16289
  ...summary.metadata ?? {},
16608
16290
  derivedFromWrapper: {
@@ -16621,25 +16303,12 @@ function named(summaries, workspace, runRoot) {
16621
16303
  });
16622
16304
  return summaries;
16623
16305
  }
16624
- var saidWhyNoCache = false;
16625
16306
  function extractionConfigStamp(config) {
16626
16307
  return [
16627
16308
  `includeReachable=${config.includeReachable !== false}`,
16628
16309
  `gapHandling=${config.extractorOptions?.gapHandling ?? "default"}`
16629
16310
  ].join(",");
16630
16311
  }
16631
- function declineWhenRunFromSource(cacheDir) {
16632
- if (cacheDir === null || adapterCodeStamp().kind === "bundle") {
16633
- return cacheDir;
16634
- }
16635
- if (!saidWhyNoCache) {
16636
- saidWhyNoCache = true;
16637
- process.stderr.write(
16638
- "[suss] extraction cache off: this process loaded the adapter from source, where nothing can tell one build of it from another. Run the built adapter to cache.\n"
16639
- );
16640
- }
16641
- return null;
16642
- }
16643
16312
  function loadRunFiles(project, candidatePaths, timer) {
16644
16313
  if (candidatePaths !== null) {
16645
16314
  const deep2 = timer.time(
@@ -16670,7 +16339,7 @@ function createTypeScriptAdapter(suppliedConfig) {
16670
16339
  ...suppliedConfig,
16671
16340
  frameworks: expandWorkspacePatterns(
16672
16341
  suppliedConfig.frameworks,
16673
- suppliedConfig.projectRoot ?? (suppliedConfig.tsConfigFilePath !== void 0 ? import_node_path11.default.dirname(suppliedConfig.tsConfigFilePath) : void 0)
16342
+ suppliedConfig.projectRoot ?? (suppliedConfig.tsConfigFilePath !== void 0 ? import_node_path10.default.dirname(suppliedConfig.tsConfigFilePath) : void 0)
16674
16343
  )
16675
16344
  };
16676
16345
  const project = config.project ?? new import_ts_morph66.Project(
@@ -16679,24 +16348,18 @@ function createTypeScriptAdapter(suppliedConfig) {
16679
16348
  skipAddingFilesFromTsConfig: true
16680
16349
  } : { skipAddingFilesFromTsConfig: true }
16681
16350
  );
16682
- const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(import_node_path11.default.dirname(config.tsConfigFilePath)) : void 0);
16351
+ const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(import_node_path10.default.dirname(config.tsConfigFilePath)) : void 0);
16683
16352
  let lazyBootstrapped = false;
16684
16353
  let projectFileSet;
16685
16354
  const cacheDir = declineWhenRunFromSource(
16686
- config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? import_node_path11.default.join(import_node_path11.default.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
16355
+ config.cacheDir === null ? null : config.cacheDir ?? (config.tsConfigFilePath !== void 0 ? import_node_path10.default.join(import_node_path10.default.dirname(config.tsConfigFilePath), ".suss", "cache") : null)
16687
16356
  );
16688
- const cache = createCacheLayer(cacheDir);
16357
+ const cache = (0, import_extractor11.createCacheLayer)(cacheDir);
16689
16358
  const packsDigest = `${computeAdapterPacksDigest(
16690
16359
  config.frameworks.map(
16691
16360
  (p) => p.version !== void 0 ? { name: p.name, version: p.version } : { name: p.name }
16692
16361
  )
16693
16362
  )}|${extractionConfigStamp(config)}|ws:${workspaceExpansionStamp(config.frameworks)}`;
16694
- const digestFor = (files) => {
16695
- const inputs = config.frameworks.flatMap(
16696
- (pack) => pack.discoveryInputs?.(files) ?? []
16697
- );
16698
- return `${packsDigest}|reads:${projectFileStamp(inputs)}`;
16699
- };
16700
16363
  const packWrappers = config.frameworks.flatMap(
16701
16364
  (pack) => pack.transparentWrappers ?? []
16702
16365
  );
@@ -16729,8 +16392,8 @@ function createTypeScriptAdapter(suppliedConfig) {
16729
16392
  return named(summaries, config.workspace, runRoot);
16730
16393
  },
16731
16394
  async extractAll() {
16732
- const timer = createTimer();
16733
- const tallies = createPackTallies(config.frameworks);
16395
+ const timer = (0, import_extractor8.createTimer)();
16396
+ const tallies = (0, import_extractor3.createPackTallies)(config.frameworks);
16734
16397
  forgetUnreadableExportFiles();
16735
16398
  forgetReassignedNamesUnstated();
16736
16399
  const lazyEligible = !lazyBootstrapped && config.tsConfigFilePath !== void 0 && config.project === void 0;
@@ -16740,20 +16403,17 @@ function createTypeScriptAdapter(suppliedConfig) {
16740
16403
  config.tsConfigFilePath ?? raise("lazy bootstrap requires tsConfigFilePath")
16741
16404
  )
16742
16405
  ) : null;
16743
- const adapterPacksDigest = cacheDir === null ? packsDigest : timer.time(
16744
- "cache.digest",
16745
- () => digestFor(
16746
- tsconfigFileList ?? project.getSourceFiles().map((sf) => sf.getFilePath())
16747
- )
16748
- );
16749
- const cacheInput = tsconfigFileList !== null ? {
16750
- files: tsconfigFileList,
16751
- adapterPacksDigest,
16752
- tsconfigPath: config.tsConfigFilePath ?? raise("lazy bootstrap requires tsConfigFilePath")
16753
- } : {
16754
- project,
16406
+ let cacheFiles = null;
16407
+ const adapterPacksDigest = cacheDir === null ? packsDigest : timer.time("cache.digest", () => {
16408
+ cacheFiles = tsconfigFileList ?? project.getSourceFiles().map((sf) => sf.getFilePath());
16409
+ return (0, import_extractor11.runDigest)(packsDigest, config.frameworks, cacheFiles);
16410
+ });
16411
+ const cacheInput = {
16412
+ // Empty when cacheDir is null: a no-op cache layer never reads
16413
+ // the file list, so there is nothing worth resolving early.
16414
+ files: cacheFiles ?? [],
16755
16415
  adapterPacksDigest,
16756
- ...config.tsConfigFilePath !== void 0 ? { tsconfigPath: config.tsConfigFilePath } : {}
16416
+ ...config.tsConfigFilePath !== void 0 ? { configPath: config.tsConfigFilePath } : {}
16757
16417
  };
16758
16418
  const lookup = await timer.timeAsync(
16759
16419
  "cache.lookup",
@@ -16959,7 +16619,8 @@ function createTypeScriptAdapter(suppliedConfig) {
16959
16619
  ],
16960
16620
  access: collectAccessRecognizers(config.frameworks, tallies),
16961
16621
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
16962
- resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom)
16622
+ resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom),
16623
+ sourceDeclarationsBehind: (declaration) => sourceDeclarationsBehind(declaration, resolution)
16963
16624
  },
16964
16625
  // Reached units the cache already serves emit nothing,
16965
16626
  // the way a cold run's seeds do not.
@@ -16996,7 +16657,10 @@ function createTypeScriptAdapter(suppliedConfig) {
16996
16657
  }
16997
16658
  timer.time(
16998
16659
  "stampModuleImports",
16999
- () => stampModuleImports(enriched, project)
16660
+ () => (0, import_extractor11.stampModuleImports)(enriched, (file) => {
16661
+ const imports = internalImportsOf(project, file);
16662
+ return imports.length === 0 ? void 0 : imports;
16663
+ })
17000
16664
  );
17001
16665
  timer.time(
17002
16666
  "emitLibraryEnvReadMarkers",
@@ -17055,7 +16719,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17055
16719
  });
17056
16720
  const composed = timer.time(
17057
16721
  "composeWrappers",
17058
- () => (0, import_extractor7.composeWrappers)(enriched)
16722
+ () => (0, import_extractor11.composeWrappers)(enriched)
17059
16723
  );
17060
16724
  if (config.onTiming !== void 0) {
17061
16725
  config.onTiming(timer.report());
@@ -17087,7 +16751,7 @@ function createTypeScriptAdapter(suppliedConfig) {
17087
16751
  };
17088
16752
  }
17089
16753
  function mountAssumptionsAgree(record2, index) {
17090
- return Object.entries(record2.mountPrefixes).every(
16754
+ return Object.entries(record2.meta.mountPrefixes).every(
17091
16755
  ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
17092
16756
  );
17093
16757
  }
@@ -17187,10 +16851,10 @@ function mergeWithReused(reused, fresh, ownersBySummary) {
17187
16851
  return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
17188
16852
  }
17189
16853
  function readsRunLevelJoins(summary) {
17190
- if ((0, import_behavioral_ir8.readSourceDocumentMetadata)(summary) !== void 0) {
16854
+ if ((0, import_behavioral_ir9.readSourceDocumentMetadata)(summary) !== void 0) {
17191
16855
  return true;
17192
16856
  }
17193
- if ((0, import_behavioral_ir8.readGraphqlMetadata)(summary)?.client !== void 0) {
16857
+ if ((0, import_behavioral_ir9.readGraphqlMetadata)(summary)?.client !== void 0) {
17194
16858
  return true;
17195
16859
  }
17196
16860
  const semantics = summary.identity.boundaryBinding?.semantics;
@@ -17247,7 +16911,7 @@ function buildCacheAttribution(args) {
17247
16911
  cacheable,
17248
16912
  deps: [...deps].sort(),
17249
16913
  claims: sink.claims,
17250
- mountPrefixes: Object.fromEntries(sink.mountPrefixes),
16914
+ meta: { mountPrefixes: Object.fromEntries(sink.mountPrefixes) },
17251
16915
  packs: packNamesByPath.get(rootPath) ?? []
17252
16916
  });
17253
16917
  }
@@ -17380,7 +17044,7 @@ function buildSubUnitSummary(subUnit, parent, invocationRecognizers, accessRecog
17380
17044
  if (parent.identity.boundaryBinding !== null) {
17381
17045
  raw.boundaryBinding = parent.identity.boundaryBinding;
17382
17046
  }
17383
- const summary = (0, import_extractor7.assembleSummary)(raw, options);
17047
+ const summary = (0, import_extractor11.assembleSummary)(raw, options);
17384
17048
  if (subUnit.metadata !== void 0 && Object.keys(subUnit.metadata).length > 0) {
17385
17049
  summary.metadata = {
17386
17050
  ...summary.metadata ?? {},
@@ -17392,8 +17056,8 @@ function buildSubUnitSummary(subUnit, parent, invocationRecognizers, accessRecog
17392
17056
  }
17393
17057
 
17394
17058
  // src/bootstrap/noTsconfigProject.ts
17395
- var import_node_fs7 = __toESM(require("fs"), 1);
17396
- var import_node_path12 = __toESM(require("path"), 1);
17059
+ var import_node_fs4 = __toESM(require("fs"), 1);
17060
+ var import_node_path11 = __toESM(require("path"), 1);
17397
17061
  var import_ts_morph67 = require("ts-morph");
17398
17062
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
17399
17063
  ".ts",
@@ -17424,7 +17088,7 @@ function findSourceFiles(rootDir, depth = 0) {
17424
17088
  const found = [];
17425
17089
  let entries;
17426
17090
  try {
17427
- entries = import_node_fs7.default.readdirSync(rootDir, { withFileTypes: true });
17091
+ entries = import_node_fs4.default.readdirSync(rootDir, { withFileTypes: true });
17428
17092
  } catch {
17429
17093
  return [];
17430
17094
  }
@@ -17432,10 +17096,10 @@ function findSourceFiles(rootDir, depth = 0) {
17432
17096
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name)) {
17433
17097
  continue;
17434
17098
  }
17435
- const full = import_node_path12.default.join(rootDir, entry.name);
17099
+ const full = import_node_path11.default.join(rootDir, entry.name);
17436
17100
  if (entry.isDirectory()) {
17437
17101
  found.push(...findSourceFiles(full, depth + 1));
17438
- } else if (SOURCE_EXTENSIONS.has(import_node_path12.default.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
17102
+ } else if (SOURCE_EXTENSIONS.has(import_node_path11.default.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
17439
17103
  found.push(full);
17440
17104
  }
17441
17105
  }
@@ -17470,15 +17134,15 @@ function createProjectWithoutTsconfig(rootDir) {
17470
17134
  return { project, files };
17471
17135
  }
17472
17136
  function findNearestTsconfig(startDir) {
17473
- let dir = import_node_path12.default.resolve(startDir);
17137
+ let dir = import_node_path11.default.resolve(startDir);
17474
17138
  while (true) {
17475
17139
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
17476
- const candidate = import_node_path12.default.join(dir, name);
17477
- if (import_node_fs7.default.existsSync(candidate)) {
17140
+ const candidate = import_node_path11.default.join(dir, name);
17141
+ if (import_node_fs4.default.existsSync(candidate)) {
17478
17142
  return candidate;
17479
17143
  }
17480
17144
  }
17481
- const parent = import_node_path12.default.dirname(dir);
17145
+ const parent = import_node_path11.default.dirname(dir);
17482
17146
  if (parent === dir) {
17483
17147
  return null;
17484
17148
  }
@@ -17487,7 +17151,7 @@ function findNearestTsconfig(startDir) {
17487
17151
  }
17488
17152
 
17489
17153
  // src/facts/why.ts
17490
- var import_node_path13 = __toESM(require("path"), 1);
17154
+ var import_node_path12 = __toESM(require("path"), 1);
17491
17155
  var import_ts_morph68 = require("ts-morph");
17492
17156
  var import_resolution7 = require("@suss/resolution");
17493
17157
  var JS_PHRASES = {
@@ -17495,12 +17159,12 @@ var JS_PHRASES = {
17495
17159
  reason: `${describe(tuple[0])} is a .bind of ${describe(tuple[1])}, which runs the same function`
17496
17160
  })
17497
17161
  };
17498
- var WhySession = class {
17162
+ var TypeScriptWhySession = class {
17499
17163
  project;
17500
17164
  store;
17501
17165
  root;
17502
17166
  constructor(options) {
17503
- this.root = import_node_path13.default.resolve(options.dir);
17167
+ this.root = import_node_path12.default.resolve(options.dir);
17504
17168
  const tsconfig = findNearestTsconfig(this.root);
17505
17169
  this.project = tsconfig === null ? createProjectWithoutTsconfig(this.root).project : new import_ts_morph68.Project({ tsConfigFilePath: tsconfig });
17506
17170
  this.store = new ResolutionStore();
@@ -17589,7 +17253,7 @@ var WhySession = class {
17589
17253
  };
17590
17254
  }
17591
17255
  sourceFileAt(file) {
17592
- const absolute = import_node_path13.default.resolve(this.root, file);
17256
+ const absolute = import_node_path12.default.resolve(this.root, file);
17593
17257
  const loaded = this.project.getSourceFile(absolute);
17594
17258
  if (loaded !== void 0) {
17595
17259
  return loaded;
@@ -17603,7 +17267,7 @@ var WhySession = class {
17603
17267
  /** A module key: a file path said relative to the root, or a package name. */
17604
17268
  displayPath(key) {
17605
17269
  if (key.startsWith(this.root)) {
17606
- return import_node_path13.default.relative(this.root, key);
17270
+ return import_node_path12.default.relative(this.root, key);
17607
17271
  }
17608
17272
  return key;
17609
17273
  }
@@ -17631,228 +17295,8 @@ function displayNameOf(node) {
17631
17295
  return text.length > 40 ? `${text.slice(0, 40)}...` : text;
17632
17296
  }
17633
17297
 
17634
- // src/packHealth.ts
17635
- function stagesOf(funnel) {
17636
- const stages = [];
17637
- const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
17638
- if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
17639
- stages.push({
17640
- from: { count: funnel.unitsInGatedFiles, name: "unit bodies" },
17641
- to: { count: funnel.effectsRecognized, name: "effects" }
17642
- });
17643
- }
17644
- if (funnel.discovers && gateSaysSomething) {
17645
- stages.push({
17646
- from: { count: funnel.candidateFiles, name: "source files" },
17647
- to: { count: funnel.unitsDiscovered, name: "units" }
17648
- });
17649
- }
17650
- stages.push(
17651
- {
17652
- from: { count: funnel.unitsClaimed, name: "units" },
17653
- to: { count: funnel.summariesBound, name: "summaries" }
17654
- },
17655
- {
17656
- from: { count: funnel.summariesBound, name: "summaries" },
17657
- to: { count: funnel.summariesWithBehavior, name: "transitions" }
17658
- }
17659
- );
17660
- return stages;
17661
- }
17662
- function funnelDrops(packs) {
17663
- const violations = [];
17664
- for (const funnel of packs) {
17665
- for (const stage of stagesOf(funnel)) {
17666
- if (stage.from.count === 0 || stage.to.count > 0) {
17667
- continue;
17668
- }
17669
- violations.push({
17670
- label: funnel.pack,
17671
- detail: `${stage.from.count} ${stage.from.name} -> 0 ${stage.to.name}`
17672
- });
17673
- }
17674
- }
17675
- return violations;
17676
- }
17677
- function recognizersWithNoUnits(packs) {
17678
- return packs.filter(
17679
- (funnel) => funnel.recognizes && !funnel.discovers && funnel.gates.length > 0 && funnel.unresolvedGates.length === 0 && funnel.candidateFiles > 0 && funnel.unitsInGatedFiles === 0 && // The closure walks a recognizer-only pack's gated exports as
17680
- // roots, so effects recognized there mean the run worked and
17681
- // the missing framework pack costs attribution, not existence.
17682
- funnel.effectsRecognized === 0
17683
- ).map((funnel) => ({
17684
- label: funnel.pack,
17685
- detail: `${funnel.candidateFiles} gated files, and no pack in this run discovered a unit in them. ${funnel.pack} reads calls inside units another pack finds, so add the pack that finds this project's handlers (-f express, -f fastify, ...), or run suss init to work out which.`
17686
- }));
17687
- }
17688
- function helpersThatMatchedNothing(packs) {
17689
- return packs.flatMap(
17690
- (funnel) => funnel.helpersUnmatched.map((helper) => ({
17691
- label: funnel.pack,
17692
- detail: `${helper} matched no call in this run, so whatever it registers is missing. suss read that helper out of the project and then failed to match a call to it, which is a bug in suss rather than in your code.`
17693
- }))
17694
- );
17695
- }
17696
- function unversionedPacks(packs) {
17697
- return packs.filter((funnel) => funnel.version === null).map((funnel) => ({
17698
- label: funnel.pack,
17699
- detail: "no version declared"
17700
- }));
17701
- }
17702
- function selfCollisions(packs) {
17703
- return packs.filter((funnel) => funnel.selfCollisions > 0).map((funnel) => ({
17704
- label: funnel.pack,
17705
- detail: `${funnel.selfCollisions} ${funnel.selfCollisions === 1 ? "unit" : "units"} matched twice, second dropped`
17706
- }));
17707
- }
17708
- function threwWhileReading(packs) {
17709
- return packs.filter((funnel) => funnel.failures.length > 0).map((funnel) => {
17710
- const first = funnel.failures[0];
17711
- const rest = funnel.failures.length - 1;
17712
- const alsoIn = rest > 0 ? ` (+${rest} more)` : "";
17713
- return {
17714
- label: funnel.pack,
17715
- detail: `${first.hook} on ${first.file}${alsoIn}: ${first.message}`
17716
- };
17717
- });
17718
- }
17719
- function packGradients(report) {
17720
- const gradients = [];
17721
- for (const funnel of report.packs) {
17722
- if (funnel.declarations === null) {
17723
- continue;
17724
- }
17725
- gradients.push(gradientOf(funnel.pack, funnel.declarations.declarations));
17726
- }
17727
- return gradients;
17728
- }
17729
- function gradientOf(pack, declarations) {
17730
- const gradient = {
17731
- pack,
17732
- dataLinks: 0,
17733
- functionLinks: [],
17734
- astLinks: [],
17735
- withoutExample: []
17736
- };
17737
- for (const declaration of declarations) {
17738
- gradient.dataLinks += declaration.dataLinks;
17739
- for (const question of declaration.functionLinks) {
17740
- gradient.functionLinks.push(`${declaration.name}.${question}`);
17741
- }
17742
- for (const question of declaration.astLinks) {
17743
- gradient.astLinks.push(`${declaration.name}.${question}`);
17744
- }
17745
- if (declaration.example === null) {
17746
- gradient.withoutExample.push(declaration.name);
17747
- }
17748
- }
17749
- return gradient;
17750
- }
17751
- function opaqueLinks(gradients) {
17752
- return gradients.filter((gradient) => gradient.functionLinks.length > 0).map((gradient) => ({
17753
- label: gradient.pack,
17754
- detail: `${gradient.functionLinks.join(", ")} (${gradient.dataLinks} data)`
17755
- }));
17756
- }
17757
- function reachesTheSyntaxTree(gradients) {
17758
- return gradients.filter((gradient) => gradient.astLinks.length > 0).map((gradient) => ({
17759
- label: gradient.pack,
17760
- detail: gradient.astLinks.join(", ")
17761
- }));
17762
- }
17763
- function undocumentedDeclarations(gradients) {
17764
- return gradients.filter((gradient) => gradient.withoutExample.length > 0).map((gradient) => ({
17765
- label: gradient.pack,
17766
- detail: gradient.withoutExample.join(", ")
17767
- }));
17768
- }
17769
- function evaluatePackHealth(report) {
17770
- const gradients = packGradients(report);
17771
- return [
17772
- {
17773
- name: "no pack throws while it reads",
17774
- code: "threw",
17775
- audience: "run",
17776
- violations: threwWhileReading(report.packs)
17777
- },
17778
- {
17779
- name: "no pack finds something and records nothing",
17780
- code: "no-output",
17781
- audience: "run",
17782
- violations: funnelDrops(report.packs)
17783
- },
17784
- {
17785
- name: "no pack collides with itself",
17786
- code: "double-match",
17787
- audience: "run",
17788
- violations: selfCollisions(report.packs)
17789
- },
17790
- {
17791
- name: "every recognizer had units to look inside",
17792
- code: "no-units",
17793
- audience: "run",
17794
- violations: recognizersWithNoUnits(report.packs)
17795
- },
17796
- {
17797
- name: "every registration helper the run read matched a call",
17798
- code: "no-helper",
17799
- audience: "run",
17800
- violations: helpersThatMatchedNothing(report.packs)
17801
- },
17802
- {
17803
- name: "every pack declares a version",
17804
- code: "no-version",
17805
- audience: "pack",
17806
- violations: unversionedPacks(report.packs)
17807
- },
17808
- {
17809
- name: "every link a declared pack states is data",
17810
- code: "fn-link",
17811
- audience: "pack",
17812
- violations: opaqueLinks(gradients)
17813
- },
17814
- {
17815
- name: "no declared pack reads the syntax tree",
17816
- code: "ast-link",
17817
- audience: "pack",
17818
- violations: reachesTheSyntaxTree(gradients)
17819
- },
17820
- {
17821
- name: "every declaration states an example",
17822
- code: "no-example",
17823
- audience: "pack",
17824
- violations: undocumentedDeclarations(gradients)
17825
- }
17826
- ];
17827
- }
17828
- function formatPackHealth(checks, audiences) {
17829
- const fired = checks.filter(
17830
- (check) => check.violations.length > 0 && audiences.includes(check.audience)
17831
- );
17832
- if (fired.length === 0) {
17833
- return "";
17834
- }
17835
- const rows = fired.flatMap(
17836
- (check) => check.violations.map((violation) => ({
17837
- code: check.code,
17838
- pack: violation.label,
17839
- detail: violation.detail
17840
- }))
17841
- );
17842
- const codeWidth = Math.max(...rows.map((row) => row.code.length));
17843
- const packWidth = Math.max(...rows.map((row) => row.pack.length));
17844
- const lines = ["", `Pack health (${rows.length}):`];
17845
- for (const row of rows) {
17846
- lines.push(
17847
- ` ${row.code.padEnd(codeWidth)} ${row.pack.padEnd(packWidth)} ${row.detail}`
17848
- );
17849
- }
17850
- return `${lines.join("\n")}
17851
- `;
17852
- }
17853
-
17854
17298
  // src/stubEvidence.ts
17855
- var import_node_path14 = __toESM(require("path"), 1);
17299
+ var import_node_path13 = __toESM(require("path"), 1);
17856
17300
  var import_ts_morph69 = require("ts-morph");
17857
17301
  function stubEvidenceIn(project, packageName, root) {
17858
17302
  const resolution = new ResolutionStore();
@@ -17868,7 +17312,7 @@ function stubEvidenceIn(project, packageName, root) {
17868
17312
  calls: []
17869
17313
  };
17870
17314
  entry.calls.push({
17871
- file: import_node_path14.default.relative(root, sourceFile.getFilePath()).split(import_node_path14.default.sep).join("/"),
17315
+ file: import_node_path13.default.relative(root, sourceFile.getFilePath()).split(import_node_path13.default.sep).join("/"),
17872
17316
  line: one.call.getStartLineNumber(),
17873
17317
  args: one.call.getArguments().map((arg) => effectArgOf(arg))
17874
17318
  });
@@ -17883,7 +17327,7 @@ function stubEvidence(options) {
17883
17327
  const project = new import_ts_morph69.Project(
17884
17328
  options.tsConfigFilePath !== void 0 ? { tsConfigFilePath: options.tsConfigFilePath } : { compilerOptions: { allowJs: true } }
17885
17329
  );
17886
- const root = options.tsConfigFilePath !== void 0 ? import_node_path14.default.dirname(options.tsConfigFilePath) : import_node_path14.default.resolve(options.directory ?? process.cwd());
17330
+ const root = options.tsConfigFilePath !== void 0 ? import_node_path13.default.dirname(options.tsConfigFilePath) : import_node_path13.default.resolve(options.directory ?? process.cwd());
17887
17331
  if (options.tsConfigFilePath === void 0) {
17888
17332
  project.addSourceFilesAtPaths([
17889
17333
  `${root}/**/*.{ts,tsx,js,jsx}`,
@@ -17896,7 +17340,7 @@ function stubEvidence(options) {
17896
17340
  0 && (module.exports = {
17897
17341
  ADAPTER_VERSION,
17898
17342
  ResolutionStore,
17899
- WhySession,
17343
+ TypeScriptWhySession,
17900
17344
  accessContextFor,
17901
17345
  buildProjectHelperIndex,
17902
17346
  callOpsFor,
@@ -17909,13 +17353,11 @@ function stubEvidence(options) {
17909
17353
  createTsSubUnitContext,
17910
17354
  createTypeScriptAdapter,
17911
17355
  discoverUnits,
17912
- evaluatePackHealth,
17913
17356
  exportedDeclarationsOf,
17914
17357
  extractCodeStructure,
17915
17358
  extractRawBranches,
17916
17359
  findNearestTsconfig,
17917
17360
  findTerminals,
17918
- formatPackHealth,
17919
17361
  functionTargetOf,
17920
17362
  importDeclarationsOf,
17921
17363
  importedNamesOf,