@suss/adapter-typescript 0.9.0 → 0.11.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.js CHANGED
@@ -11,7 +11,11 @@ import {
11
11
  graphqlResolverBinding,
12
12
  messageBusBinding,
13
13
  packageExportBinding,
14
- restBinding as restBinding2
14
+ readGraphqlMetadata as readGraphqlMetadata2,
15
+ readSourceDocumentMetadata,
16
+ restBinding as restBinding2,
17
+ withGraphqlMetadata as withGraphqlMetadata2,
18
+ withSourceDocumentMetadata
15
19
  } from "@suss/behavioral-ir";
16
20
  import { Database as Database5 } from "@suss/datalog";
17
21
  import {
@@ -1030,32 +1034,72 @@ function collectFallthroughCaseLabels(caseClause) {
1030
1034
  }
1031
1035
 
1032
1036
  // src/paths/pathConditions.ts
1033
- import { Node as Node8 } from "ts-morph";
1037
+ import { Node as Node9 } from "ts-morph";
1034
1038
  import {
1035
1039
  enumerateStructuredPaths,
1036
1040
  PathBudgetExceeded,
1037
1041
  UnmodeledFlow as UnmodeledFlow2
1038
1042
  } from "@suss/extractor";
1039
1043
 
1044
+ // src/walk/descent.ts
1045
+ import { Node as Node7 } from "ts-morph";
1046
+ var NO_BARRIERS = /* @__PURE__ */ new Set();
1047
+ function isDescentStop(node, func, barriers = NO_BARRIERS) {
1048
+ if (node === func) {
1049
+ return false;
1050
+ }
1051
+ if (Node7.isFunctionDeclaration(node) || Node7.isMethodDeclaration(node)) {
1052
+ return true;
1053
+ }
1054
+ if (Node7.isArrowFunction(node) || Node7.isFunctionExpression(node)) {
1055
+ return barriers.has(node);
1056
+ }
1057
+ return false;
1058
+ }
1059
+ function isInlineCallback(node, func, barriers = NO_BARRIERS) {
1060
+ if (node === func) {
1061
+ return false;
1062
+ }
1063
+ if (!(Node7.isArrowFunction(node) || Node7.isFunctionExpression(node))) {
1064
+ return false;
1065
+ }
1066
+ return !barriers.has(node);
1067
+ }
1068
+ function isModuleScopeStop(node) {
1069
+ return Node7.isFunctionDeclaration(node) || Node7.isMethodDeclaration(node) || Node7.isArrowFunction(node) || Node7.isFunctionExpression(node) || Node7.isClassDeclaration(node) || Node7.isClassExpression(node);
1070
+ }
1071
+ function crossesNestedFunctionScope(node, func) {
1072
+ let current2 = node;
1073
+ while (current2 !== void 0 && current2 !== func) {
1074
+ if (startsItsOwnScope(current2)) {
1075
+ return true;
1076
+ }
1077
+ current2 = current2.getParent();
1078
+ }
1079
+ return false;
1080
+ }
1081
+ function startsItsOwnScope(node) {
1082
+ return Node7.isArrowFunction(node) || Node7.isFunctionExpression(node) || Node7.isFunctionDeclaration(node) || Node7.isMethodDeclaration(node) || Node7.isGetAccessorDeclaration(node) || Node7.isSetAccessorDeclaration(node) || Node7.isConstructorDeclaration(node);
1083
+ }
1084
+
1040
1085
  // src/paths/lowering.ts
1041
1086
  import {
1042
- Node as Node7
1087
+ Node as Node8
1043
1088
  } from "ts-morph";
1044
1089
  import {
1045
1090
  UnmodeledFlow
1046
1091
  } from "@suss/extractor";
1047
- var isFunctionBoundary = (node) => Node7.isFunctionDeclaration(node) || Node7.isFunctionExpression(node) || Node7.isArrowFunction(node) || Node7.isMethodDeclaration(node);
1048
1092
  function exitKindOf(stmt) {
1049
1093
  let sawReturn = false;
1050
1094
  let sawThrow = false;
1051
1095
  const visit = (node) => {
1052
- if (node !== stmt && isFunctionBoundary(node)) {
1096
+ if (node !== stmt && startsItsOwnScope(node)) {
1053
1097
  return;
1054
1098
  }
1055
- if (Node7.isReturnStatement(node)) {
1099
+ if (Node8.isReturnStatement(node)) {
1056
1100
  sawReturn = true;
1057
1101
  }
1058
- if (Node7.isThrowStatement(node)) {
1102
+ if (Node8.isThrowStatement(node)) {
1059
1103
  sawThrow = true;
1060
1104
  }
1061
1105
  for (const child of node.getChildren()) {
@@ -1072,14 +1116,14 @@ var statementsOf = (stmt) => {
1072
1116
  if (stmt === void 0) {
1073
1117
  return [];
1074
1118
  }
1075
- if (Node7.isBlock(stmt)) {
1119
+ if (Node8.isBlock(stmt)) {
1076
1120
  return stmt.getStatements();
1077
1121
  }
1078
1122
  return [stmt];
1079
1123
  };
1080
- var isLoop = (stmt) => Node7.isForOfStatement(stmt) || Node7.isForInStatement(stmt) || Node7.isForStatement(stmt) || Node7.isWhileStatement(stmt) || Node7.isDoStatement(stmt);
1124
+ var isLoop = (stmt) => Node8.isForOfStatement(stmt) || Node8.isForInStatement(stmt) || Node8.isForStatement(stmt) || Node8.isWhileStatement(stmt) || Node8.isDoStatement(stmt);
1081
1125
  function getLoopBody(stmt) {
1082
- if (Node7.isForOfStatement(stmt) || Node7.isForInStatement(stmt) || Node7.isForStatement(stmt) || Node7.isWhileStatement(stmt) || Node7.isDoStatement(stmt)) {
1126
+ if (Node8.isForOfStatement(stmt) || Node8.isForInStatement(stmt) || Node8.isForStatement(stmt) || Node8.isWhileStatement(stmt) || Node8.isDoStatement(stmt)) {
1083
1127
  return stmt.getStatement();
1084
1128
  }
1085
1129
  return void 0;
@@ -1094,37 +1138,97 @@ var conditionOf = (expr) => ({
1094
1138
  sourceText: expr.getText(),
1095
1139
  expression: expr
1096
1140
  });
1097
- function lowerStatement(stmt, rawToStructured) {
1098
- const structured = buildStructured(stmt, rawToStructured);
1099
- rawToStructured.set(stmt, structured);
1141
+ function lowerStatement(stmt, ctx) {
1142
+ const structured = {
1143
+ ...buildStructured(stmt, ctx),
1144
+ callbacks: lowerCallbacksIn(ownExpressionsOf(stmt), ctx)
1145
+ };
1146
+ ctx.rawToStructured.set(stmt, structured);
1100
1147
  return structured;
1101
1148
  }
1102
- function lowerList(stmts, rawToStructured) {
1103
- return spliceBlocks(stmts).map((s) => lowerStatement(s, rawToStructured));
1149
+ function lowerList(stmts, ctx) {
1150
+ return spliceBlocks(stmts).map((s) => lowerStatement(s, ctx));
1151
+ }
1152
+ function ownExpressionsOf(stmt) {
1153
+ if (Node8.isIfStatement(stmt) || Node8.isSwitchStatement(stmt)) {
1154
+ return [stmt.getExpression()];
1155
+ }
1156
+ if (Node8.isWhileStatement(stmt) || Node8.isDoStatement(stmt)) {
1157
+ return [stmt.getExpression()];
1158
+ }
1159
+ if (Node8.isForOfStatement(stmt) || Node8.isForInStatement(stmt)) {
1160
+ return [stmt.getInitializer(), stmt.getExpression()];
1161
+ }
1162
+ if (Node8.isForStatement(stmt)) {
1163
+ return [
1164
+ stmt.getInitializer(),
1165
+ stmt.getCondition(),
1166
+ stmt.getIncrementor()
1167
+ ].filter((n) => n !== void 0);
1168
+ }
1169
+ if (Node8.isTryStatement(stmt)) {
1170
+ return [];
1171
+ }
1172
+ if (Node8.isReturnStatement(stmt) || Node8.isThrowStatement(stmt)) {
1173
+ const value = stmt.getExpression();
1174
+ return value === void 0 ? [] : [value];
1175
+ }
1176
+ if (Node8.isBreakStatement(stmt) || Node8.isContinueStatement(stmt)) {
1177
+ return [];
1178
+ }
1179
+ return [stmt];
1180
+ }
1181
+ function lowerCallbacksIn(parts, ctx) {
1182
+ const bodies = [];
1183
+ const visit = (node, root) => {
1184
+ if (node !== root && isDescentStop(node, ctx.func, ctx.barriers)) {
1185
+ return;
1186
+ }
1187
+ if (node !== root && (Node8.isArrowFunction(node) || Node8.isFunctionExpression(node)) && isInlineCallback(node, ctx.func, ctx.barriers)) {
1188
+ const body = node.getBody();
1189
+ if (body !== void 0 && Node8.isBlock(body)) {
1190
+ bodies.push(lowerList(body.getStatements(), ctx));
1191
+ return;
1192
+ }
1193
+ } else if (node !== root && startsItsOwnScope(node)) {
1194
+ return;
1195
+ }
1196
+ for (const child of node.getChildren()) {
1197
+ visit(child, root);
1198
+ }
1199
+ };
1200
+ for (const part of parts) {
1201
+ visit(part, part);
1202
+ }
1203
+ return bodies;
1204
+ }
1205
+ function lowerExpressionBodyCallbacks(expression, func, barriers = NO_BARRIERS) {
1206
+ return lowerCallbacksIn([expression], {
1207
+ func,
1208
+ barriers,
1209
+ rawToStructured: /* @__PURE__ */ new Map()
1210
+ });
1104
1211
  }
1105
1212
  function spliceBlocks(stmts) {
1106
1213
  return stmts.flatMap(
1107
- (s) => Node7.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1214
+ (s) => Node8.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1108
1215
  );
1109
1216
  }
1110
- function buildStructured(stmt, rawToStructured) {
1111
- if (Node7.isIfStatement(stmt)) {
1217
+ function buildStructured(stmt, ctx) {
1218
+ if (Node8.isIfStatement(stmt)) {
1112
1219
  const elseBranch = stmt.getElseStatement();
1113
1220
  return {
1114
1221
  kind: "if",
1115
1222
  condition: conditionOf(stmt.getExpression()),
1116
- thenBody: lowerList(
1117
- statementsOf(stmt.getThenStatement()),
1118
- rawToStructured
1119
- ),
1120
- elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), rawToStructured),
1223
+ thenBody: lowerList(statementsOf(stmt.getThenStatement()), ctx),
1224
+ elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), ctx),
1121
1225
  exitKind: exitKindOf(stmt)
1122
1226
  };
1123
1227
  }
1124
- if (Node7.isSwitchStatement(stmt)) {
1228
+ if (Node8.isSwitchStatement(stmt)) {
1125
1229
  return {
1126
1230
  kind: "switch",
1127
- groups: lowerSwitchGroups(stmt, rawToStructured),
1231
+ groups: lowerSwitchGroups(stmt, ctx),
1128
1232
  exitKind: exitKindOf(stmt)
1129
1233
  };
1130
1234
  }
@@ -1132,36 +1236,36 @@ function buildStructured(stmt, rawToStructured) {
1132
1236
  return {
1133
1237
  kind: "loop",
1134
1238
  condition: { sourceText: loopHeaderText(stmt), expression: null },
1135
- body: lowerList(statementsOf(getLoopBody(stmt)), rawToStructured),
1239
+ body: lowerList(statementsOf(getLoopBody(stmt)), ctx),
1136
1240
  exitKind: exitKindOf(stmt)
1137
1241
  };
1138
1242
  }
1139
- if (Node7.isTryStatement(stmt)) {
1243
+ if (Node8.isTryStatement(stmt)) {
1140
1244
  const catchClause = stmt.getCatchClause();
1141
1245
  const finallyBlock = stmt.getFinallyBlock();
1142
1246
  return {
1143
1247
  kind: "try",
1144
- tryBody: lowerList(stmt.getTryBlock().getStatements(), rawToStructured),
1145
- catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), rawToStructured),
1146
- finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), rawToStructured),
1248
+ tryBody: lowerList(stmt.getTryBlock().getStatements(), ctx),
1249
+ catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), ctx),
1250
+ finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), ctx),
1147
1251
  exitKind: exitKindOf(stmt)
1148
1252
  };
1149
1253
  }
1150
- if (Node7.isReturnStatement(stmt)) {
1254
+ if (Node8.isReturnStatement(stmt)) {
1151
1255
  return { kind: "exit", exit: "return", exitKind: exitKindOf(stmt) };
1152
1256
  }
1153
- if (Node7.isThrowStatement(stmt)) {
1257
+ if (Node8.isThrowStatement(stmt)) {
1154
1258
  return { kind: "exit", exit: "throw", exitKind: exitKindOf(stmt) };
1155
1259
  }
1156
- if (Node7.isBreakStatement(stmt)) {
1260
+ if (Node8.isBreakStatement(stmt)) {
1157
1261
  return { kind: "exit", exit: "break", exitKind: exitKindOf(stmt) };
1158
1262
  }
1159
- if (Node7.isContinueStatement(stmt)) {
1263
+ if (Node8.isContinueStatement(stmt)) {
1160
1264
  return { kind: "exit", exit: "continue", exitKind: exitKindOf(stmt) };
1161
1265
  }
1162
1266
  return { kind: "opaque", exitKind: exitKindOf(stmt) };
1163
1267
  }
1164
- function lowerSwitchGroups(stmt, rawToStructured) {
1268
+ function lowerSwitchGroups(stmt, ctx) {
1165
1269
  const switchText = stmt.getExpression().getText();
1166
1270
  const groups = [];
1167
1271
  let pendingLabels = [];
@@ -1170,7 +1274,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1170
1274
  if (sawDefault) {
1171
1275
  throw new UnmodeledFlow("default clause is not last");
1172
1276
  }
1173
- if (Node7.isDefaultClause(clause)) {
1277
+ if (Node8.isDefaultClause(clause)) {
1174
1278
  sawDefault = true;
1175
1279
  const stmts2 = clause.getStatements();
1176
1280
  if (stmts2.length === 0) {
@@ -1178,11 +1282,11 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1178
1282
  }
1179
1283
  groups.push({
1180
1284
  condition: null,
1181
- ...lowerGroupBody(stmts2, rawToStructured)
1285
+ ...lowerGroupBody(stmts2, ctx)
1182
1286
  });
1183
1287
  continue;
1184
1288
  }
1185
- if (!Node7.isCaseClause(clause)) {
1289
+ if (!Node8.isCaseClause(clause)) {
1186
1290
  continue;
1187
1291
  }
1188
1292
  pendingLabels.push(clause.getExpression().getText());
@@ -1195,7 +1299,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1195
1299
  const sourceText = labels.map((l) => `${switchText} === ${l}`).join(" || ");
1196
1300
  groups.push({
1197
1301
  condition: { sourceText, expression: null },
1198
- ...lowerGroupBody(stmts, rawToStructured)
1302
+ ...lowerGroupBody(stmts, ctx)
1199
1303
  });
1200
1304
  }
1201
1305
  return groups;
@@ -1203,41 +1307,42 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1203
1307
  function validateClauseBreaks(stmts) {
1204
1308
  const last = stmts[stmts.length - 1];
1205
1309
  for (const stmt of stmts) {
1206
- if (stmt === last && Node7.isBreakStatement(stmt)) {
1310
+ if (stmt === last && Node8.isBreakStatement(stmt)) {
1207
1311
  continue;
1208
1312
  }
1209
- if (Node7.isSwitchStatement(stmt) || isLoop(stmt)) {
1313
+ if (Node8.isSwitchStatement(stmt) || isLoop(stmt)) {
1210
1314
  continue;
1211
1315
  }
1212
1316
  stmt.forEachDescendant((node, traversal) => {
1213
- if (isFunctionBoundary(node) || Node7.isSwitchStatement(node) || isLoop(node)) {
1317
+ if (startsItsOwnScope(node) || Node8.isSwitchStatement(node) || isLoop(node)) {
1214
1318
  traversal.skip();
1215
1319
  return;
1216
1320
  }
1217
- if (Node7.isBreakStatement(node)) {
1321
+ if (Node8.isBreakStatement(node)) {
1218
1322
  throw new UnmodeledFlow("non-trailing break in switch clause");
1219
1323
  }
1220
1324
  });
1221
- if (Node7.isBreakStatement(stmt)) {
1325
+ if (Node8.isBreakStatement(stmt)) {
1222
1326
  throw new UnmodeledFlow("non-trailing break in switch clause");
1223
1327
  }
1224
1328
  }
1225
1329
  }
1226
- function lowerGroupBody(rawStmts, rawToStructured) {
1330
+ function lowerGroupBody(rawStmts, ctx) {
1227
1331
  const stmts = spliceBlocks(rawStmts);
1228
1332
  validateClauseBreaks(stmts);
1229
1333
  const last = stmts[stmts.length - 1];
1230
- const hasTrailingBreak = last !== void 0 && Node7.isBreakStatement(last);
1334
+ const hasTrailingBreak = last !== void 0 && Node8.isBreakStatement(last);
1231
1335
  const kept = hasTrailingBreak ? stmts.slice(0, -1) : stmts;
1232
- const body = lowerList(kept, rawToStructured);
1336
+ const body = lowerList(kept, ctx);
1233
1337
  if (hasTrailingBreak && last !== void 0) {
1234
- lowerStatement(last, rawToStructured);
1338
+ lowerStatement(last, ctx);
1235
1339
  }
1236
1340
  return { hasTrailingBreak, body };
1237
1341
  }
1238
- function lowerFunctionBody(body, terminalNodes) {
1342
+ function lowerFunctionBody(body, terminalNodes, func = body, barriers = NO_BARRIERS) {
1239
1343
  const rawToStructured = /* @__PURE__ */ new Map();
1240
- const statements = lowerList(body.getStatements(), rawToStructured);
1344
+ const ctx = { func, barriers, rawToStructured };
1345
+ const statements = lowerList(body.getStatements(), ctx);
1241
1346
  const terminalsByStmt = /* @__PURE__ */ new Map();
1242
1347
  const terminalHomeRaw = /* @__PURE__ */ new Map();
1243
1348
  for (const terminal of terminalNodes) {
@@ -1259,7 +1364,7 @@ function lowerFunctionBody(body, terminalNodes) {
1259
1364
  function enclosingLoweredStatement(terminal, body, rawToStructured) {
1260
1365
  let current2 = terminal;
1261
1366
  while (current2 !== void 0 && current2 !== body) {
1262
- if (Node7.isStatement(current2) && rawToStructured.has(current2)) {
1367
+ if (Node8.isStatement(current2) && rawToStructured.has(current2)) {
1263
1368
  return current2;
1264
1369
  }
1265
1370
  current2 = current2.getParent();
@@ -1268,39 +1373,42 @@ function enclosingLoweredStatement(terminal, body, rawToStructured) {
1268
1373
  }
1269
1374
 
1270
1375
  // src/paths/pathConditions.ts
1271
- function containsUnmodeledFlow(body) {
1376
+ function containsUnmodeledFlow(body, func, barriers) {
1272
1377
  let found = false;
1273
1378
  body.forEachDescendant((node, traversal) => {
1274
- if (isFunctionBoundary(node)) {
1379
+ if (isDescentStop(node, func, barriers)) {
1275
1380
  traversal.skip();
1276
1381
  return;
1277
1382
  }
1278
- if (Node8.isLabeledStatement(node)) {
1383
+ if (Node9.isLabeledStatement(node)) {
1279
1384
  found = true;
1280
1385
  traversal.stop();
1281
1386
  }
1282
1387
  });
1283
1388
  return found;
1284
1389
  }
1285
- function computePathConditions(func, terminalNodes) {
1390
+ function loweredExpressionBody(body, terminalNodes, func, barriers) {
1391
+ const statement = {
1392
+ kind: "opaque",
1393
+ exitKind: null,
1394
+ callbacks: lowerExpressionBodyCallbacks(body, func, barriers)
1395
+ };
1396
+ return {
1397
+ statements: [statement],
1398
+ terminalsByStmt: /* @__PURE__ */ new Map([[statement, terminalNodes]]),
1399
+ terminalHomeRaw: /* @__PURE__ */ new Map()
1400
+ };
1401
+ }
1402
+ function computePathConditions(func, terminalNodes, barriers = NO_BARRIERS) {
1286
1403
  const body = func.getBody();
1287
1404
  if (body === void 0) {
1288
1405
  return { byTerminal: /* @__PURE__ */ new Map(), fallthrough: [] };
1289
1406
  }
1290
- if (!Node8.isBlock(body)) {
1291
- const byTerminal = /* @__PURE__ */ new Map();
1292
- for (const terminal of terminalNodes) {
1293
- byTerminal.set(terminal, [
1294
- collectAncestorConditionInfosBelow(terminal, func)
1295
- ]);
1296
- }
1297
- return { byTerminal, fallthrough: [] };
1298
- }
1299
- if (containsUnmodeledFlow(body)) {
1407
+ if (containsUnmodeledFlow(body, func, barriers)) {
1300
1408
  return degradedResult(func, terminalNodes, "labeled statement");
1301
1409
  }
1302
1410
  try {
1303
- const lowered = lowerFunctionBody(body, terminalNodes);
1411
+ const lowered = Node9.isBlock(body) ? lowerFunctionBody(body, terminalNodes, func, barriers) : loweredExpressionBody(body, terminalNodes, func, barriers);
1304
1412
  const result = enumerateStructuredPaths({
1305
1413
  statements: lowered.statements,
1306
1414
  terminalsByStmt: lowered.terminalsByStmt
@@ -1367,7 +1475,7 @@ function lineAt(node, pos) {
1367
1475
  }
1368
1476
 
1369
1477
  // src/moduleExports.ts
1370
- import { Node as Node9 } from "ts-morph";
1478
+ import { Node as Node10 } from "ts-morph";
1371
1479
 
1372
1480
  // src/bootstrap/lazyProjectInit.ts
1373
1481
  import fs from "fs/promises";
@@ -1500,7 +1608,7 @@ var ModuleGraph = class {
1500
1608
  return created;
1501
1609
  }
1502
1610
  assert(relation, tuple) {
1503
- if (this.db.add(relation, tuple)) {
1611
+ if (this.db.add(relation, tuple) === "added") {
1504
1612
  this.stale = true;
1505
1613
  }
1506
1614
  }
@@ -1717,8 +1825,8 @@ function loadImportGraphsDepthFirstFromPaths(project, rootPaths) {
1717
1825
  stack.pop();
1718
1826
  loadOrder.push(top.path);
1719
1827
  let deepest = 0;
1720
- for (const path11 of top.deps) {
1721
- deepest = Math.max(deepest, chainDepth.get(path11) ?? 0);
1828
+ for (const path12 of top.deps) {
1829
+ deepest = Math.max(deepest, chainDepth.get(path12) ?? 0);
1722
1830
  }
1723
1831
  chainDepth.set(top.path, deepest + 1);
1724
1832
  }
@@ -1946,6 +2054,30 @@ async function readImportsConcurrently(paths) {
1946
2054
  return results;
1947
2055
  }
1948
2056
 
2057
+ // src/depTracking.ts
2058
+ function createDependencySink() {
2059
+ return { files: /* @__PURE__ */ new Set(), mountPrefixes: /* @__PURE__ */ new Map(), claims: [] };
2060
+ }
2061
+ var activeSink = null;
2062
+ function withDependencySink(sink, fn) {
2063
+ const previous = activeSink;
2064
+ activeSink = sink;
2065
+ try {
2066
+ return fn();
2067
+ } finally {
2068
+ activeSink = previous;
2069
+ }
2070
+ }
2071
+ function recordFileDependency(filePath) {
2072
+ activeSink?.files.add(filePath);
2073
+ }
2074
+ function recordMountPrefix(childId, prefix) {
2075
+ activeSink?.mountPrefixes.set(childId, prefix);
2076
+ }
2077
+ function recordUnitClaim(key, pack) {
2078
+ activeSink?.claims.push({ key, pack });
2079
+ }
2080
+
1949
2081
  // src/perFileCache.ts
1950
2082
  function createPerFileCache() {
1951
2083
  const byParse = /* @__PURE__ */ new WeakMap();
@@ -1961,6 +2093,7 @@ function createPerFileCache() {
1961
2093
 
1962
2094
  // src/moduleExports.ts
1963
2095
  function exportedDeclarationsOf(sourceFile) {
2096
+ recordFileDependency(sourceFile.getFilePath());
1964
2097
  const cached = exportsByFile.get(sourceFile);
1965
2098
  if (cached !== void 0) {
1966
2099
  return cached;
@@ -1975,7 +2108,14 @@ function resolveAliasedSymbol(symbol) {
1975
2108
  warmReExportChains(declaration.getSourceFile(), "every import");
1976
2109
  }
1977
2110
  try {
1978
- return symbol.getAliasedSymbol();
2111
+ const aliased = symbol.getAliasedSymbol();
2112
+ for (const declaration of aliased?.getDeclarations() ?? []) {
2113
+ const file = declaration.getSourceFile();
2114
+ if (!file.isInNodeModules()) {
2115
+ recordFileDependency(file.getFilePath());
2116
+ }
2117
+ }
2118
+ return aliased;
1979
2119
  } catch (error) {
1980
2120
  if (!(error instanceof RangeError)) {
1981
2121
  throw error;
@@ -2031,11 +2171,11 @@ function walkAndWarm(root, reach) {
2031
2171
  if (warmedFiles.get(target) !== void 0) {
2032
2172
  continue;
2033
2173
  }
2034
- const path11 = target.getFilePath();
2035
- if (visited.has(path11)) {
2174
+ const path12 = target.getFilePath();
2175
+ if (visited.has(path12)) {
2036
2176
  continue;
2037
2177
  }
2038
- visited.add(path11);
2178
+ visited.add(path12);
2039
2179
  stack.push(frameOf(target, reach));
2040
2180
  continue;
2041
2181
  }
@@ -2052,7 +2192,7 @@ function sitsOnWarmChains(file) {
2052
2192
  return false;
2053
2193
  }
2054
2194
  const imported = importedFilePathsOf(project, file.getFilePath());
2055
- return imported.length > 0 && imported.every((path11) => warmed.has(path11));
2195
+ return imported.length > 0 && imported.every((path12) => warmed.has(path12));
2056
2196
  }
2057
2197
  function markWarmed(file) {
2058
2198
  warmedFiles.set(file, true);
@@ -2090,7 +2230,7 @@ function reExportShapeOf(file, reach) {
2090
2230
  for (const assignment of file.getExportAssignments()) {
2091
2231
  exportNodes.push(assignment);
2092
2232
  const expression = assignment.getExpression();
2093
- if (Node9.isIdentifier(expression)) {
2233
+ if (Node10.isIdentifier(expression)) {
2094
2234
  localNames.add(expression.getText());
2095
2235
  }
2096
2236
  }
@@ -2166,38 +2306,6 @@ function reportUnreadableExports(file) {
2166
2306
  );
2167
2307
  }
2168
2308
 
2169
- // src/walk/descent.ts
2170
- import { Node as Node10 } from "ts-morph";
2171
- var NO_BARRIERS = /* @__PURE__ */ new Set();
2172
- function isDescentStop(node, func, barriers = NO_BARRIERS) {
2173
- if (node === func) {
2174
- return false;
2175
- }
2176
- if (Node10.isFunctionDeclaration(node) || Node10.isMethodDeclaration(node)) {
2177
- return true;
2178
- }
2179
- if (Node10.isArrowFunction(node) || Node10.isFunctionExpression(node)) {
2180
- return barriers.has(node);
2181
- }
2182
- return false;
2183
- }
2184
- function isModuleScopeStop(node) {
2185
- return Node10.isFunctionDeclaration(node) || Node10.isMethodDeclaration(node) || Node10.isArrowFunction(node) || Node10.isFunctionExpression(node) || Node10.isClassDeclaration(node) || Node10.isClassExpression(node);
2186
- }
2187
- function crossesNestedFunctionScope(node, func) {
2188
- let current2 = node;
2189
- while (current2 !== void 0 && current2 !== func) {
2190
- if (startsItsOwnScope(current2)) {
2191
- return true;
2192
- }
2193
- current2 = current2.getParent();
2194
- }
2195
- return false;
2196
- }
2197
- function startsItsOwnScope(node) {
2198
- return Node10.isArrowFunction(node) || Node10.isFunctionExpression(node) || Node10.isFunctionDeclaration(node) || Node10.isMethodDeclaration(node) || Node10.isGetAccessorDeclaration(node) || Node10.isSetAccessorDeclaration(node) || Node10.isConstructorDeclaration(node);
2199
- }
2200
-
2201
2309
  // src/resolve/invocationEffects.ts
2202
2310
  function isImportedFrom(identifier, expectedModule) {
2203
2311
  if (!Node11.isIdentifier(identifier)) {
@@ -2217,14 +2325,38 @@ function isImportedFrom(identifier, expectedModule) {
2217
2325
  (decl) => decl.getSourceFile().getFilePath().includes(`/node_modules/${expectedModule}/`)
2218
2326
  );
2219
2327
  }
2220
- function methodDeclaredIn(callee, expectedModule) {
2328
+ function methodDeclaredIn(callee, expectedModule, resolve2) {
2221
2329
  if (!Node11.isPropertyAccessExpression(callee)) {
2222
2330
  return false;
2223
2331
  }
2224
2332
  const declarations = callee.getNameNode().getSymbol()?.getDeclarations();
2225
- return declarations !== void 0 && declarations.some(
2333
+ if (declarations !== void 0 && declarations.some(
2226
2334
  (declaration) => declaration.getSourceFile().getFilePath().includes(`/node_modules/${expectedModule}/`)
2227
- );
2335
+ )) {
2336
+ return true;
2337
+ }
2338
+ if (resolve2 === void 0) {
2339
+ return false;
2340
+ }
2341
+ const written = resolve2(callee.getExpression());
2342
+ const made = maker(written);
2343
+ return made !== null && isImportedFrom(rootIdentifier(made), expectedModule);
2344
+ }
2345
+ function maker(written) {
2346
+ if (written === null) {
2347
+ return null;
2348
+ }
2349
+ if (Node11.isNewExpression(written) || Node11.isCallExpression(written)) {
2350
+ return written.getExpression();
2351
+ }
2352
+ return null;
2353
+ }
2354
+ function rootIdentifier(expression) {
2355
+ let root = expression;
2356
+ while (Node11.isPropertyAccessExpression(root)) {
2357
+ root = root.getExpression();
2358
+ }
2359
+ return root;
2228
2360
  }
2229
2361
  function importSpecifierMatches(decl, expectedModule) {
2230
2362
  if (Node11.isImportSpecifier(decl)) {
@@ -2304,6 +2436,26 @@ function extractInvocationEffects(func, barriers = NO_BARRIERS) {
2304
2436
  }
2305
2437
  return;
2306
2438
  }
2439
+ if (Node11.isReturnStatement(node)) {
2440
+ const returned = node.getExpression();
2441
+ const { call, async } = returned === void 0 ? { call: null, async: false } : unwrapCall(returned);
2442
+ if (call !== null) {
2443
+ const preconditions = collectPreconditions(node, func);
2444
+ results.push({
2445
+ effect: {
2446
+ type: "invocation",
2447
+ callee: call.getExpression().getText(),
2448
+ args: extractArgs(call),
2449
+ async,
2450
+ ...preconditions.length > 0 ? { preconditions } : {}
2451
+ },
2452
+ line: startLineOf(node),
2453
+ neverTerminal: false,
2454
+ node: call
2455
+ });
2456
+ }
2457
+ return;
2458
+ }
2307
2459
  if (Node11.isCallExpression(node)) {
2308
2460
  const parent = node.getParent();
2309
2461
  if (parent === void 0) {
@@ -4909,7 +5061,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4909
5061
  ];
4910
5062
  const { byTerminal } = computePathConditions(
4911
5063
  func,
4912
- terminals.map(({ node }) => node)
5064
+ terminals.map(({ node }) => node),
5065
+ barriers
4913
5066
  );
4914
5067
  const wantsFallthrough = terminalPatterns.some(
4915
5068
  (p) => p.match.type === "functionFallthrough"
@@ -4921,7 +5074,11 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4921
5074
  if (!hasDefaultTerminal) {
4922
5075
  const synthetic = makeFallthroughTerminal(func);
4923
5076
  terminals.push(synthetic);
4924
- const pathResult = computePathConditions(func, [synthetic.node]);
5077
+ const pathResult = computePathConditions(
5078
+ func,
5079
+ [synthetic.node],
5080
+ barriers
5081
+ );
4925
5082
  byTerminal.set(
4926
5083
  synthetic.node,
4927
5084
  pathResult.fallthrough.length > 0 ? pathResult.fallthrough : [[]]
@@ -4967,7 +5124,10 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4967
5124
  const terminalLines = new Set(
4968
5125
  distinctBranches.map((b) => b.terminal.location.start)
4969
5126
  );
4970
- defaultBranch.effects = invocations.filter((i) => i.neverTerminal || !terminalLines.has(i.line)).map((i) => i.effect);
5127
+ const terminalNodes = new Set(terminals.map(({ node }) => node));
5128
+ defaultBranch.effects = invocations.filter(
5129
+ (i) => i.node !== void 0 ? !terminalNodes.has(i.node) : i.neverTerminal || !terminalLines.has(i.line)
5130
+ ).map((i) => i.effect);
4971
5131
  }
4972
5132
  }
4973
5133
  if (recognized.length > 0) {
@@ -5012,9 +5172,9 @@ function createSourceFileLookup(project) {
5012
5172
  if (sf === null) {
5013
5173
  return null;
5014
5174
  }
5015
- const path11 = sf.getFilePath();
5016
- const index = functionsByFile.get(path11) ?? indexFunctions(sf);
5017
- functionsByFile.set(path11, index);
5175
+ const path12 = sf.getFilePath();
5176
+ const index = functionsByFile.get(path12) ?? indexFunctions(sf);
5177
+ functionsByFile.set(path12, index);
5018
5178
  return index.get(rangeKey(location.range)) ?? null;
5019
5179
  }
5020
5180
  };
@@ -5052,7 +5212,7 @@ function indexFunctions(sf) {
5052
5212
  import { createHash as createHash2 } from "crypto";
5053
5213
  import fs2 from "fs/promises";
5054
5214
  import path2 from "path";
5055
- var SCHEMA_VERSION = "5";
5215
+ var SCHEMA_VERSION = "6";
5056
5216
  var MAX_ENTRIES = 2;
5057
5217
  var ENTRY_PREFIX = "key-";
5058
5218
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
@@ -5064,6 +5224,7 @@ function createCacheLayer(cacheDir) {
5064
5224
  kind: "miss",
5065
5225
  diagnostic: { kind: "miss", missReason: "no-manifest" }
5066
5226
  }),
5227
+ plan: async () => null,
5067
5228
  write: async () => {
5068
5229
  }
5069
5230
  };
@@ -5094,17 +5255,32 @@ function createCacheLayer(cacheDir) {
5094
5255
  diagnostic: { kind: "hit" }
5095
5256
  };
5096
5257
  },
5097
- async write(input, summaries) {
5258
+ async plan(input) {
5259
+ const entryDir = entryDirFor(cacheDir, input);
5260
+ const manifest = await readManifest(path2.join(entryDir, "manifest.json"));
5261
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
5262
+ return null;
5263
+ }
5264
+ const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
5265
+ if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
5266
+ return null;
5267
+ }
5268
+ await markUsed(entryDir);
5269
+ return buildPlan(manifest, await resolveFileStamps(input));
5270
+ },
5271
+ async write(input, summaries, attribution) {
5272
+ const entryDir = entryDirFor(cacheDir, input);
5273
+ const previous = await readManifest(path2.join(entryDir, "manifest.json"));
5098
5274
  const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
5099
- const files = await resolveFileStamps(input);
5275
+ const files = await hashStamps(await resolveFileStamps(input), previous);
5100
5276
  const manifest = {
5101
5277
  schemaVersion: SCHEMA_VERSION,
5102
5278
  adapterPacksDigest: input.adapterPacksDigest,
5103
5279
  tsconfigStamp,
5104
5280
  files,
5105
- summaries
5281
+ summaries,
5282
+ ...attribution === void 0 ? {} : encodeAttribution(attribution)
5106
5283
  };
5107
- const entryDir = entryDirFor(cacheDir, input);
5108
5284
  await fs2.mkdir(entryDir, { recursive: true });
5109
5285
  await fs2.writeFile(
5110
5286
  path2.join(entryDir, "manifest.json"),
@@ -5114,6 +5290,163 @@ function createCacheLayer(cacheDir) {
5114
5290
  }
5115
5291
  };
5116
5292
  }
5293
+ async function buildPlan(manifest, currentStamps) {
5294
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
5295
+ const current2 = new Map(currentStamps.map((f) => [f.path, f]));
5296
+ const changed = /* @__PURE__ */ new Set();
5297
+ const removed = /* @__PURE__ */ new Set();
5298
+ for (const p of stored.keys()) {
5299
+ if (!current2.has(p)) {
5300
+ removed.add(p);
5301
+ }
5302
+ }
5303
+ const toVerify = [];
5304
+ for (const [p, stamp] of current2) {
5305
+ const before = stored.get(p);
5306
+ if (before === void 0) {
5307
+ changed.add(p);
5308
+ } else if (!fileStampEquals(before, stamp)) {
5309
+ if (before.contentHash === void 0) {
5310
+ changed.add(p);
5311
+ } else {
5312
+ toVerify.push(p);
5313
+ }
5314
+ }
5315
+ }
5316
+ await Promise.all(
5317
+ toVerify.map(async (p) => {
5318
+ const hash = await hashFile(p);
5319
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
5320
+ changed.add(p);
5321
+ }
5322
+ })
5323
+ );
5324
+ const roots = /* @__PURE__ */ new Map();
5325
+ const rootNames = manifest.roots ?? [];
5326
+ const depPaths = manifest.depPaths ?? [];
5327
+ let rootsDeclined = 0;
5328
+ rootNames.forEach((rootPath, i) => {
5329
+ const meta = manifest.rootMeta?.[i];
5330
+ if (meta === void 0) {
5331
+ return;
5332
+ }
5333
+ if (!meta.cacheable) {
5334
+ rootsDeclined += 1;
5335
+ }
5336
+ roots.set(rootPath, {
5337
+ path: rootPath,
5338
+ cacheable: meta.cacheable,
5339
+ deps: meta.deps.flatMap((d) => {
5340
+ const p = depPaths[d];
5341
+ return p === void 0 ? [] : [p];
5342
+ }),
5343
+ claims: meta.claims,
5344
+ mountPrefixes: meta.mountPrefixes,
5345
+ packs: meta.packs
5346
+ });
5347
+ });
5348
+ const validRoots = /* @__PURE__ */ new Set();
5349
+ for (const [rootPath, record] of roots) {
5350
+ if (!record.cacheable || !current2.has(rootPath)) {
5351
+ continue;
5352
+ }
5353
+ if (changed.has(rootPath)) {
5354
+ continue;
5355
+ }
5356
+ const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
5357
+ if (!depMoved) {
5358
+ validRoots.add(rootPath);
5359
+ }
5360
+ }
5361
+ const owners = manifest.owners ?? [];
5362
+ return {
5363
+ changed,
5364
+ removed,
5365
+ roots,
5366
+ validRoots,
5367
+ rootsDeclined,
5368
+ reuse(valid) {
5369
+ const summaries = [];
5370
+ const reusedOwners = [];
5371
+ manifest.summaries.forEach((summary, i) => {
5372
+ const ownerPaths = (owners[i] ?? []).flatMap((o) => {
5373
+ const p = rootNames[o];
5374
+ return p === void 0 ? [] : [p];
5375
+ });
5376
+ if (ownerPaths.some((p) => valid.has(p))) {
5377
+ summaries.push(summary);
5378
+ reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
5379
+ }
5380
+ });
5381
+ return { summaries, owners: reusedOwners };
5382
+ },
5383
+ allSummaries() {
5384
+ return manifest.summaries;
5385
+ },
5386
+ attribution() {
5387
+ return {
5388
+ roots: [...roots.values()],
5389
+ owners: owners.map(
5390
+ (ownerIds) => ownerIds.flatMap((o) => {
5391
+ const p = rootNames[o];
5392
+ return p === void 0 ? [] : [p];
5393
+ })
5394
+ )
5395
+ };
5396
+ }
5397
+ };
5398
+ }
5399
+ function encodeAttribution(attribution) {
5400
+ const roots = attribution.roots.map((r) => r.path);
5401
+ const rootIndex = new Map(roots.map((p, i) => [p, i]));
5402
+ const depIndex = /* @__PURE__ */ new Map();
5403
+ const depPaths = [];
5404
+ const depIdOf = (p) => {
5405
+ const existing = depIndex.get(p);
5406
+ if (existing !== void 0) {
5407
+ return existing;
5408
+ }
5409
+ const id = depPaths.length;
5410
+ depPaths.push(p);
5411
+ depIndex.set(p, id);
5412
+ return id;
5413
+ };
5414
+ const rootMeta = attribution.roots.map((r) => ({
5415
+ cacheable: r.cacheable,
5416
+ deps: r.deps.map(depIdOf),
5417
+ claims: r.claims,
5418
+ mountPrefixes: r.mountPrefixes,
5419
+ packs: r.packs
5420
+ }));
5421
+ const owners = attribution.owners.map(
5422
+ (ownerPaths) => ownerPaths.flatMap((p) => {
5423
+ const i = rootIndex.get(p);
5424
+ return i === void 0 ? [] : [i];
5425
+ })
5426
+ );
5427
+ return { roots, rootMeta, depPaths, owners };
5428
+ }
5429
+ async function hashStamps(stamps, previous) {
5430
+ const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
5431
+ return Promise.all(
5432
+ stamps.map(async (stamp) => {
5433
+ const prior = before.get(stamp.path);
5434
+ if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
5435
+ return { ...stamp, contentHash: prior.contentHash };
5436
+ }
5437
+ const contentHash = await hashFile(stamp.path);
5438
+ return contentHash === null ? stamp : { ...stamp, contentHash };
5439
+ })
5440
+ );
5441
+ }
5442
+ async function hashFile(filePath) {
5443
+ try {
5444
+ const content = await fs2.readFile(filePath);
5445
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
5446
+ } catch {
5447
+ return null;
5448
+ }
5449
+ }
5117
5450
  async function describeAbsentEntry(cacheDir) {
5118
5451
  try {
5119
5452
  const entries = await fs2.readdir(cacheDir, { withFileTypes: true });
@@ -5127,7 +5460,7 @@ function entryDirFor(cacheDir, input) {
5127
5460
  SCHEMA_VERSION,
5128
5461
  input.adapterPacksDigest,
5129
5462
  input.tsconfigPath ?? ""
5130
- ].join("\0");
5463
+ ].join(" ");
5131
5464
  const name = createHash2("sha256").update(key).digest("hex").slice(0, 16);
5132
5465
  return path2.join(cacheDir, `${ENTRY_PREFIX}${name}`);
5133
5466
  }
@@ -5368,7 +5701,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5368
5701
  }
5369
5702
  const responses = [];
5370
5703
  let method;
5371
- let path11;
5704
+ let path12;
5372
5705
  for (const prop of endpointNode.getProperties()) {
5373
5706
  if (!Node24.isPropertyAssignment(prop)) {
5374
5707
  continue;
@@ -5383,7 +5716,7 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5383
5716
  if (propName === pattern.pathProperty) {
5384
5717
  const val = prop.getInitializer();
5385
5718
  if (val !== void 0 && Node24.isStringLiteral(val)) {
5386
- path11 = val.getLiteralValue();
5719
+ path12 = val.getLiteralValue();
5387
5720
  }
5388
5721
  }
5389
5722
  if (propName === pattern.responseExtraction.property) {
@@ -5407,10 +5740,10 @@ function extractEndpointContract(endpointNode, pattern, framework) {
5407
5740
  if (responses.length === 0) {
5408
5741
  return null;
5409
5742
  }
5410
- const boundaryBinding = method !== void 0 || path11 !== void 0 ? restBinding({
5743
+ const boundaryBinding = method !== void 0 || path12 !== void 0 ? restBinding({
5411
5744
  transport: "http",
5412
5745
  method: method ?? null,
5413
- path: path11 ?? null,
5746
+ path: path12 ?? null,
5414
5747
  recognition: framework
5415
5748
  }) : null;
5416
5749
  return {
@@ -6001,6 +6334,13 @@ function literalIndexOf(index) {
6001
6334
  }
6002
6335
  return null;
6003
6336
  }
6337
+ function asFallbackExpression(expression) {
6338
+ if (!Node26.isBinaryExpression(expression)) {
6339
+ return null;
6340
+ }
6341
+ const operator = expression.getOperatorToken().getKind();
6342
+ return operator === SyntaxKind6.BarBarToken || operator === SyntaxKind6.QuestionQuestionToken ? expression : null;
6343
+ }
6004
6344
  function unwrapExpression(expression) {
6005
6345
  let current2 = expression;
6006
6346
  for (; ; ) {
@@ -6112,6 +6452,12 @@ function emitValue(db, table, value) {
6112
6452
  }
6113
6453
  return id;
6114
6454
  }
6455
+ const fallback = asFallbackExpression(expression);
6456
+ if (fallback !== null) {
6457
+ fact(db, "fallbackBranch", id, emitValue(db, table, fallback.getLeft()));
6458
+ fact(db, "fallbackBranch", id, emitValue(db, table, fallback.getRight()));
6459
+ return id;
6460
+ }
6115
6461
  if (Node26.isCallExpression(expression) || Node26.isNewExpression(expression)) {
6116
6462
  emitCallFacts(db, table, expression);
6117
6463
  fact(db, "writtenValue", id);
@@ -6576,24 +6922,27 @@ function stampGraphqlClientRefs(summaries, sourceFiles, packs, resolution) {
6576
6922
  const sole = soleGraphqlClientRef(
6577
6923
  collectGraphqlClientRefs(sourceFiles, packs, resolution)
6578
6924
  );
6579
- if (sole === null && scopes.length === 0) {
6580
- return;
6581
- }
6582
6925
  const workspace = sole !== null ? boundWorkspaceFor(sole, packs) : null;
6583
6926
  const soleClient = sole !== null ? workspace !== null ? { ...sole, workspace } : sole : null;
6927
+ let registry = null;
6584
6928
  for (const summary of summaries) {
6585
6929
  if (!isGraphqlOperationBinding(summary.identity.boundaryBinding)) {
6586
6930
  continue;
6587
6931
  }
6932
+ const existing = readGraphqlMetadata(summary) ?? {};
6588
6933
  const scoped = scopedWorkspaceFor(summary.location.file, scopes);
6589
6934
  const client = scoped !== null ? { uri: null, uriRef: null, workspace: scoped } : soleClient;
6590
- if (client === null) {
6935
+ const dangling = (existing.unresolvedFragments?.length ?? 0) > 0;
6936
+ if (client === null && !dangling) {
6591
6937
  continue;
6592
6938
  }
6593
- const existing = readGraphqlMetadata(summary) ?? {};
6939
+ if (dangling && registry === null) {
6940
+ registry = fragmentRegistryStatus(sourceFiles, packs);
6941
+ }
6594
6942
  summary.metadata = withGraphqlMetadata(summary.metadata, {
6595
6943
  ...existing,
6596
- client
6944
+ ...client !== null ? { client } : {},
6945
+ ...dangling && registry !== null ? { fragmentRegistry: registry } : {}
6597
6946
  });
6598
6947
  }
6599
6948
  }
@@ -6659,47 +7008,197 @@ function soleGraphqlClientRef(refs) {
6659
7008
  }
6660
7009
  return [...distinct.values()][0] ?? null;
6661
7010
  }
6662
- function localNamesFor(sourceFile, specs) {
6663
- const names = /* @__PURE__ */ new Map();
6664
- for (const importDecl of sourceFile.getImportDeclarations()) {
6665
- const module = importDecl.getModuleSpecifierValue();
6666
- for (const spec of specs) {
6667
- if (module !== spec.importModule) {
7011
+ function fragmentRegistryStatus(sourceFiles, packs) {
7012
+ const statuses = [];
7013
+ for (const spec of packs.flatMap((pack) => pack.graphqlClients ?? [])) {
7014
+ const registrySpec = spec.fragmentRegistry;
7015
+ if (registrySpec === void 0) {
7016
+ continue;
7017
+ }
7018
+ for (const sourceFile of sourceFiles) {
7019
+ const local = localImportName(
7020
+ sourceFile,
7021
+ spec.importModule,
7022
+ spec.importName
7023
+ );
7024
+ if (local === null) {
6668
7025
  continue;
6669
7026
  }
6670
- for (const named2 of importDecl.getNamedImports()) {
6671
- if (named2.getName() !== spec.importName) {
6672
- continue;
7027
+ sourceFile.forEachDescendant((node) => {
7028
+ if (isConstructionNamed(node, local)) {
7029
+ statuses.push(constructionRegistryStatus(node, registrySpec));
6673
7030
  }
6674
- const local = named2.getAliasNode()?.getText() ?? named2.getName();
6675
- names.set(local, spec.uriProperty);
6676
- }
7031
+ });
6677
7032
  }
6678
7033
  }
6679
- return names;
7034
+ return combineRegistryStatuses(statuses);
6680
7035
  }
6681
- function constructionRef(node, localNames, resolution) {
6682
- if (!Node28.isNewExpression(node) && !Node28.isCallExpression(node)) {
6683
- return null;
7036
+ function combineRegistryStatuses(statuses) {
7037
+ if (statuses.includes("configured")) {
7038
+ return "configured";
6684
7039
  }
6685
- const callee = node.getExpression();
6686
- if (callee === void 0 || !Node28.isIdentifier(callee)) {
6687
- return null;
7040
+ if (statuses.length === 0 || statuses.includes("unknown")) {
7041
+ return "unknown";
6688
7042
  }
6689
- const uriProperty = localNames.get(callee.getText());
6690
- if (uriProperty === void 0) {
6691
- return null;
7043
+ return "absent";
7044
+ }
7045
+ function constructionRegistryStatus(construction, spec) {
7046
+ const options = firstArgumentObjectLiteral(construction);
7047
+ if (options === null) {
7048
+ return "unknown";
6692
7049
  }
6693
- const optionsArg = node.getArguments()[0];
6694
- if (optionsArg === void 0 || !Node28.isObjectLiteralExpression(optionsArg)) {
6695
- return null;
7050
+ const cacheExpr = propertyValueOf2(options, spec.cacheProperty);
7051
+ if (cacheExpr === null) {
7052
+ return "unknown";
6696
7053
  }
6697
- const property = optionsArg.getProperty(uriProperty);
6698
- if (property === void 0 || !Node28.isPropertyAssignment(property)) {
7054
+ const cacheConstruction = resolveToConstructionOf(
7055
+ cacheExpr,
7056
+ spec.cacheConstructor
7057
+ );
7058
+ if (cacheConstruction === null) {
7059
+ return "unknown";
7060
+ }
7061
+ if (!Node28.isNewExpression(cacheConstruction) && !Node28.isCallExpression(cacheConstruction)) {
7062
+ return "unknown";
7063
+ }
7064
+ const args = cacheConstruction.getArguments();
7065
+ if (args.length === 0) {
7066
+ return "absent";
7067
+ }
7068
+ const cacheOptions = firstArgumentObjectLiteral(cacheConstruction);
7069
+ if (cacheOptions === null) {
7070
+ return "unknown";
7071
+ }
7072
+ if (cacheOptions.getProperty(spec.registryProperty) !== void 0) {
7073
+ return "configured";
7074
+ }
7075
+ const hasSpread = cacheOptions.getProperties().some((property) => Node28.isSpreadAssignment(property));
7076
+ return hasSpread ? "unknown" : "absent";
7077
+ }
7078
+ function firstArgumentObjectLiteral(construction) {
7079
+ if (!Node28.isNewExpression(construction) && !Node28.isCallExpression(construction)) {
6699
7080
  return null;
6700
7081
  }
6701
- const value = property.getInitializer();
6702
- if (value === void 0) {
7082
+ const arg = construction.getArguments()[0];
7083
+ if (arg === void 0 || !Node28.isObjectLiteralExpression(arg)) {
7084
+ return null;
7085
+ }
7086
+ return arg;
7087
+ }
7088
+ function propertyValueOf2(options, name) {
7089
+ const property = options.getProperty(name);
7090
+ if (property === void 0) {
7091
+ return null;
7092
+ }
7093
+ if (Node28.isPropertyAssignment(property)) {
7094
+ return property.getInitializer() ?? null;
7095
+ }
7096
+ if (Node28.isShorthandPropertyAssignment(property)) {
7097
+ const symbol = property.getProject().getTypeChecker().getShorthandAssignmentValueSymbol(property);
7098
+ return symbol?.getValueDeclaration() ?? null;
7099
+ }
7100
+ return null;
7101
+ }
7102
+ function resolveToConstructionOf(expression, cacheClass) {
7103
+ let current2 = expression;
7104
+ for (let hop = 0; hop < 4 && current2 !== void 0; hop += 1) {
7105
+ current2 = unwrapExpression2(current2);
7106
+ if (Node28.isVariableDeclaration(current2)) {
7107
+ current2 = current2.getInitializer();
7108
+ continue;
7109
+ }
7110
+ if (Node28.isNewExpression(current2) || Node28.isCallExpression(current2)) {
7111
+ const callee = current2.getExpression();
7112
+ if (callee === void 0 || !Node28.isIdentifier(callee)) {
7113
+ return null;
7114
+ }
7115
+ const local = localImportName(
7116
+ current2.getSourceFile(),
7117
+ cacheClass.importModule,
7118
+ cacheClass.importName
7119
+ );
7120
+ return local !== null && callee.getText() === local ? current2 : null;
7121
+ }
7122
+ if (!Node28.isIdentifier(current2)) {
7123
+ return null;
7124
+ }
7125
+ const declaration = current2.getSymbol()?.getValueDeclaration();
7126
+ if (declaration === void 0 || !Node28.isVariableDeclaration(declaration)) {
7127
+ return null;
7128
+ }
7129
+ current2 = declaration.getInitializer();
7130
+ }
7131
+ return null;
7132
+ }
7133
+ function unwrapExpression2(node) {
7134
+ let current2 = node;
7135
+ while (Node28.isParenthesizedExpression(current2) || Node28.isAsExpression(current2) || Node28.isNonNullExpression(current2)) {
7136
+ current2 = current2.getExpression();
7137
+ }
7138
+ return current2;
7139
+ }
7140
+ function isConstructionNamed(node, localName) {
7141
+ if (!Node28.isNewExpression(node) && !Node28.isCallExpression(node)) {
7142
+ return false;
7143
+ }
7144
+ const callee = node.getExpression();
7145
+ return callee !== void 0 && Node28.isIdentifier(callee) && callee.getText() === localName;
7146
+ }
7147
+ function localImportName(sourceFile, importModule, importName) {
7148
+ for (const importDecl of sourceFile.getImportDeclarations()) {
7149
+ const module = importDecl.getModuleSpecifierValue();
7150
+ if (module !== importModule && !module.startsWith(`${importModule}/`)) {
7151
+ continue;
7152
+ }
7153
+ for (const named2 of importDecl.getNamedImports()) {
7154
+ if (named2.getName() === importName) {
7155
+ return named2.getAliasNode()?.getText() ?? named2.getName();
7156
+ }
7157
+ }
7158
+ }
7159
+ return null;
7160
+ }
7161
+ function localNamesFor(sourceFile, specs) {
7162
+ const names = /* @__PURE__ */ new Map();
7163
+ for (const importDecl of sourceFile.getImportDeclarations()) {
7164
+ const module = importDecl.getModuleSpecifierValue();
7165
+ for (const spec of specs) {
7166
+ if (module !== spec.importModule) {
7167
+ continue;
7168
+ }
7169
+ for (const named2 of importDecl.getNamedImports()) {
7170
+ if (named2.getName() !== spec.importName) {
7171
+ continue;
7172
+ }
7173
+ const local = named2.getAliasNode()?.getText() ?? named2.getName();
7174
+ names.set(local, spec.uriProperty);
7175
+ }
7176
+ }
7177
+ }
7178
+ return names;
7179
+ }
7180
+ function constructionRef(node, localNames, resolution) {
7181
+ if (!Node28.isNewExpression(node) && !Node28.isCallExpression(node)) {
7182
+ return null;
7183
+ }
7184
+ const callee = node.getExpression();
7185
+ if (callee === void 0 || !Node28.isIdentifier(callee)) {
7186
+ return null;
7187
+ }
7188
+ const uriProperty = localNames.get(callee.getText());
7189
+ if (uriProperty === void 0) {
7190
+ return null;
7191
+ }
7192
+ const optionsArg = node.getArguments()[0];
7193
+ if (optionsArg === void 0 || !Node28.isObjectLiteralExpression(optionsArg)) {
7194
+ return null;
7195
+ }
7196
+ const property = optionsArg.getProperty(uriProperty);
7197
+ if (property === void 0 || !Node28.isPropertyAssignment(property)) {
7198
+ return null;
7199
+ }
7200
+ const value = property.getInitializer();
7201
+ if (value === void 0) {
6703
7202
  return null;
6704
7203
  }
6705
7204
  const literal = stringValueOf(value, resolution);
@@ -6986,7 +7485,7 @@ function calleeOf(decorator) {
6986
7485
 
6987
7486
  // src/discovery/decoratedMethod.ts
6988
7487
  function resolverTypeName(args) {
6989
- return args.classTypeName ?? args.typeMap[args.decoratorName] ?? null;
7488
+ return args.typeMap[args.decoratorName] ?? args.classTypeName ?? null;
6990
7489
  }
6991
7490
  function resolveResolverClassTypeName(decoratorArg) {
6992
7491
  if (Node32.isArrowFunction(decoratorArg)) {
@@ -7406,32 +7905,163 @@ function innerTemplateText(template) {
7406
7905
  }
7407
7906
  return raw;
7408
7907
  }
7409
- function resolveGraphqlDocument(arg, resolution) {
7410
- const stripped = stripDocumentNodeCasts(arg);
7411
- const inline = documentTextFromExpression(stripped);
7908
+ function startAssembly(resolution) {
7909
+ return {
7910
+ resolution,
7911
+ seen: /* @__PURE__ */ new Set(),
7912
+ unresolvedInterpolations: [],
7913
+ unresolvedInsideSelection: false
7914
+ };
7915
+ }
7916
+ function assembledTemplateText(template, assembly) {
7917
+ if (Node35.isNoSubstitutionTemplateLiteral(template)) {
7918
+ return innerTemplateText(template);
7919
+ }
7920
+ if (!Node35.isTemplateExpression(template)) {
7921
+ return null;
7922
+ }
7923
+ let text = template.getHead().getLiteralText();
7924
+ for (const span of template.getTemplateSpans()) {
7925
+ const spliced = interpolatedDocumentText(span.getExpression(), assembly);
7926
+ if (spliced === null) {
7927
+ assembly.unresolvedInterpolations.push(
7928
+ singleLine(span.getExpression().getText())
7929
+ );
7930
+ if (openBraceDepth(text) > 0) {
7931
+ assembly.unresolvedInsideSelection = true;
7932
+ }
7933
+ } else {
7934
+ text += `
7935
+ ${spliced}
7936
+ `;
7937
+ }
7938
+ text += span.getLiteral().getLiteralText();
7939
+ }
7940
+ return text;
7941
+ }
7942
+ function openBraceDepth(text) {
7943
+ let depth = 0;
7944
+ for (const char of text) {
7945
+ if (char === "{") {
7946
+ depth += 1;
7947
+ } else if (char === "}") {
7948
+ depth -= 1;
7949
+ }
7950
+ }
7951
+ return depth;
7952
+ }
7953
+ function interpolatedDocumentText(expr, assembly) {
7954
+ const stripped = stripDocumentNodeCasts(expr);
7955
+ const inline = documentTextFromExpression(stripped, assembly);
7412
7956
  if (inline !== null) {
7413
- return { document: inline };
7957
+ return inline;
7414
7958
  }
7415
- const templateText = resolveGqlTemplateText(stripped);
7416
- if (templateText !== null) {
7417
- return { document: templateText };
7959
+ const named2 = resolveGqlTemplateText(stripped, assembly);
7960
+ if (named2 !== null) {
7961
+ return named2;
7418
7962
  }
7419
7963
  const objectDoc = resolveTypedDocumentSource(stripped);
7420
7964
  if (objectDoc !== null) {
7421
- return { document: objectDoc };
7965
+ return objectDoc;
7422
7966
  }
7423
- const throughFacts = resolveThroughFacts(stripped, resolution);
7424
- if (throughFacts !== null) {
7425
- return { document: throughFacts };
7967
+ return resolveThroughFacts(stripped, assembly);
7968
+ }
7969
+ function resolveGraphqlDocument(arg, resolution) {
7970
+ const assembly = startAssembly(resolution);
7971
+ const stripped = stripDocumentNodeCasts(arg);
7972
+ const text = documentTextFromExpression(stripped, assembly) ?? resolveGqlTemplateText(stripped, assembly) ?? resolveTypedDocumentSource(stripped) ?? resolveThroughFacts(stripped, assembly);
7973
+ if (text !== null) {
7974
+ return assembledDocumentResolution(text, stripped, assembly);
7426
7975
  }
7427
7976
  return resolveTypedDocumentHeader(stripped);
7428
7977
  }
7429
- function documentTextFromExpression(node) {
7978
+ function assembledDocumentResolution(text, arg, assembly) {
7979
+ const interpolations = assembly.unresolvedInterpolations;
7980
+ if (assembly.unresolvedInsideSelection) {
7981
+ const header = parseGraphqlOperation(text);
7982
+ return {
7983
+ ...header !== null ? { operationType: header.operationType } : {},
7984
+ ...header?.operationName !== void 0 ? { operationName: header.operationName } : {},
7985
+ unresolved: {
7986
+ reference: singleLine(arg.getText()),
7987
+ reason: `interpolated ${describeInterpolations(interpolations)} inside a selection set did not resolve to a GraphQL document, so what the operation selects is not statically written`
7988
+ }
7989
+ };
7990
+ }
7991
+ const dangling = danglingFragmentSpreads(text);
7992
+ if (dangling === null) {
7993
+ if (interpolations.length === 0) {
7994
+ return { document: text };
7995
+ }
7996
+ return {
7997
+ unresolved: {
7998
+ reference: singleLine(arg.getText()),
7999
+ reason: `the document did not parse after dropping interpolated ${describeInterpolations(interpolations)} that resolved to no GraphQL document`
8000
+ }
8001
+ };
8002
+ }
8003
+ if (dangling.length > 0) {
8004
+ return { document: text, unresolvedFragments: dangling };
8005
+ }
8006
+ if (interpolations.length > 0) {
8007
+ return {
8008
+ document: text,
8009
+ unresolved: {
8010
+ reference: singleLine(arg.getText()),
8011
+ reason: `interpolated ${describeInterpolations(interpolations)} did not resolve to a GraphQL document, so whatever it contributes is not part of the stored document`
8012
+ }
8013
+ };
8014
+ }
8015
+ return { document: text };
8016
+ }
8017
+ function describeInterpolations(interpolations) {
8018
+ const quoted = interpolations.map((text) => `\`${text}\``).join(", ");
8019
+ return interpolations.length === 1 ? `expression ${quoted}` : `expressions ${quoted}`;
8020
+ }
8021
+ function danglingFragmentSpreads(text) {
8022
+ let doc;
8023
+ try {
8024
+ doc = graphqlParse(text);
8025
+ } catch {
8026
+ return null;
8027
+ }
8028
+ const defined = /* @__PURE__ */ new Set();
8029
+ for (const def of doc.definitions) {
8030
+ if (def.kind === GraphqlKind.FRAGMENT_DEFINITION) {
8031
+ defined.add(def.name.value);
8032
+ }
8033
+ }
8034
+ const dangling = /* @__PURE__ */ new Set();
8035
+ for (const def of doc.definitions) {
8036
+ if (def.kind === GraphqlKind.OPERATION_DEFINITION || def.kind === GraphqlKind.FRAGMENT_DEFINITION) {
8037
+ collectDanglingSpreads(def.selectionSet, defined, dangling);
8038
+ }
8039
+ }
8040
+ return [...dangling].sort();
8041
+ }
8042
+ function collectDanglingSpreads(selectionSet, defined, dangling) {
8043
+ for (const selection of selectionSet.selections) {
8044
+ if (selection.kind === GraphqlKind.FRAGMENT_SPREAD) {
8045
+ if (!defined.has(selection.name.value)) {
8046
+ dangling.add(selection.name.value);
8047
+ }
8048
+ continue;
8049
+ }
8050
+ if (selection.selectionSet !== void 0) {
8051
+ collectDanglingSpreads(selection.selectionSet, defined, dangling);
8052
+ }
8053
+ }
8054
+ }
8055
+ function documentTextFromExpression(node, assembly) {
7430
8056
  if (Node35.isTaggedTemplateExpression(node)) {
7431
8057
  if (!isDocumentTag(node.getTag())) {
7432
8058
  return null;
7433
8059
  }
7434
- return innerTemplateText(node.getTemplate());
8060
+ if (assembly.seen.has(node)) {
8061
+ return "";
8062
+ }
8063
+ assembly.seen.add(node);
8064
+ return assembledTemplateText(node.getTemplate(), assembly);
7435
8065
  }
7436
8066
  if (!Node35.isCallExpression(node)) {
7437
8067
  return null;
@@ -7447,7 +8077,11 @@ function documentTextFromExpression(node) {
7447
8077
  if (!Node35.isNoSubstitutionTemplateLiteral(template) && !Node35.isTemplateExpression(template)) {
7448
8078
  return null;
7449
8079
  }
7450
- return innerTemplateText(template);
8080
+ if (assembly.seen.has(node)) {
8081
+ return "";
8082
+ }
8083
+ assembly.seen.add(node);
8084
+ return assembledTemplateText(template, assembly);
7451
8085
  }
7452
8086
  function isDocumentTag(tag) {
7453
8087
  if (!Node35.isIdentifier(tag)) {
@@ -7467,15 +8101,15 @@ function isDocumentTag(tag) {
7467
8101
  }
7468
8102
  return false;
7469
8103
  }
7470
- function resolveThroughFacts(arg, resolution) {
7471
- if (resolution === void 0 || !Node35.isIdentifier(arg)) {
8104
+ function resolveThroughFacts(arg, assembly) {
8105
+ if (assembly.resolution === void 0 || !Node35.isIdentifier(arg)) {
7472
8106
  return null;
7473
8107
  }
7474
- const written = resolution.resolveWrittenValue(arg);
8108
+ const written = assembly.resolution.resolveWrittenValue(arg);
7475
8109
  if (written === null) {
7476
8110
  return null;
7477
8111
  }
7478
- const text = documentTextFromExpression(written);
8112
+ const text = documentTextFromExpression(written, assembly);
7479
8113
  if (text !== null) {
7480
8114
  return text;
7481
8115
  }
@@ -7488,7 +8122,13 @@ function operationInfoFromResolution(resolution, callOperationType) {
7488
8122
  return null;
7489
8123
  }
7490
8124
  const operationType = operation.operationName !== void 0 ? operation.operationType : callOperationType;
7491
- return { ...operation, operationType, document: resolution.document };
8125
+ return {
8126
+ ...operation,
8127
+ operationType,
8128
+ document: resolution.document,
8129
+ ...resolution.unresolvedFragments !== void 0 ? { unresolvedFragments: resolution.unresolvedFragments } : {},
8130
+ ...resolution.unresolved !== void 0 ? { unresolved: resolution.unresolved } : {}
8131
+ };
7492
8132
  }
7493
8133
  return {
7494
8134
  operationType: resolution.operationType ?? callOperationType,
@@ -7530,8 +8170,8 @@ function importedVariableInitializers(identifier) {
7530
8170
  }
7531
8171
  return inits;
7532
8172
  }
7533
- function resolveGqlTemplateText(arg) {
7534
- const direct = documentTextFromExpression(arg);
8173
+ function resolveGqlTemplateText(arg, assembly) {
8174
+ const direct = documentTextFromExpression(arg, assembly);
7535
8175
  if (direct !== null) {
7536
8176
  return direct;
7537
8177
  }
@@ -7551,7 +8191,10 @@ function resolveGqlTemplateText(arg) {
7551
8191
  }
7552
8192
  }
7553
8193
  for (const init of importedVariableInitializers(arg)) {
7554
- const text = documentTextFromExpression(stripDocumentNodeCasts(init));
8194
+ const text = documentTextFromExpression(
8195
+ stripDocumentNodeCasts(init),
8196
+ assembly
8197
+ );
7555
8198
  if (text !== null) {
7556
8199
  return text;
7557
8200
  }
@@ -8139,14 +8782,14 @@ function unitForJsxRoute(node, match, routeNames, kind, resolution) {
8139
8782
  }
8140
8783
  return routeUnit(node, kind, match.method, resolved.path, target, resolution);
8141
8784
  }
8142
- function routeUnit(at, kind, method, path11, target, resolution) {
8143
- const routeInfo = { method, path: path11 };
8785
+ function routeUnit(at, kind, method, path12, target, resolution) {
8786
+ const routeInfo = { method, path: path12 };
8144
8787
  if (target.kind === "unreadable") {
8145
8788
  return {
8146
8789
  func: null,
8147
8790
  announcedAt: at,
8148
8791
  kind,
8149
- name: path11,
8792
+ name: path12,
8150
8793
  nameKind: "label",
8151
8794
  routeInfo
8152
8795
  };
@@ -9034,9 +9677,9 @@ function withMountPrefix(routeInfo, subjectNode, mountPrefixes) {
9034
9677
  const prefix = mountPrefixes.effectivePrefixFor(subjectNode);
9035
9678
  return prefix === "" ? routeInfo : { ...routeInfo, path: joinMountedPath(prefix, routeInfo.path) };
9036
9679
  }
9037
- function joinMountedPath(prefix, path11) {
9680
+ function joinMountedPath(prefix, path12) {
9038
9681
  const trimmed = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
9039
- return trimmed === "" ? path11 : trimmed + path11;
9682
+ return trimmed === "" ? path12 : trimmed + path12;
9040
9683
  }
9041
9684
  function registrationSubjectsOf(sourceFile, importModule, importName) {
9042
9685
  let importedLocalName = null;
@@ -9200,11 +9843,11 @@ function extractRouteInfoFromBinding(call, methodName, binding, resolution) {
9200
9843
  return null;
9201
9844
  }
9202
9845
  const method2 = stringProperty(routeObject, binding.method.property);
9203
- const path11 = stringProperty(routeObject, binding.path.property);
9204
- if (method2 === null || path11 === null) {
9846
+ const path12 = stringProperty(routeObject, binding.path.property);
9847
+ if (method2 === null || path12 === null) {
9205
9848
  return null;
9206
9849
  }
9207
- return { method: method2.toUpperCase(), path: path11 };
9850
+ return { method: method2.toUpperCase(), path: path12 };
9208
9851
  }
9209
9852
  if (binding.method.type !== "fromRegistration" || binding.path.type !== "fromRegistration") {
9210
9853
  return null;
@@ -9311,7 +9954,7 @@ function bodyReferencesLoopVar(loop, name) {
9311
9954
  }
9312
9955
  function readRouteSpec(element, shape, resolution) {
9313
9956
  let method = null;
9314
- let path11 = null;
9957
+ let path12 = null;
9315
9958
  let handler = null;
9316
9959
  for (const prop of element.getProperties()) {
9317
9960
  if (Node44.isMethodDeclaration(prop) && prop.getName() === shape.handlerKey) {
@@ -9329,16 +9972,16 @@ function readRouteSpec(element, shape, resolution) {
9329
9972
  if (name === shape.methodKey) {
9330
9973
  method = readStringLiteralValue(init);
9331
9974
  } else if (name === shape.pathKey) {
9332
- path11 = readStringLiteralValue(init);
9975
+ path12 = readStringLiteralValue(init);
9333
9976
  } else if (name === shape.handlerKey) {
9334
9977
  const func = functionValueOf(init, resolution);
9335
9978
  handler = func === null ? null : { func, name: handlerName(init) };
9336
9979
  }
9337
9980
  }
9338
- if (method === null || path11 === null || handler === null) {
9981
+ if (method === null || path12 === null || handler === null) {
9339
9982
  return null;
9340
9983
  }
9341
- return { method, path: path11, handler: handler.func, handlerName: handler.name };
9984
+ return { method, path: path12, handler: handler.func, handlerName: handler.name };
9342
9985
  }
9343
9986
  function readStringLiteralValue(node) {
9344
9987
  if (Node44.isStringLiteral(node) || Node44.isNoSubstitutionTemplateLiteral(node)) {
@@ -9367,8 +10010,8 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
9367
10010
  }
9368
10011
  const args = node.getArguments();
9369
10012
  for (const reg of match.registrations) {
9370
- const path11 = substitutePath(reg.pathTemplate, args);
9371
- if (path11 === null) {
10013
+ const path12 = substitutePath(reg.pathTemplate, args);
10014
+ if (path12 === null) {
9372
10015
  continue;
9373
10016
  }
9374
10017
  const handler = resolveHandler(reg.handlerArg, args, resolution);
@@ -9381,7 +10024,7 @@ function discoverRegistrationTemplates(sourceFile, match, kind, resolution) {
9381
10024
  name: handler.name,
9382
10025
  routeInfo: {
9383
10026
  method: reg.method.toUpperCase(),
9384
- path: path11
10027
+ path: path12
9385
10028
  }
9386
10029
  });
9387
10030
  }
@@ -9550,7 +10193,10 @@ function discoverResolverMaps(sourceFile, match, kind, resolution) {
9550
10193
  resolverInfo: {
9551
10194
  typeName,
9552
10195
  fieldName,
9553
- ...schemaSdl !== null ? { schemaSdl } : {}
10196
+ ...schemaSdl !== null ? {
10197
+ schemaSdl,
10198
+ schemaDocument: config.getSourceFile().getFilePath()
10199
+ } : {}
9554
10200
  }
9555
10201
  });
9556
10202
  }
@@ -9747,7 +10393,15 @@ function discoverUnits(sourceFile, patterns, resolution, mountPrefixes) {
9747
10393
  }
9748
10394
 
9749
10395
  // src/discovery/mountPrefix.ts
9750
- var NO_MOUNTS = { effectivePrefixFor: () => "" };
10396
+ var NO_MOUNTS = {
10397
+ // Recorded even with no mounts anywhere: a mount added later in some
10398
+ // other file changes this router's prefix without touching its file.
10399
+ effectivePrefixFor: (routerNode) => {
10400
+ recordMountPrefix(nodeId(routerNode), "");
10401
+ return "";
10402
+ },
10403
+ prefixForId: () => ""
10404
+ };
9751
10405
  function buildMountPrefixIndex(packsByFile, resolution) {
9752
10406
  const subjectIdsByPack = /* @__PURE__ */ new Map();
9753
10407
  const mountWorkByPack = /* @__PURE__ */ new Map();
@@ -9804,10 +10458,15 @@ function buildMountPrefixIndex(packsByFile, resolution) {
9804
10458
  return NO_MOUNTS;
9805
10459
  }
9806
10460
  const memo = /* @__PURE__ */ new Map();
10461
+ const byId = (childId) => resolvePrefix(edgesByChild, memo, childId, /* @__PURE__ */ new Set()) ?? "";
9807
10462
  return {
9808
10463
  effectivePrefixFor(routerNode) {
9809
- return resolvePrefix(edgesByChild, memo, nodeId(routerNode), /* @__PURE__ */ new Set()) ?? "";
9810
- }
10464
+ const childId = nodeId(routerNode);
10465
+ const prefix = byId(childId);
10466
+ recordMountPrefix(childId, prefix);
10467
+ return prefix;
10468
+ },
10469
+ prefixForId: byId
9811
10470
  };
9812
10471
  }
9813
10472
  function recordEdge(edgesByChild, candidate) {
@@ -10051,10 +10710,12 @@ import {
10051
10710
  deriveOnDemand,
10052
10711
  evaluate as evaluate2,
10053
10712
  lit as lit2,
10713
+ proofOf,
10054
10714
  rule as rule2,
10055
10715
  tupleKey,
10056
10716
  tupleKeyParts,
10057
- variable as v2
10717
+ variable as v2,
10718
+ witnesses
10058
10719
  } from "@suss/datalog";
10059
10720
  import {
10060
10721
  ANSWER_RELATIONS,
@@ -10068,7 +10729,8 @@ var JS_RULES = [
10068
10729
  rule2(
10069
10730
  "stepsTo",
10070
10731
  [v2("r"), v2("t"), VALUE_STEP],
10071
- [lit2("bindCall", v2("r"), v2("t"))]
10732
+ [lit2("bindCall", v2("r"), v2("t"))],
10733
+ "bind"
10072
10734
  )
10073
10735
  ];
10074
10736
  var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
@@ -10078,6 +10740,15 @@ var RESOLUTION_PROGRAM = process.env.SUSS_RESOLUTION_ON_DEMAND === "0" ? {
10078
10740
  [...SHARED_RULES, ...JS_RULES, ...RESOLUTION_QUESTIONS],
10079
10741
  ANSWER_RELATIONS
10080
10742
  );
10743
+ var WITNESS_RULES = [...SHARED_RULES, ...JS_RULES];
10744
+ var NOT_BASE_FACTS = /* @__PURE__ */ new Set([
10745
+ ...[...SHARED_RULES, ...JS_RULES, ...RESOLUTION_QUESTIONS].map(
10746
+ (r) => r.head.relation
10747
+ ),
10748
+ ...RESOLUTION_PROGRAM.rules.map((r) => r.head.relation),
10749
+ "wanted",
10750
+ "wantedOrigin"
10751
+ ]);
10081
10752
  var QUERY_FACTS = RESOLUTION_PROGRAM.demandDriven.length === 0 ? [] : [...RESOLUTION_PROGRAM.demandDriven, "wanted", "wantedOrigin"];
10082
10753
  var MAX_MODULE_HOPS = 6;
10083
10754
  var ResolutionStore = class {
@@ -10086,6 +10757,8 @@ var ResolutionStore = class {
10086
10757
  fullyExtracted = /* @__PURE__ */ new Set();
10087
10758
  seededValues = /* @__PURE__ */ new Set();
10088
10759
  importedNames = /* @__PURE__ */ new Map();
10760
+ /** Files the most recent wave walk entered, for the memo to keep. */
10761
+ lastQueryWalked = [];
10089
10762
  declarations = /* @__PURE__ */ new Map();
10090
10763
  graph = new ModuleGraph();
10091
10764
  stale = true;
@@ -10103,13 +10776,64 @@ var ResolutionStore = class {
10103
10776
  * the walk never reaches on its own.
10104
10777
  */
10105
10778
  resolveCallable(value, alsoFrom) {
10779
+ const sources = this.resolveCallableSources(value, alsoFrom);
10780
+ const only = sources[0];
10781
+ return sources.length === 1 && only !== void 0 ? only : null;
10782
+ }
10783
+ /**
10784
+ * Every function the value comes down to. One is the answer
10785
+ * `resolveCallable` gives; several is a value with more than one
10786
+ * possible source, which a caller says at the site rather than
10787
+ * folding into the same nothing as a chain that went nowhere.
10788
+ */
10789
+ resolveCallableSources(value, alsoFrom) {
10106
10790
  const target = factKeyOf(value);
10107
10791
  return this.resolveByWaves(
10108
10792
  target,
10109
10793
  "wanted",
10110
- () => this.lookup(target),
10794
+ () => this.lookupSources(target),
10111
10795
  alsoFrom
10796
+ ) ?? [];
10797
+ }
10798
+ /**
10799
+ * The witness proof behind `resolveCallable`'s answer. Resolves the
10800
+ * value first, which walks files and extracts facts in the usual
10801
+ * waves, then re-evaluates the rules over those base facts under the
10802
+ * witness algebra and rebuilds the proof of the answer. Null when
10803
+ * the value does not resolve at all.
10804
+ */
10805
+ explainCallable(value, options = {}) {
10806
+ const resolved = this.resolveCallable(value, options.alsoFrom);
10807
+ if (resolved === null) {
10808
+ return null;
10809
+ }
10810
+ const proofDb = new Database2();
10811
+ let baseFacts = 0;
10812
+ for (const relation of this.db.relationNames()) {
10813
+ if (NOT_BASE_FACTS.has(relation)) {
10814
+ continue;
10815
+ }
10816
+ for (const tuple of this.db.facts(relation)) {
10817
+ proofDb.add(relation, tuple);
10818
+ baseFacts++;
10819
+ }
10820
+ }
10821
+ const started = performance.now();
10822
+ evaluate2(proofDb, WITNESS_RULES, witnesses);
10823
+ const evaluateMs = performance.now() - started;
10824
+ const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
10825
+ const proof = proofOf(
10826
+ proofDb,
10827
+ "resolves",
10828
+ [nodeId(factKeyOf(value)), nodeId(resolved)],
10829
+ options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth }
10112
10830
  );
10831
+ return {
10832
+ target: resolved,
10833
+ proof,
10834
+ nodeFor: (atom) => this.table.byId.get(String(atom)),
10835
+ stats: { baseFacts, derivedFacts, evaluateMs }
10836
+ };
10113
10837
  }
10114
10838
  resolveObject(value) {
10115
10839
  const target = factKeyOf(value);
@@ -10146,7 +10870,10 @@ var ResolutionStore = class {
10146
10870
  const declaration = `${nodeId(refersTo)}|${modules.join(",")}`;
10147
10871
  const cached = this.importedNames.get(declaration);
10148
10872
  if (cached !== void 0) {
10149
- return cached;
10873
+ for (const walkedPath of cached.walked) {
10874
+ recordFileDependency(walkedPath);
10875
+ }
10876
+ return cached.names;
10150
10877
  }
10151
10878
  const target = factKeyOf(value);
10152
10879
  const found = this.resolveByWaves(
@@ -10154,7 +10881,10 @@ var ResolutionStore = class {
10154
10881
  "wantedOrigin",
10155
10882
  () => this.lookupImportedNames(target, modules)
10156
10883
  ) ?? [];
10157
- this.importedNames.set(declaration, found);
10884
+ this.importedNames.set(declaration, {
10885
+ names: found,
10886
+ walked: [...this.lastQueryWalked]
10887
+ });
10158
10888
  return found;
10159
10889
  }
10160
10890
  /**
@@ -10188,6 +10918,7 @@ var ResolutionStore = class {
10188
10918
  this.wantValue(question, value);
10189
10919
  this.seedValue(value);
10190
10920
  const walked = /* @__PURE__ */ new Set();
10921
+ this.lastQueryWalked = [];
10191
10922
  let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10192
10923
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10193
10924
  const next = [];
@@ -10196,6 +10927,8 @@ var ResolutionStore = class {
10196
10927
  continue;
10197
10928
  }
10198
10929
  walked.add(sourceFile.getFilePath());
10930
+ this.lastQueryWalked.push(sourceFile.getFilePath());
10931
+ recordFileDependency(sourceFile.getFilePath());
10199
10932
  this.extractFile(sourceFile);
10200
10933
  next.push(...this.graph.importedFilesOf(sourceFile));
10201
10934
  }
@@ -10223,7 +10956,7 @@ var ResolutionStore = class {
10223
10956
  this.stale = false;
10224
10957
  }
10225
10958
  wantValue(question, value) {
10226
- if (this.db.add(question, [nodeId(value)])) {
10959
+ if (this.db.add(question, [nodeId(value)]) === "added") {
10227
10960
  this.stale = true;
10228
10961
  }
10229
10962
  }
@@ -10287,10 +11020,12 @@ var ResolutionStore = class {
10287
11020
  return this.graph.filesReachingAnyPackage(fileSets);
10288
11021
  }
10289
11022
  /**
10290
- * Two candidates give null. Picking whichever arrived first would make
10291
- * the result depend on the order the facts came in.
11023
+ * Null keeps the wave walk widening; any candidate at all ends it,
11024
+ * since more files can only add candidates, never take one away. The
11025
+ * single-answer policy stays with the callers: picking one of two
11026
+ * would make the result depend on the order the facts came in.
10292
11027
  */
10293
- lookup(value) {
11028
+ lookupSources(value) {
10294
11029
  this.derive();
10295
11030
  const candidates = /* @__PURE__ */ new Set();
10296
11031
  for (const target of this.answersFor("wantedResolves", nodeId(value))) {
@@ -10302,10 +11037,7 @@ var ResolutionStore = class {
10302
11037
  candidates.add(resolved);
10303
11038
  }
10304
11039
  }
10305
- if (candidates.size !== 1) {
10306
- return null;
10307
- }
10308
- return [...candidates][0];
11040
+ return candidates.size === 0 ? null : [...candidates];
10309
11041
  }
10310
11042
  derive() {
10311
11043
  if (!this.stale) {
@@ -10495,6 +11227,27 @@ function moduleInitSummary(sourceFile, effects, options) {
10495
11227
  return assembleSummary(raw, options);
10496
11228
  }
10497
11229
 
11230
+ // src/referencedFiles.ts
11231
+ function createReferenceIndex(sourceFiles) {
11232
+ const direct = /* @__PURE__ */ new Map();
11233
+ for (const sourceFile of sourceFiles) {
11234
+ const referenced = /* @__PURE__ */ new Set();
11235
+ for (const target of sourceFile.getReferencedSourceFiles()) {
11236
+ if (target.isInNodeModules() || target.isDeclarationFile()) {
11237
+ continue;
11238
+ }
11239
+ referenced.add(target.getFilePath());
11240
+ }
11241
+ direct.set(sourceFile.getFilePath(), referenced);
11242
+ }
11243
+ const none = /* @__PURE__ */ new Set();
11244
+ return {
11245
+ directOf(filePath) {
11246
+ return direct.get(filePath) ?? none;
11247
+ }
11248
+ };
11249
+ }
11250
+
10498
11251
  // src/resolve/boundaryEffects.ts
10499
11252
  import { evaluate as evaluate3, lit as lit3, rule as rule3, variable } from "@suss/datalog";
10500
11253
  var EFFECT_RULES = [
@@ -10679,6 +11432,11 @@ function classifyStop(declarations) {
10679
11432
  if (declarations.length === 0) {
10680
11433
  return "noDeclaration";
10681
11434
  }
11435
+ for (const declaration of declarations) {
11436
+ if (!isInExternalCode(declaration.getSourceFile())) {
11437
+ recordFileDependency(declaration.getSourceFile().getFilePath());
11438
+ }
11439
+ }
10682
11440
  if (declarations.every(isExternalDeclaration)) {
10683
11441
  return "outsideRun";
10684
11442
  }
@@ -10690,6 +11448,7 @@ function classifyStop(declarations) {
10690
11448
  var RECORDED = {
10691
11449
  noBody: true,
10692
11450
  unsettledValue: true,
11451
+ multipleSources: true,
10693
11452
  outsideRun: false,
10694
11453
  noDeclaration: false
10695
11454
  };
@@ -10699,6 +11458,7 @@ function worthRecording(reason) {
10699
11458
  var STOP_SENTENCE = {
10700
11459
  noBody: (callee) => `The call to ${callee} lands on a declaration with no body, so whatever runs there is missing from this summary`,
10701
11460
  unsettledValue: (callee) => `The call to ${callee} goes through a value this run could not settle, so whatever runs there is missing from this summary`,
11461
+ multipleSources: (callee) => `The call to ${callee} reaches a value with more than one possible source, so whatever runs there is missing from this summary`,
10702
11462
  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`,
10703
11463
  noDeclaration: (callee) => `The call to ${callee} has no declaration this run could find, so whatever runs there is missing from this summary`
10704
11464
  };
@@ -10752,24 +11512,6 @@ function resolveDecl(decl, calleeName) {
10752
11512
  }
10753
11513
  return null;
10754
11514
  }
10755
- if (Node50.isImportSpecifier(decl)) {
10756
- const importDecl = decl.getImportDeclaration();
10757
- const sf = importDecl.getModuleSpecifierSourceFile();
10758
- if (sf === void 0 || isInExternalCode(sf)) {
10759
- return null;
10760
- }
10761
- const exported = exportedDeclarationsOf(sf).get(decl.getName());
10762
- if (exported === void 0) {
10763
- return null;
10764
- }
10765
- for (const ed of exported) {
10766
- const resolved = resolveDecl(ed, calleeName);
10767
- if (resolved !== null) {
10768
- return resolved;
10769
- }
10770
- }
10771
- return null;
10772
- }
10773
11515
  return null;
10774
11516
  }
10775
11517
  function declName(decl) {
@@ -10797,20 +11539,27 @@ function resolveCallee(call, calleeName, scan) {
10797
11539
  }
10798
11540
  const callee = call.getExpression();
10799
11541
  const symbol = callee.getSymbol();
10800
- const declarations = symbol?.getDeclarations() ?? [];
11542
+ const declarations = declarationsBehind(symbol);
10801
11543
  for (const decl of declarations) {
10802
11544
  const resolved = resolveDecl(decl, calleeName);
10803
11545
  if (resolved !== null) {
10804
11546
  return { kind: "followed", candidate: resolved };
10805
11547
  }
10806
11548
  }
10807
- const resolveCallable = scan.resolveCallable;
10808
- if (resolveCallable !== void 0 && Node50.isPropertyAccessExpression(callee) && declarations.some(isDeclaredShape)) {
10809
- const value = resolveCallable(callee, scan.reachedFrom);
10810
- const resolved = value === null ? null : resolveDecl(value, calleeName);
11549
+ const resolveSources = scan.resolveCallableSources;
11550
+ if (resolveSources !== void 0 && Node50.isPropertyAccessExpression(callee) && declarations.some(isDeclaredShape)) {
11551
+ const sources = resolveSources(callee, scan.reachedFrom);
11552
+ const only = sources.length === 1 ? sources[0] : void 0;
11553
+ const resolved = only === void 0 ? null : resolveDecl(only, calleeName);
10811
11554
  if (resolved !== null) {
10812
11555
  return { kind: "followed", candidate: resolved };
10813
11556
  }
11557
+ if (sources.length > 1) {
11558
+ return {
11559
+ kind: "stopped",
11560
+ stop: { callee: calleeName, reason: "multipleSources" }
11561
+ };
11562
+ }
10814
11563
  }
10815
11564
  return {
10816
11565
  kind: "stopped",
@@ -10885,7 +11634,7 @@ var REACHABLE_RULES = [
10885
11634
  )
10886
11635
  ];
10887
11636
  var NO_RECOGNIZERS = { invocation: [], access: [] };
10888
- function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS) {
11637
+ function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = []) {
10889
11638
  const lookup = createSourceFileLookup(project);
10890
11639
  const db = facts?.db ?? new Database3();
10891
11640
  const functionByKey = /* @__PURE__ */ new Map();
@@ -10905,6 +11654,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10905
11654
  db.add("entry", [key]);
10906
11655
  }
10907
11656
  }
11657
+ const knownKeys = /* @__PURE__ */ new Set();
11658
+ for (const summary of alreadySummarized) {
11659
+ const func = lookup.functionAt(summary.location);
11660
+ if (func !== null) {
11661
+ const key = nodeKey2(func);
11662
+ knownKeys.add(key);
11663
+ facts?.unitKeyBySummary.set(summary, key);
11664
+ }
11665
+ }
10908
11666
  for (; ; ) {
10909
11667
  evaluate4(db, REACHABLE_RULES);
10910
11668
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
@@ -10919,10 +11677,14 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10919
11677
  }
10920
11678
  const cameFrom = reachedFrom.get(key);
10921
11679
  const scan = {
10922
- ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
11680
+ ...recognizers.resolveCallableSources === void 0 ? {} : { resolveCallableSources: recognizers.resolveCallableSources },
10923
11681
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10924
11682
  };
10925
- const { candidates, stops } = collectReachable(source.func, scan);
11683
+ const { candidates, stops } = scanWithRecording(
11684
+ key,
11685
+ facts,
11686
+ () => collectReachable(source.func, scan)
11687
+ );
10926
11688
  if (stops.length > 0) {
10927
11689
  stopsByKey.set(key, stops);
10928
11690
  }
@@ -10941,7 +11703,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10941
11703
  const reached = [];
10942
11704
  for (const [keyAtom] of db.facts("reachable")) {
10943
11705
  const key = String(keyAtom);
10944
- if (seedKeys.has(key)) {
11706
+ if (seedKeys.has(key) || knownKeys.has(key)) {
10945
11707
  continue;
10946
11708
  }
10947
11709
  const candidate = functionByKey.get(key);
@@ -10955,7 +11717,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10955
11717
  candidate.func.getSourceFile().getFilePath()
10956
11718
  );
10957
11719
  }
10958
- const summary = extractReachableSummary(candidate, options, recognizers);
11720
+ const summary = scanWithRecording(
11721
+ key,
11722
+ facts,
11723
+ () => extractReachableSummary(candidate, options, recognizers)
11724
+ );
10959
11725
  facts?.unitKeyBySummary.set(summary, key);
10960
11726
  rememberSummary(summariesByKey, key, summary);
10961
11727
  reached.push(summary);
@@ -10963,6 +11729,20 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10963
11729
  recordStops(stopsByKey, summariesByKey, options);
10964
11730
  return [...seeds, ...reached];
10965
11731
  }
11732
+ function scanWithRecording(key, facts, fn) {
11733
+ const filesByKey = facts?.filesByKey;
11734
+ if (filesByKey === void 0) {
11735
+ return fn();
11736
+ }
11737
+ const sink = createDependencySink();
11738
+ const result = withDependencySink(sink, fn);
11739
+ const bucket = filesByKey.get(key) ?? /* @__PURE__ */ new Set();
11740
+ for (const filePath of sink.files) {
11741
+ bucket.add(filePath);
11742
+ }
11743
+ filesByKey.set(key, bucket);
11744
+ return result;
11745
+ }
10966
11746
  function rememberSummary(summariesByKey, key, summary) {
10967
11747
  const bucket = summariesByKey.get(key) ?? [];
10968
11748
  bucket.push(summary);
@@ -11718,20 +12498,25 @@ import {
11718
12498
  disambiguateSummaryIds,
11719
12499
  summaryIdFromParts
11720
12500
  } from "@suss/behavioral-ir";
12501
+ function workspaceRootFor(anchor) {
12502
+ return namedPackageDirAbove(anchor) ?? path7.resolve(anchor);
12503
+ }
11721
12504
  function workspaceNameFor(root) {
11722
12505
  if (root === void 0) {
11723
12506
  return null;
11724
12507
  }
11725
- let at = path7.resolve(root);
12508
+ const dir = namedPackageDirAbove(root);
12509
+ if (dir !== null) {
12510
+ return packageNameAt(dir);
12511
+ }
12512
+ const folder = path7.basename(path7.resolve(root));
12513
+ return folder.length > 0 ? folder : null;
12514
+ }
12515
+ function namedPackageDirAbove(start) {
12516
+ let at = path7.resolve(start);
11726
12517
  for (let up = 0; up < 12; up += 1) {
11727
- try {
11728
- const manifest = JSON.parse(
11729
- fs6.readFileSync(path7.join(at, "package.json"), "utf8")
11730
- );
11731
- if (typeof manifest.name === "string" && manifest.name.length > 0) {
11732
- return manifest.name;
11733
- }
11734
- } catch {
12518
+ if (packageNameAt(at) !== null) {
12519
+ return at;
11735
12520
  }
11736
12521
  const parent = path7.dirname(at);
11737
12522
  if (parent === at) {
@@ -11739,8 +12524,19 @@ function workspaceNameFor(root) {
11739
12524
  }
11740
12525
  at = parent;
11741
12526
  }
11742
- const folder = path7.basename(path7.resolve(root));
11743
- return folder.length > 0 ? folder : null;
12527
+ return null;
12528
+ }
12529
+ function packageNameAt(dir) {
12530
+ try {
12531
+ const manifest = JSON.parse(
12532
+ fs6.readFileSync(path7.join(dir, "package.json"), "utf8")
12533
+ );
12534
+ if (typeof manifest.name === "string" && manifest.name.length > 0) {
12535
+ return manifest.name;
12536
+ }
12537
+ } catch {
12538
+ }
12539
+ return null;
11744
12540
  }
11745
12541
  function nameSummaries(summaries, args) {
11746
12542
  for (const summary of summaries) {
@@ -12224,6 +13020,36 @@ function extractCodeStructure(unit, pack, invocationRecognizers = [], accessReco
12224
13020
  function responseAccessorNames(pack, kind) {
12225
13021
  return pack.responseSemantics?.filter((m) => m.semantics.type === kind).map((m) => m.name);
12226
13022
  }
13023
+ var ACCESSOR_FIELD_SEMANTICS = {
13024
+ bodyAccessors: "body",
13025
+ statusAccessors: "statusCode",
13026
+ successAccessors: "statusRange"
13027
+ };
13028
+ function responseAccessors(pack) {
13029
+ const out = {};
13030
+ for (const [field, kind] of Object.entries(ACCESSOR_FIELD_SEMANTICS)) {
13031
+ const names = responseAccessorNames(pack, kind);
13032
+ if (names !== void 0 && names.length > 0) {
13033
+ out[field] = names;
13034
+ }
13035
+ }
13036
+ return out;
13037
+ }
13038
+ var FUNCTION_FALLTHROUGH_TERMINAL = {
13039
+ kind: "return",
13040
+ match: { type: "functionFallthrough" },
13041
+ extraction: {}
13042
+ };
13043
+ function terminalsFor(unit, pack) {
13044
+ const declared = unit.terminals ?? pack.terminals;
13045
+ if (unit.callSite === void 0) {
13046
+ return declared;
13047
+ }
13048
+ if (declared.some((p) => p.match.type === "functionFallthrough")) {
13049
+ return declared;
13050
+ }
13051
+ return [...declared, FUNCTION_FALLTHROUGH_TERMINAL];
13052
+ }
12227
13053
  function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers, barriers, resolveWrittenValue) {
12228
13054
  const { func, kind, name } = unit;
12229
13055
  if (func === null) {
@@ -12235,7 +13061,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12235
13061
  );
12236
13062
  const extracted = extractRawBranches(
12237
13063
  func,
12238
- unit.terminals ?? pack.terminals,
13064
+ terminalsFor(unit, pack),
12239
13065
  invocationRecognizers,
12240
13066
  accessRecognizers,
12241
13067
  barriers,
@@ -12271,8 +13097,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12271
13097
  }
12272
13098
  }
12273
13099
  }
12274
- const bodyAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "body") : void 0;
12275
- const statusAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "statusCode") : void 0;
13100
+ const accessors = unit.callSite !== void 0 ? responseAccessors(pack) : {};
12276
13101
  return {
12277
13102
  identity: {
12278
13103
  name,
@@ -12296,8 +13121,8 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12296
13121
  bodyContent: bodyContentOf(func),
12297
13122
  dependencyCalls: depCalls,
12298
13123
  declaredContract: null,
12299
- ...bodyAccessors !== void 0 && bodyAccessors.length > 0 ? { bodyAccessors } : {},
12300
- ...statusAccessors !== void 0 && statusAccessors.length > 0 ? { statusAccessors } : {}
13124
+ ...accessors,
13125
+ ...unit.callSite !== void 0 && pack.failureDelivery !== void 0 ? { failureDelivery: pack.failureDelivery } : {}
12301
13126
  };
12302
13127
  }
12303
13128
  function extractConsumerBinding(unit, pattern, pack, resolution) {
@@ -12310,11 +13135,11 @@ function extractConsumerBinding(unit, pattern, pack, resolution) {
12310
13135
  return null;
12311
13136
  }
12312
13137
  const method = extractBindingMethod(binding, callSite, pack, resolution);
12313
- const path11 = extractBindingPath(binding, callSite, pack, resolution);
13138
+ const path12 = extractBindingPath(binding, callSite, pack, resolution);
12314
13139
  return restBinding2({
12315
13140
  transport: pack.protocol,
12316
13141
  method: method ?? null,
12317
- path: path11 ?? null,
13142
+ path: path12 ?? null,
12318
13143
  recognition: pack.name
12319
13144
  });
12320
13145
  }
@@ -12403,12 +13228,12 @@ function originEndOf(flattened) {
12403
13228
  const slash = flattened.indexOf("/", opener[0].length);
12404
13229
  return slash === -1 ? flattened.length : slash;
12405
13230
  }
12406
- function appendPathText(path11, text) {
13231
+ function appendPathText(path12, text) {
12407
13232
  const idx = text.search(/[?#]/);
12408
13233
  if (idx === -1) {
12409
- return { path: path11 + text, stop: false };
13234
+ return { path: path12 + text, stop: false };
12410
13235
  }
12411
- return { path: path11 + text.slice(0, idx), stop: true };
13236
+ return { path: path12 + text.slice(0, idx), stop: true };
12412
13237
  }
12413
13238
  function parseAbsoluteUrl(text) {
12414
13239
  try {
@@ -12432,8 +13257,8 @@ function pathnameOfAbsoluteLiteral(text) {
12432
13257
  return stripQueryAndFragment(stripOriginManually(text));
12433
13258
  }
12434
13259
  function pathFromLiteralUrl(text) {
12435
- const path11 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
12436
- return path11 === "" ? void 0 : path11;
13260
+ const path12 = isAbsoluteUrlLiteral(text) ? pathnameOfAbsoluteLiteral(text) : stripQueryAndFragment(text);
13261
+ return path12 === "" ? void 0 : path12;
12437
13262
  }
12438
13263
  function pathFromTemplateUrl(arg) {
12439
13264
  const headText = arg.getHead().getLiteralText();
@@ -12441,7 +13266,7 @@ function pathFromTemplateUrl(arg) {
12441
13266
  const tails = spans.map((span) => span.getLiteral().getLiteralText());
12442
13267
  const originEnd = originEndOf([headText, ...tails].join(SUBSTITUTION));
12443
13268
  const head = appendPathText("", headText.slice(originEnd));
12444
- let path11 = head.path;
13269
+ let path12 = head.path;
12445
13270
  let stop = head.stop;
12446
13271
  let at = headText.length;
12447
13272
  for (let i = 0; i < spans.length && !stop; i++) {
@@ -12451,18 +13276,18 @@ function pathFromTemplateUrl(arg) {
12451
13276
  continue;
12452
13277
  }
12453
13278
  if (at >= originEnd) {
12454
- path11 += `{${placeholderName(span.getExpression())}}`;
13279
+ path12 += `{${placeholderName(span.getExpression())}}`;
12455
13280
  }
12456
13281
  at += SUBSTITUTION.length;
12457
13282
  const appended = appendPathText(
12458
- path11,
13283
+ path12,
12459
13284
  tailText.slice(Math.max(0, originEnd - at))
12460
13285
  );
12461
- path11 = appended.path;
13286
+ path12 = appended.path;
12462
13287
  stop = appended.stop;
12463
13288
  at += tailText.length;
12464
13289
  }
12465
- return path11 === "" ? void 0 : path11;
13290
+ return path12 === "" ? void 0 : path12;
12466
13291
  }
12467
13292
  function extractBindingPath(binding, callSite, pack, resolution) {
12468
13293
  const p = binding.path;
@@ -12651,12 +13476,20 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12651
13476
  if (claimant.pack === pack.name && claimant.file === sourceFile.getFilePath() && tally !== void 0) {
12652
13477
  tally.selfCollisions += 1;
12653
13478
  }
13479
+ if (claimant.file !== sourceFile.getFilePath()) {
13480
+ recordFileDependency(claimant.file);
13481
+ }
12654
13482
  continue;
12655
13483
  }
12656
13484
  claimed.set(claimKey, {
12657
13485
  pack: pack.name,
12658
13486
  file: sourceFile.getFilePath()
12659
13487
  });
13488
+ recordUnitClaim(claimKey, pack.name);
13489
+ const declaringFile = unit.func?.getSourceFile().getFilePath();
13490
+ if (declaringFile !== void 0 && declaringFile !== sourceFile.getFilePath()) {
13491
+ recordFileDependency(declaringFile);
13492
+ }
12660
13493
  unitsWalkedHere += 1;
12661
13494
  if (tally !== void 0) {
12662
13495
  tally.unitsClaimed += 1;
@@ -12695,6 +13528,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12695
13528
  }
12696
13529
  if (unit.resolverInfo.schemaSdl !== void 0) {
12697
13530
  raw.graphqlSchemaSdl = unit.resolverInfo.schemaSdl;
13531
+ if (unit.resolverInfo.schemaDocument !== void 0) {
13532
+ raw.sourceDocumentLabel = unit.resolverInfo.schemaDocument;
13533
+ }
12698
13534
  if (unit.resolverInfo.typeName !== null) {
12699
13535
  const contract = deriveGraphqlContract(
12700
13536
  unit.resolverInfo.schemaSdl,
@@ -12730,6 +13566,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12730
13566
  if (unit.operationInfo.document !== void 0) {
12731
13567
  raw.graphqlDocument = unit.operationInfo.document;
12732
13568
  }
13569
+ if (unit.operationInfo.unresolvedFragments !== void 0) {
13570
+ raw.graphqlUnresolvedFragments = unit.operationInfo.unresolvedFragments;
13571
+ }
12733
13572
  if (unit.operationInfo.unresolved !== void 0) {
12734
13573
  raw.graphqlUnresolvedDocument = unit.operationInfo.unresolved;
12735
13574
  }
@@ -12883,6 +13722,37 @@ function fileImporting(project, modulePrefix) {
12883
13722
  }
12884
13723
  return void 0;
12885
13724
  }
13725
+ function liftSchemasOntoDocuments(summaries) {
13726
+ const sdlByDocument = /* @__PURE__ */ new Map();
13727
+ for (const summary of summaries) {
13728
+ const label = readSourceDocumentMetadata(summary)?.label;
13729
+ const graphql = readGraphqlMetadata2(summary);
13730
+ if (label === void 0 || graphql?.schemaSdl === void 0) {
13731
+ continue;
13732
+ }
13733
+ sdlByDocument.set(label, graphql.schemaSdl);
13734
+ const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
13735
+ summary.metadata = withGraphqlMetadata2(summary.metadata, rest);
13736
+ }
13737
+ for (const [label, schemaSdl] of sdlByDocument) {
13738
+ summaries.push(schemaDocumentSummary(label, schemaSdl));
13739
+ }
13740
+ }
13741
+ function schemaDocumentSummary(label, schemaSdl) {
13742
+ return {
13743
+ kind: "library",
13744
+ location: { file: label, range: { start: 0, end: 0 }, exportName: null },
13745
+ identity: { name: label, exportPath: null, boundaryBinding: null },
13746
+ inputs: [],
13747
+ transitions: [],
13748
+ gaps: [],
13749
+ confidence: { source: "declared", level: "high" },
13750
+ metadata: withGraphqlMetadata2(
13751
+ withSourceDocumentMetadata(void 0, { label }),
13752
+ { schemaSdl }
13753
+ )
13754
+ };
13755
+ }
12886
13756
  function libraryEnvReadMarker(pack, declared, importer) {
12887
13757
  return {
12888
13758
  kind: "library",
@@ -13000,8 +13870,8 @@ function synthesizeCallerSummaries(wrapper, _project, options) {
13000
13870
  if (pathArg === void 0) {
13001
13871
  continue;
13002
13872
  }
13003
- const path11 = literalOrTemplate(pathArg);
13004
- if (path11 === void 0) {
13873
+ const path12 = literalOrTemplate(pathArg);
13874
+ if (path12 === void 0) {
13005
13875
  continue;
13006
13876
  }
13007
13877
  const callerFunc = enclosingFunction(callExpr);
@@ -13013,7 +13883,7 @@ function synthesizeCallerSummaries(wrapper, _project, options) {
13013
13883
  continue;
13014
13884
  }
13015
13885
  seen.add(dedupKey);
13016
- out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path11, options));
13886
+ out.push(buildCallerSummary(wrapper, callerFunc, callExpr, path12, options));
13017
13887
  }
13018
13888
  return out;
13019
13889
  }
@@ -13065,12 +13935,12 @@ function literalOrTemplate(arg) {
13065
13935
  return arg.getLiteralValue();
13066
13936
  }
13067
13937
  if (Node54.isTemplateExpression(arg)) {
13068
- let path11 = arg.getHead().getLiteralText();
13938
+ let path12 = arg.getHead().getLiteralText();
13069
13939
  for (const span of arg.getTemplateSpans()) {
13070
- path11 += `{${placeholderName(span.getExpression())}}`;
13071
- path11 += span.getLiteral().getLiteralText();
13940
+ path12 += `{${placeholderName(span.getExpression())}}`;
13941
+ path12 += span.getLiteral().getLiteralText();
13072
13942
  }
13073
- return path11;
13943
+ return path12;
13074
13944
  }
13075
13945
  return void 0;
13076
13946
  }
@@ -13087,7 +13957,7 @@ function callerName(func) {
13087
13957
  }
13088
13958
  return "anonymous";
13089
13959
  }
13090
- function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13960
+ function buildCallerSummary(wrapper, callerFunc, callExpr, path12, options) {
13091
13961
  const wrapperBinding = wrapper.summary.identity.boundaryBinding;
13092
13962
  const wrapperRest = wrapperBinding?.semantics.name === "rest" ? wrapperBinding.semantics : null;
13093
13963
  const syntheticPack = {
@@ -13119,7 +13989,7 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13119
13989
  raw.boundaryBinding = restBinding2({
13120
13990
  transport: wrapperBinding?.transport ?? "http",
13121
13991
  method: wrapperRest?.method ?? null,
13122
- path: path11,
13992
+ path: path12,
13123
13993
  recognition: wrapperBinding?.recognition ?? "unknown"
13124
13994
  });
13125
13995
  const summary = assembleSummary3(raw, options);
@@ -13133,8 +14003,8 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13133
14003
  summary.confidence = { source: "inferred_static", level: "low" };
13134
14004
  return summary;
13135
14005
  }
13136
- function named(summaries, workspace) {
13137
- const projectRoot = commonDirectoryOf(summaries.map((s) => s.location.file));
14006
+ function named(summaries, workspace, runRoot) {
14007
+ const projectRoot = runRoot ?? commonDirectoryOf(summaries.map((s) => s.location.file));
13138
14008
  nameSummaries(summaries, {
13139
14009
  workspace: workspace ?? workspaceNameFor(projectRoot),
13140
14010
  projectRoot
@@ -13192,6 +14062,7 @@ function createTypeScriptAdapter(config) {
13192
14062
  skipAddingFilesFromTsConfig: true
13193
14063
  } : { skipAddingFilesFromTsConfig: true }
13194
14064
  );
14065
+ const runRoot = config.projectRoot ?? (config.tsConfigFilePath !== void 0 ? workspaceRootFor(path9.dirname(config.tsConfigFilePath)) : void 0);
13195
14066
  let lazyBootstrapped = false;
13196
14067
  let projectFileSet;
13197
14068
  const cacheDir = declineWhenRunFromSource(
@@ -13232,7 +14103,7 @@ function createTypeScriptAdapter(config) {
13232
14103
  )
13233
14104
  );
13234
14105
  }
13235
- return named(summaries, config.workspace);
14106
+ return named(summaries, config.workspace, runRoot);
13236
14107
  },
13237
14108
  async extractAll() {
13238
14109
  const timer = createTimer();
@@ -13265,7 +14136,25 @@ function createTypeScriptAdapter(config) {
13265
14136
  if (config.onTiming !== void 0) {
13266
14137
  config.onTiming(timer.report());
13267
14138
  }
13268
- return named(lookup.summaries, config.workspace);
14139
+ return named(lookup.summaries, config.workspace, runRoot);
14140
+ }
14141
+ const plan = lookup.diagnostic.missReason === "files-changed" ? await timer.timeAsync("cache.plan", () => cache.plan(cacheInput)) : null;
14142
+ if (plan !== null && plan.changed.size === 0 && plan.removed.size === 0) {
14143
+ try {
14144
+ await cache.write(
14145
+ cacheInput,
14146
+ plan.allSummaries(),
14147
+ plan.attribution()
14148
+ );
14149
+ } catch {
14150
+ }
14151
+ if (config.onCacheDiagnostic !== void 0) {
14152
+ config.onCacheDiagnostic({ kind: "hit" });
14153
+ }
14154
+ if (config.onTiming !== void 0) {
14155
+ config.onTiming(timer.report());
14156
+ }
14157
+ return named(plan.allSummaries(), config.workspace, runRoot);
13269
14158
  }
13270
14159
  let candidatePaths = null;
13271
14160
  if (lazyEligible) {
@@ -13296,16 +14185,58 @@ function createTypeScriptAdapter(config) {
13296
14185
  "mountPrefix",
13297
14186
  () => buildMountPrefixIndex(packsByFile, resolution)
13298
14187
  );
14188
+ const caching = cacheDir !== null;
14189
+ const validRoots = /* @__PURE__ */ new Set();
14190
+ if (plan !== null) {
14191
+ const packNamesByPath = /* @__PURE__ */ new Map();
14192
+ for (const [sf, packs] of packsByFile) {
14193
+ packNamesByPath.set(
14194
+ sf.getFilePath(),
14195
+ packs.map((p) => p.name).sort().join(",")
14196
+ );
14197
+ }
14198
+ for (const rootPath of plan.validRoots) {
14199
+ const record = plan.roots.get(rootPath);
14200
+ if (record === void 0) {
14201
+ continue;
14202
+ }
14203
+ if (!mountAssumptionsAgree(record, mountPrefixes)) {
14204
+ continue;
14205
+ }
14206
+ const stored = [...record.packs].sort().join(",");
14207
+ if ((packNamesByPath.get(rootPath) ?? "") !== stored) {
14208
+ continue;
14209
+ }
14210
+ validRoots.add(rootPath);
14211
+ }
14212
+ }
14213
+ const reused = plan !== null && validRoots.size > 0 ? plan.reuse(validRoots) : null;
13299
14214
  const claimedUnits = /* @__PURE__ */ new Map();
14215
+ if (reused !== null && plan !== null) {
14216
+ for (const rootPath of validRoots) {
14217
+ for (const claim of plan.roots.get(rootPath)?.claims ?? []) {
14218
+ claimedUnits.set(claim.key, { pack: claim.pack, file: rootPath });
14219
+ }
14220
+ }
14221
+ }
14222
+ const walkList = reused === null ? sourceFiles : sourceFiles.filter((sf) => !validRoots.has(sf.getFilePath()));
14223
+ const sinkByRoot = /* @__PURE__ */ new Map();
14224
+ const ownersBySummary = /* @__PURE__ */ new Map();
13300
14225
  timer.time("extract per-file", () => {
13301
- for (const sourceFile of sourceFiles) {
14226
+ for (const sourceFile of walkList) {
14227
+ const rootPath = sourceFile.getFilePath();
14228
+ const sink = createDependencySink();
14229
+ if (caching) {
14230
+ sinkByRoot.set(rootPath, sink);
14231
+ }
13302
14232
  const applicablePacks = packsByFile.get(sourceFile);
13303
14233
  if (applicablePacks === void 0) {
13304
14234
  continue;
13305
14235
  }
13306
14236
  try {
13307
- summaries.push(
13308
- ...extractFromSourceFile(
14237
+ const walked = withDependencySink(
14238
+ sink,
14239
+ () => extractFromSourceFile(
13309
14240
  sourceFile,
13310
14241
  applicablePacks,
13311
14242
  claimedUnits,
@@ -13315,6 +14246,10 @@ function createTypeScriptAdapter(config) {
13315
14246
  mountPrefixes
13316
14247
  )
13317
14248
  );
14249
+ for (const summary of walked) {
14250
+ ownersBySummary.set(summary, /* @__PURE__ */ new Set([rootPath]));
14251
+ }
14252
+ summaries.push(...walked);
13318
14253
  } catch (error) {
13319
14254
  if (!(error instanceof RangeError)) {
13320
14255
  throw error;
@@ -13323,23 +14258,32 @@ function createTypeScriptAdapter(config) {
13323
14258
  }
13324
14259
  }
13325
14260
  });
14261
+ const wrapperInput = reused === null ? summaries : [...summaries, ...reused.summaries];
13326
14262
  const withWrappers = timer.time(
13327
14263
  "expandWrapperCallers",
13328
- () => expandWrapperCallers(summaries, project, config.extractorOptions)
14264
+ () => expandWrapperCallers(wrapperInput, project, config.extractorOptions)
13329
14265
  );
14266
+ const pipeline = reused === null ? withWrappers : [...summaries, ...withWrappers.slice(wrapperInput.length)];
13330
14267
  const withSubUnits = timer.time(
13331
14268
  "synthesizeSubUnits",
13332
14269
  () => synthesizeSubUnits(
13333
- withWrappers,
14270
+ pipeline,
13334
14271
  project,
13335
14272
  config.frameworks,
13336
14273
  config.extractorOptions,
13337
- tallies
14274
+ tallies,
14275
+ (created, parent) => {
14276
+ const parentOwners = ownersBySummary.get(parent);
14277
+ if (parentOwners !== void 0) {
14278
+ ownersBySummary.set(created, new Set(parentOwners));
14279
+ }
14280
+ }
13338
14281
  )
13339
14282
  );
13340
14283
  const closureFacts = {
13341
14284
  db: new Database5(),
13342
- unitKeyBySummary: /* @__PURE__ */ new Map()
14285
+ unitKeyBySummary: /* @__PURE__ */ new Map(),
14286
+ ...caching ? { filesByKey: /* @__PURE__ */ new Map() } : {}
13343
14287
  };
13344
14288
  const withClosure = config.includeReachable !== false ? timer.time(
13345
14289
  "expandReachableClosure",
@@ -13356,13 +14300,29 @@ function createTypeScriptAdapter(config) {
13356
14300
  ),
13357
14301
  access: collectAccessRecognizers(config.frameworks, tallies),
13358
14302
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13359
- resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13360
- }
14303
+ resolveCallableSources: (value, alsoFrom) => resolution.resolveCallableSources(value, alsoFrom)
14304
+ },
14305
+ // Reached units the cache already serves emit nothing,
14306
+ // the way a cold run's seeds do not.
14307
+ reused?.summaries ?? []
13361
14308
  )
13362
14309
  ) : withSubUnits;
14310
+ const closureOwnership = caching && config.includeReachable !== false ? timer.time(
14311
+ "cache.attributeClosure",
14312
+ () => attributeReachedSummaries(
14313
+ withSubUnits,
14314
+ withClosure.slice(withSubUnits.length),
14315
+ closureFacts,
14316
+ ownersBySummary
14317
+ )
14318
+ ) : null;
14319
+ const merged = timer.time(
14320
+ "cache.merge",
14321
+ () => mergeWithReused(reused, withClosure, ownersBySummary)
14322
+ );
13363
14323
  const enriched = timer.time(
13364
14324
  "enrichRethrows",
13365
- () => enrichRethrows(withClosure, project, closureFacts)
14325
+ () => enrichRethrows(merged.summaries, project, closureFacts)
13366
14326
  );
13367
14327
  if (config.includeReachable !== false) {
13368
14328
  timer.time(
@@ -13378,6 +14338,10 @@ function createTypeScriptAdapter(config) {
13378
14338
  "emitLibraryEnvReadMarkers",
13379
14339
  () => emitLibraryEnvReadMarkers(enriched, project, config.frameworks)
13380
14340
  );
14341
+ timer.time(
14342
+ "liftSchemasOntoDocuments",
14343
+ () => liftSchemasOntoDocuments(enriched)
14344
+ );
13381
14345
  timer.time(
13382
14346
  "stampGraphqlClientRefs",
13383
14347
  () => stampGraphqlClientRefs(
@@ -13387,12 +14351,41 @@ function createTypeScriptAdapter(config) {
13387
14351
  resolution
13388
14352
  )
13389
14353
  );
14354
+ if (config.onCacheDiagnostic !== void 0 && plan !== null) {
14355
+ config.onCacheDiagnostic({
14356
+ kind: "partial",
14357
+ partial: {
14358
+ filesChanged: plan.changed.size,
14359
+ filesRemoved: plan.removed.size,
14360
+ rootsReused: validRoots.size,
14361
+ rootsReextracted: walkList.length,
14362
+ rootsDeclined: plan.rootsDeclined,
14363
+ summariesReused: merged.reusedKept.length
14364
+ }
14365
+ });
14366
+ }
13390
14367
  await timer.timeAsync("cache.write", async () => {
13391
- if (enriched.length === 0) {
14368
+ if (!caching || enriched.length === 0) {
13392
14369
  return;
13393
14370
  }
13394
14371
  try {
13395
- await cache.write(cacheInput, enriched);
14372
+ const attribution = buildCacheAttribution({
14373
+ project,
14374
+ plan,
14375
+ validRoots,
14376
+ sinkByRoot,
14377
+ ownersBySummary,
14378
+ closureOwnership,
14379
+ unitKeyBySummary: closureFacts.unitKeyBySummary,
14380
+ filesByKey: closureFacts.filesByKey,
14381
+ packsByFile,
14382
+ reusedKept: merged.reusedKept,
14383
+ reusedOwners: merged.reusedOwners,
14384
+ // Everything after the reused prefix, the passes' own
14385
+ // additions (markers, schema documents) included.
14386
+ fresh: enriched.slice(merged.reusedKept.length)
14387
+ });
14388
+ await cache.write(cacheInput, enriched, attribution);
13396
14389
  } catch {
13397
14390
  }
13398
14391
  });
@@ -13405,7 +14398,7 @@ function createTypeScriptAdapter(config) {
13405
14398
  packs: config.frameworks,
13406
14399
  tallies,
13407
14400
  filesInProject: tsconfigFileList?.length ?? null,
13408
- filesWalked: sourceFiles.length,
14401
+ filesWalked: walkList.length,
13409
14402
  summaries: enriched,
13410
14403
  tsConfigFilePath: config.tsConfigFilePath,
13411
14404
  projectRoot: commonDirectoryOf(
@@ -13415,17 +14408,204 @@ function createTypeScriptAdapter(config) {
13415
14408
  })
13416
14409
  );
13417
14410
  }
13418
- nameSummaries(enriched, {
13419
- workspace: config.workspace ?? workspaceNameFor(
13420
- commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
13421
- ),
13422
- projectRoot: commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
13423
- });
13424
- return enriched;
14411
+ return named(
14412
+ enriched,
14413
+ config.workspace,
14414
+ runRoot ?? commonDirectoryOf(sourceFiles.map((f) => f.getFilePath()))
14415
+ );
13425
14416
  }
13426
14417
  };
13427
14418
  }
13428
- function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
14419
+ function mountAssumptionsAgree(record, index) {
14420
+ return Object.entries(record.mountPrefixes).every(
14421
+ ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
14422
+ );
14423
+ }
14424
+ function attributeReachedSummaries(seeds, reached, facts, owners) {
14425
+ const adjacency = /* @__PURE__ */ new Map();
14426
+ for (const [from, to] of facts.db.facts("calls")) {
14427
+ const key = String(from);
14428
+ const bucket = adjacency.get(key) ?? [];
14429
+ bucket.push(String(to));
14430
+ adjacency.set(key, bucket);
14431
+ }
14432
+ const seedKeysByRoot = /* @__PURE__ */ new Map();
14433
+ for (const seed of seeds) {
14434
+ const key = facts.unitKeyBySummary.get(seed);
14435
+ if (key === void 0) {
14436
+ continue;
14437
+ }
14438
+ for (const root of owners.get(seed) ?? []) {
14439
+ const bucket = seedKeysByRoot.get(root) ?? [];
14440
+ bucket.push(key);
14441
+ seedKeysByRoot.set(root, bucket);
14442
+ }
14443
+ }
14444
+ const reachableByRoot = /* @__PURE__ */ new Map();
14445
+ const rootsByKey = /* @__PURE__ */ new Map();
14446
+ for (const [root, seedKeys] of seedKeysByRoot) {
14447
+ const seen = new Set(seedKeys);
14448
+ const queue = [...seedKeys];
14449
+ while (queue.length > 0) {
14450
+ const key = queue.pop();
14451
+ if (key === void 0) {
14452
+ continue;
14453
+ }
14454
+ for (const next of adjacency.get(key) ?? []) {
14455
+ if (!seen.has(next)) {
14456
+ seen.add(next);
14457
+ queue.push(next);
14458
+ }
14459
+ }
14460
+ const keyRoots = rootsByKey.get(key) ?? /* @__PURE__ */ new Set();
14461
+ keyRoots.add(root);
14462
+ rootsByKey.set(key, keyRoots);
14463
+ }
14464
+ reachableByRoot.set(root, seen);
14465
+ }
14466
+ for (const summary of reached) {
14467
+ const key = facts.unitKeyBySummary.get(summary);
14468
+ if (key === void 0) {
14469
+ continue;
14470
+ }
14471
+ const keyRoots = rootsByKey.get(key);
14472
+ if (keyRoots !== void 0 && keyRoots.size > 0) {
14473
+ owners.set(summary, new Set(keyRoots));
14474
+ }
14475
+ }
14476
+ return { reachableByRoot, rootsByKey };
14477
+ }
14478
+ function fileOfNodeKey(key) {
14479
+ const cut = key.lastIndexOf(":");
14480
+ return cut <= 0 ? null : key.slice(0, cut);
14481
+ }
14482
+ function summaryMergeKey(summary) {
14483
+ return JSON.stringify([
14484
+ summary.location.file,
14485
+ summary.location.range.start,
14486
+ summary.location.range.end,
14487
+ summary.kind,
14488
+ summary.identity.name,
14489
+ summary.identity.exportPath,
14490
+ summary.identity.boundaryBinding
14491
+ ]);
14492
+ }
14493
+ function mergeWithReused(reused, fresh, ownersBySummary) {
14494
+ if (reused === null) {
14495
+ return { summaries: fresh, reusedKept: [], reusedOwners: [] };
14496
+ }
14497
+ const freshByKey = /* @__PURE__ */ new Map();
14498
+ for (const summary of fresh) {
14499
+ freshByKey.set(summaryMergeKey(summary), summary);
14500
+ }
14501
+ const reusedKept = [];
14502
+ const reusedOwners = [];
14503
+ reused.summaries.forEach((summary, i) => {
14504
+ const owners = reused.owners[i] ?? [];
14505
+ const supersededBy = freshByKey.get(summaryMergeKey(summary));
14506
+ if (supersededBy !== void 0) {
14507
+ const freshOwners = ownersBySummary.get(supersededBy) ?? /* @__PURE__ */ new Set();
14508
+ for (const owner of owners) {
14509
+ freshOwners.add(owner);
14510
+ }
14511
+ ownersBySummary.set(supersededBy, freshOwners);
14512
+ return;
14513
+ }
14514
+ reusedKept.push(summary);
14515
+ reusedOwners.push(owners);
14516
+ });
14517
+ return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
14518
+ }
14519
+ function readsRunLevelJoins(summary) {
14520
+ if (readSourceDocumentMetadata(summary) !== void 0) {
14521
+ return true;
14522
+ }
14523
+ if (readGraphqlMetadata2(summary)?.client !== void 0) {
14524
+ return true;
14525
+ }
14526
+ const semantics = summary.identity.boundaryBinding?.semantics;
14527
+ return semantics !== void 0 && "operationType" in semantics;
14528
+ }
14529
+ function buildCacheAttribution(args) {
14530
+ const references = createReferenceIndex(
14531
+ args.project.getSourceFiles().filter((sf) => !sf.isDeclarationFile())
14532
+ );
14533
+ const packNamesByPath = /* @__PURE__ */ new Map();
14534
+ for (const [sf, packs] of args.packsByFile) {
14535
+ packNamesByPath.set(
14536
+ sf.getFilePath(),
14537
+ packs.map((p) => p.name)
14538
+ );
14539
+ }
14540
+ const summariesByRoot = /* @__PURE__ */ new Map();
14541
+ for (const summary of args.fresh) {
14542
+ for (const root of args.ownersBySummary.get(summary) ?? []) {
14543
+ const bucket = summariesByRoot.get(root) ?? [];
14544
+ bucket.push(summary);
14545
+ summariesByRoot.set(root, bucket);
14546
+ }
14547
+ }
14548
+ const roots = [];
14549
+ for (const [rootPath, sink] of args.sinkByRoot) {
14550
+ const deps = new Set(sink.files);
14551
+ for (const dep of references.directOf(rootPath)) {
14552
+ deps.add(dep);
14553
+ }
14554
+ let cacheable = true;
14555
+ for (const summary of summariesByRoot.get(rootPath) ?? []) {
14556
+ deps.add(summary.location.file);
14557
+ if (readsRunLevelJoins(summary)) {
14558
+ cacheable = false;
14559
+ }
14560
+ }
14561
+ const reachable = args.closureOwnership?.reachableByRoot.get(rootPath) ?? [];
14562
+ for (const key of reachable) {
14563
+ const keyFile = fileOfNodeKey(key);
14564
+ if (keyFile !== null) {
14565
+ deps.add(keyFile);
14566
+ for (const hop of references.directOf(keyFile)) {
14567
+ deps.add(hop);
14568
+ }
14569
+ }
14570
+ for (const filePath of args.filesByKey?.get(key) ?? []) {
14571
+ deps.add(filePath);
14572
+ }
14573
+ }
14574
+ deps.delete(rootPath);
14575
+ roots.push({
14576
+ path: rootPath,
14577
+ cacheable,
14578
+ deps: [...deps].sort(),
14579
+ claims: sink.claims,
14580
+ mountPrefixes: Object.fromEntries(sink.mountPrefixes),
14581
+ packs: packNamesByPath.get(rootPath) ?? []
14582
+ });
14583
+ }
14584
+ if (args.plan !== null) {
14585
+ for (const rootPath of args.validRoots) {
14586
+ const record = args.plan.roots.get(rootPath);
14587
+ if (record !== void 0) {
14588
+ roots.push(record);
14589
+ }
14590
+ }
14591
+ }
14592
+ const reusedOwners = args.reusedKept.map((summary, i) => {
14593
+ const combined = new Set(args.reusedOwners[i] ?? []);
14594
+ const key = args.unitKeyBySummary.get(summary);
14595
+ if (key !== void 0) {
14596
+ for (const root of args.closureOwnership?.rootsByKey.get(key) ?? []) {
14597
+ combined.add(root);
14598
+ }
14599
+ }
14600
+ return [...combined];
14601
+ });
14602
+ const owners = [
14603
+ ...reusedOwners,
14604
+ ...args.fresh.map((s) => [...args.ownersBySummary.get(s) ?? []])
14605
+ ];
14606
+ return { roots, owners };
14607
+ }
14608
+ function synthesizeSubUnits(summaries, project, frameworks, options, tallies, onCreated) {
13429
14609
  const packByRecognition = /* @__PURE__ */ new Map();
13430
14610
  for (const pack of frameworks) {
13431
14611
  packByRecognition.set(pack.name, pack);
@@ -13468,6 +14648,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13468
14648
  );
13469
14649
  if (summary !== null) {
13470
14650
  synthesized.push(summary);
14651
+ onCreated?.(summary, parent);
13471
14652
  }
13472
14653
  }
13473
14654
  }
@@ -13476,14 +14657,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13476
14657
  var DEFAULT_SUB_UNIT_TERMINALS = [
13477
14658
  { kind: "return", match: { type: "returnStatement" }, extraction: {} },
13478
14659
  { kind: "throw", match: { type: "throwExpression" }, extraction: {} },
13479
- // A callback routinely falls off the end returning `undefined`, so
13480
- // fall-through gives it a default transition. HTTP packs do not opt in:
13481
- // a handler that sends no response should come out with none.
13482
- {
13483
- kind: "return",
13484
- match: { type: "functionFallthrough" },
13485
- extraction: {}
13486
- }
14660
+ FUNCTION_FALLTHROUGH_TERMINAL
13487
14661
  ];
13488
14662
  var DEFAULT_SUB_UNIT_INPUT_MAPPING = {
13489
14663
  type: "positionalParams",
@@ -13673,7 +14847,7 @@ function readStringArg(arg) {
13673
14847
  }
13674
14848
  return candidate.value;
13675
14849
  }
13676
- function rootIdentifier(node) {
14850
+ function rootIdentifier2(node) {
13677
14851
  let current2 = node;
13678
14852
  while (Node55.isPropertyAccessExpression(current2)) {
13679
14853
  current2 = current2.getExpression();
@@ -13681,10 +14855,165 @@ function rootIdentifier(node) {
13681
14855
  return Node55.isIdentifier(current2) ? current2 : null;
13682
14856
  }
13683
14857
 
14858
+ // src/facts/why.ts
14859
+ import path11 from "path";
14860
+ import { Node as Node56, Project as Project3, SyntaxKind as SyntaxKind12 } from "ts-morph";
14861
+ import { explainResolutionProof, renderExplanation } from "@suss/resolution";
14862
+ var JS_PHRASES = {
14863
+ bind: ({ tuple, describe }) => ({
14864
+ reason: `${describe(tuple[0])} is a .bind of ${describe(tuple[1])}, which runs the same function`
14865
+ })
14866
+ };
14867
+ var WhySession = class {
14868
+ project;
14869
+ store;
14870
+ root;
14871
+ constructor(options) {
14872
+ this.root = path11.resolve(options.dir);
14873
+ const tsconfig = findNearestTsconfig(this.root);
14874
+ this.project = tsconfig === null ? createProjectWithoutTsconfig(this.root).project : new Project3({ tsConfigFilePath: tsconfig });
14875
+ this.store = new ResolutionStore();
14876
+ }
14877
+ /**
14878
+ * The smallest expression on `line` of `file` whose text is exactly
14879
+ * `text`, or null. Spelled from the person's side: the name as
14880
+ * written, not a node id.
14881
+ */
14882
+ findExpression(file, line, text) {
14883
+ const sourceFile = this.sourceFileAt(file);
14884
+ if (sourceFile === null) {
14885
+ return null;
14886
+ }
14887
+ let found = null;
14888
+ sourceFile.forEachDescendant((node) => {
14889
+ if (Node56.isExpression(node) && node.getStartLineNumber() === line && node.getText() === text && (found === null || node.getWidth() <= found.getWidth())) {
14890
+ found = node;
14891
+ }
14892
+ });
14893
+ return found;
14894
+ }
14895
+ /**
14896
+ * The callee of the call written as `calleeText` between `startLine`
14897
+ * and `endLine` of `file`, or null. This is how a summary's recorded
14898
+ * callee gets back to its node: the summary keeps source text and a
14899
+ * range, not ids.
14900
+ */
14901
+ findCallee(file, startLine, endLine, calleeText) {
14902
+ const sourceFile = this.sourceFileAt(file);
14903
+ if (sourceFile === null) {
14904
+ return null;
14905
+ }
14906
+ for (const call of sourceFile.getDescendantsOfKind(
14907
+ SyntaxKind12.CallExpression
14908
+ )) {
14909
+ const line = call.getStartLineNumber();
14910
+ if (line < startLine || line > endLine) {
14911
+ continue;
14912
+ }
14913
+ if (call.getExpression().getText() === calleeText) {
14914
+ return call.getExpression();
14915
+ }
14916
+ }
14917
+ return null;
14918
+ }
14919
+ /**
14920
+ * Why `value` resolves to what it does: the witness proof, flattened
14921
+ * to the chain and rendered. Null when the value does not resolve,
14922
+ * which the caller says in its own words.
14923
+ */
14924
+ explain(value, options = {}) {
14925
+ const explained = this.store.explainCallable(value, options);
14926
+ if (explained === null) {
14927
+ return null;
14928
+ }
14929
+ const describe = (atom) => {
14930
+ const node = explained.nodeFor(atom);
14931
+ if (node === void 0) {
14932
+ return this.displayPath(String(atom));
14933
+ }
14934
+ const at = this.locate(node);
14935
+ return `${at.name} (${at.file}:${at.line})`;
14936
+ };
14937
+ const explanation = explainResolutionProof(explained.proof, {
14938
+ describe,
14939
+ phrases: JS_PHRASES
14940
+ });
14941
+ if (explanation === null) {
14942
+ return null;
14943
+ }
14944
+ return {
14945
+ explanation,
14946
+ chain: explanation.atoms.map((atom) => describe(atom)),
14947
+ lines: renderExplanation(explanation, describe),
14948
+ target: this.locate(explained.target),
14949
+ stats: explained.stats
14950
+ };
14951
+ }
14952
+ /** Where a found node is, in the terms an answer prints. */
14953
+ locate(node) {
14954
+ return {
14955
+ name: displayNameOf(node),
14956
+ file: this.displayPath(node.getSourceFile().getFilePath()),
14957
+ line: node.getStartLineNumber()
14958
+ };
14959
+ }
14960
+ sourceFileAt(file) {
14961
+ const absolute = path11.resolve(this.root, file);
14962
+ const loaded = this.project.getSourceFile(absolute);
14963
+ if (loaded !== void 0) {
14964
+ return loaded;
14965
+ }
14966
+ try {
14967
+ return this.project.addSourceFileAtPath(absolute);
14968
+ } catch {
14969
+ return null;
14970
+ }
14971
+ }
14972
+ /** A module key: a file path said relative to the root, or a package name. */
14973
+ displayPath(key) {
14974
+ if (key.startsWith(this.root)) {
14975
+ return path11.relative(this.root, key);
14976
+ }
14977
+ return key;
14978
+ }
14979
+ };
14980
+ function displayNameOf(node) {
14981
+ if (Node56.isIdentifier(node) || Node56.isPropertyAccessExpression(node)) {
14982
+ return node.getText();
14983
+ }
14984
+ if (Node56.isCallExpression(node) || Node56.isNewExpression(node)) {
14985
+ return `${node.getExpression()?.getText() ?? "call"}(...)`;
14986
+ }
14987
+ if (Node56.isFunctionDeclaration(node) || Node56.isClassDeclaration(node) || Node56.isMethodDeclaration(node) || Node56.isVariableDeclaration(node)) {
14988
+ const name = node.getName();
14989
+ if (name !== void 0) {
14990
+ return name;
14991
+ }
14992
+ }
14993
+ if (Node56.isArrowFunction(node) || Node56.isFunctionExpression(node)) {
14994
+ const parent = node.getParent();
14995
+ if (parent !== void 0 && Node56.isVariableDeclaration(parent)) {
14996
+ return parent.getName();
14997
+ }
14998
+ }
14999
+ const text = node.getText().split("\n")[0].trim();
15000
+ return text.length > 40 ? `${text.slice(0, 40)}...` : text;
15001
+ }
15002
+
13684
15003
  // src/packHealth.ts
13685
15004
  function stagesOf(funnel) {
13686
15005
  const stages = [];
13687
15006
  const gateSaysSomething = funnel.gates.length > 0 && funnel.unresolvedGates.length === 0;
15007
+ if (funnel.recognizes && !funnel.discovers && gateSaysSomething) {
15008
+ stages.push({
15009
+ from: {
15010
+ name: "unit bodies to look inside",
15011
+ count: funnel.unitsInGatedFiles
15012
+ },
15013
+ to: { name: "effects recognized", count: funnel.effectsRecognized },
15014
+ meaning: "its import gate found the library and it matched nothing in the bodies it saw"
15015
+ });
15016
+ }
13688
15017
  if (funnel.discovers && gateSaysSomething) {
13689
15018
  stages.push({
13690
15019
  from: { name: "candidate files", count: funnel.candidateFiles },
@@ -13794,7 +15123,8 @@ function formatPackHealth(checks, audiences) {
13794
15123
  }
13795
15124
 
13796
15125
  // src/resolve/readName.ts
13797
- import { Node as Node56, SyntaxKind as SyntaxKind12 } from "ts-morph";
15126
+ import { Node as Node57, SyntaxKind as SyntaxKind13 } from "ts-morph";
15127
+ import { patternHole, referenceName } from "@suss/behavioral-ir";
13798
15128
  var MAX_HELPER_HOPS = 2;
13799
15129
  var UNNAMED_HOLE = "param";
13800
15130
  function readName(expr, options = {}) {
@@ -13808,6 +15138,14 @@ function readName(expr, options = {}) {
13808
15138
  if (name !== null || options.unsettled !== "reference") {
13809
15139
  return name;
13810
15140
  }
15141
+ const reference = referenceIn(expr);
15142
+ return reference === null ? null : referenceName(reference);
15143
+ }
15144
+ function referenceIn(expr) {
15145
+ const fromParameter = parameterPath(expr);
15146
+ if (fromParameter !== null) {
15147
+ return fromParameter;
15148
+ }
13811
15149
  const asked = holeName(expr, {
13812
15150
  resolve: () => null,
13813
15151
  bindings: /* @__PURE__ */ new Map(),
@@ -13815,7 +15153,25 @@ function readName(expr, options = {}) {
13815
15153
  seen: /* @__PURE__ */ new Set(),
13816
15154
  insideHole: true
13817
15155
  });
13818
- return asked === UNNAMED_HOLE ? null : `{${asked}}`;
15156
+ return asked === UNNAMED_HOLE ? null : { root: asked, fields: [] };
15157
+ }
15158
+ function parameterPath(expr) {
15159
+ const fields = [];
15160
+ let step = expr;
15161
+ while (Node57.isPropertyAccessExpression(step)) {
15162
+ fields.unshift(step.getName());
15163
+ step = step.getExpression();
15164
+ }
15165
+ if (!Node57.isIdentifier(step) || !isParameter(step)) {
15166
+ return null;
15167
+ }
15168
+ return { root: step.getText(), fields };
15169
+ }
15170
+ function isParameter(identifier) {
15171
+ const declarations = identifier.getSymbol()?.getDeclarations() ?? [];
15172
+ return declarations.some(
15173
+ (declaration) => Node57.isParameterDeclaration(declaration)
15174
+ );
13819
15175
  }
13820
15176
  function read(expr, ctx) {
13821
15177
  if (ctx.seen.has(expr)) {
@@ -13823,35 +15179,52 @@ function read(expr, ctx) {
13823
15179
  }
13824
15180
  const seen = new Set(ctx.seen).add(expr);
13825
15181
  const inner = { ...ctx, seen };
13826
- if (Node56.isStringLiteral(expr) || Node56.isNoSubstitutionTemplateLiteral(expr)) {
15182
+ if (Node57.isStringLiteral(expr) || Node57.isNoSubstitutionTemplateLiteral(expr)) {
13827
15183
  return expr.getLiteralValue();
13828
15184
  }
13829
- if (Node56.isNumericLiteral(expr)) {
15185
+ if (Node57.isNumericLiteral(expr)) {
13830
15186
  return expr.getLiteralText();
13831
15187
  }
13832
- if (Node56.isTemplateExpression(expr)) {
15188
+ if (Node57.isTemplateExpression(expr)) {
13833
15189
  return fromTemplate(expr, inner);
13834
15190
  }
13835
- if (Node56.isParenthesizedExpression(expr)) {
15191
+ if (Node57.isParenthesizedExpression(expr)) {
13836
15192
  return read(expr.getExpression(), inner);
13837
15193
  }
13838
- if (Node56.isBinaryExpression(expr)) {
15194
+ if (Node57.isBinaryExpression(expr)) {
13839
15195
  return fromBinary(expr, inner);
13840
15196
  }
13841
- if (Node56.isCallExpression(expr)) {
15197
+ if (Node57.isCallExpression(expr)) {
13842
15198
  return fromCall(expr, inner);
13843
15199
  }
13844
- if (Node56.isIdentifier(expr)) {
15200
+ if (Node57.isIdentifier(expr)) {
13845
15201
  const only = boundOnce(expr, ctx);
13846
15202
  if (only !== null) {
13847
15203
  return read(only.node, { ...inner, bindings: only.scope });
13848
15204
  }
13849
15205
  }
13850
15206
  const written = ctx.resolve(expr);
13851
- return written === null || written === expr ? null : read(written, inner);
15207
+ if (written === null || written === expr) {
15208
+ return null;
15209
+ }
15210
+ if (ctx.insideHole && isFallbackBranch(written)) {
15211
+ return null;
15212
+ }
15213
+ return read(written, inner);
15214
+ }
15215
+ function isFallbackBranch(node) {
15216
+ let step = node.getParent();
15217
+ while (step !== void 0 && Node57.isParenthesizedExpression(step)) {
15218
+ step = step.getParent();
15219
+ }
15220
+ if (step === void 0 || !Node57.isBinaryExpression(step)) {
15221
+ return false;
15222
+ }
15223
+ const operator = step.getOperatorToken().getKind();
15224
+ return operator === SyntaxKind13.BarBarToken || operator === SyntaxKind13.QuestionQuestionToken;
13852
15225
  }
13853
15226
  function fromTemplate(expr, ctx) {
13854
- if (!Node56.isTemplateExpression(expr)) {
15227
+ if (!Node57.isTemplateExpression(expr)) {
13855
15228
  return null;
13856
15229
  }
13857
15230
  let name = expr.getHead().getLiteralText();
@@ -13862,28 +15235,28 @@ function fromTemplate(expr, ctx) {
13862
15235
  return name;
13863
15236
  }
13864
15237
  function fromBinary(expr, ctx) {
13865
- if (!Node56.isBinaryExpression(expr)) {
15238
+ if (!Node57.isBinaryExpression(expr)) {
13866
15239
  return null;
13867
15240
  }
13868
15241
  const operator = expr.getOperatorToken().getKind();
13869
- if (operator === SyntaxKind12.BarBarToken || operator === SyntaxKind12.QuestionQuestionToken) {
15242
+ if (operator === SyntaxKind13.BarBarToken || operator === SyntaxKind13.QuestionQuestionToken) {
13870
15243
  return ctx.insideHole ? null : read(expr.getRight(), ctx);
13871
15244
  }
13872
- if (operator !== SyntaxKind12.PlusToken) {
15245
+ if (operator !== SyntaxKind13.PlusToken) {
13873
15246
  return null;
13874
15247
  }
13875
15248
  const parts = { ...ctx, insideHole: true };
13876
15249
  return readOrHole(expr.getLeft(), parts) + readOrHole(expr.getRight(), parts);
13877
15250
  }
13878
15251
  function fromCall(expr, ctx) {
13879
- if (!Node56.isCallExpression(expr)) {
15252
+ if (!Node57.isCallExpression(expr)) {
13880
15253
  return null;
13881
15254
  }
13882
15255
  const callee = expr.getExpression();
13883
- if (Node56.isPropertyAccessExpression(callee) && callee.getName() === "join") {
15256
+ if (Node57.isPropertyAccessExpression(callee) && callee.getName() === "join") {
13884
15257
  return fromJoin(callee.getExpression(), expr.getArguments(), ctx);
13885
15258
  }
13886
- if (Node56.isIdentifier(callee) && callee.getText() === "String") {
15259
+ if (Node57.isIdentifier(callee) && callee.getText() === "String") {
13887
15260
  const only = expr.getArguments()[0];
13888
15261
  return only === void 0 ? null : readOrHole(only, ctx);
13889
15262
  }
@@ -13902,7 +15275,7 @@ function fromCall(expr, ctx) {
13902
15275
  }
13903
15276
  function fromJoin(subject, args, ctx) {
13904
15277
  const separator = args[0];
13905
- if (separator === void 0 || !Node56.isStringLiteral(separator)) {
15278
+ if (separator === void 0 || !Node57.isStringLiteral(separator)) {
13906
15279
  return null;
13907
15280
  }
13908
15281
  const parts = elementsOf(subject, ctx);
@@ -13918,17 +15291,17 @@ function fromJoin(subject, args, ctx) {
13918
15291
  ).join(separator.getLiteralValue());
13919
15292
  }
13920
15293
  function elementsOf(subject, ctx) {
13921
- if (Node56.isArrayLiteralExpression(subject)) {
15294
+ if (Node57.isArrayLiteralExpression(subject)) {
13922
15295
  return subject.getElements().map((element) => here(element, ctx));
13923
15296
  }
13924
- if (Node56.isCallExpression(subject)) {
15297
+ if (Node57.isCallExpression(subject)) {
13925
15298
  const callee = subject.getExpression();
13926
- if (Node56.isPropertyAccessExpression(callee) && callee.getName() === "map") {
15299
+ if (Node57.isPropertyAccessExpression(callee) && callee.getName() === "map") {
13927
15300
  return elementsOf(callee.getExpression(), ctx);
13928
15301
  }
13929
15302
  return null;
13930
15303
  }
13931
- if (!Node56.isIdentifier(subject)) {
15304
+ if (!Node57.isIdentifier(subject)) {
13932
15305
  return null;
13933
15306
  }
13934
15307
  const bound = ctx.bindings.get(subject.getText());
@@ -13936,7 +15309,7 @@ function elementsOf(subject, ctx) {
13936
15309
  return null;
13937
15310
  }
13938
15311
  const only = bound.length === 1 ? bound[0] : void 0;
13939
- return only !== void 0 && Node56.isArrayLiteralExpression(only.node) ? only.node.getElements().map((element) => here(element, ctx)) : bound;
15312
+ return only !== void 0 && Node57.isArrayLiteralExpression(only.node) ? only.node.getElements().map((element) => here(element, ctx)) : bound;
13940
15313
  }
13941
15314
  function singleReturnBody(callee, ctx) {
13942
15315
  const declaration = declarationOf2(callee, ctx);
@@ -13951,22 +15324,22 @@ function singleReturnBody(callee, ctx) {
13951
15324
  if (body === void 0) {
13952
15325
  return null;
13953
15326
  }
13954
- if (Node56.isExpression(body)) {
15327
+ if (Node57.isExpression(body)) {
13955
15328
  return { parameters, returned: body };
13956
15329
  }
13957
- if (!Node56.isBlock(body)) {
15330
+ if (!Node57.isBlock(body)) {
13958
15331
  return null;
13959
15332
  }
13960
15333
  const statements = body.getStatements();
13961
15334
  const only = statements[0];
13962
- if (statements.length !== 1 || only === void 0 || !Node56.isReturnStatement(only)) {
15335
+ if (statements.length !== 1 || only === void 0 || !Node57.isReturnStatement(only)) {
13963
15336
  return null;
13964
15337
  }
13965
15338
  const returned = only.getExpression();
13966
15339
  return returned === void 0 ? null : { parameters, returned };
13967
15340
  }
13968
15341
  function declarationOf2(callee, ctx) {
13969
- const symbol = Node56.isPropertyAccessExpression(callee) ? callee.getNameNode().getSymbol() : callee.getSymbol();
15342
+ const symbol = Node57.isPropertyAccessExpression(callee) ? callee.getNameNode().getSymbol() : callee.getSymbol();
13970
15343
  const declarations = symbol?.getAliasedSymbol()?.getDeclarations() ?? symbol?.getDeclarations() ?? [];
13971
15344
  for (const declaration of declarations) {
13972
15345
  const callable = asCallable(declaration);
@@ -13978,14 +15351,14 @@ function declarationOf2(callee, ctx) {
13978
15351
  return written === null || written === callee ? null : declarationOf2(written, { ...ctx, resolve: () => null });
13979
15352
  }
13980
15353
  function asCallable(declaration) {
13981
- if (Node56.isFunctionDeclaration(declaration) || Node56.isMethodDeclaration(declaration) || Node56.isArrowFunction(declaration) || Node56.isFunctionExpression(declaration)) {
15354
+ if (Node57.isFunctionDeclaration(declaration) || Node57.isMethodDeclaration(declaration) || Node57.isArrowFunction(declaration) || Node57.isFunctionExpression(declaration)) {
13982
15355
  return declaration;
13983
15356
  }
13984
- if (!Node56.isVariableDeclaration(declaration)) {
15357
+ if (!Node57.isVariableDeclaration(declaration)) {
13985
15358
  return null;
13986
15359
  }
13987
15360
  const initializer = declaration.getInitializer();
13988
- if (initializer !== void 0 && (Node56.isArrowFunction(initializer) || Node56.isFunctionExpression(initializer))) {
15361
+ if (initializer !== void 0 && (Node57.isArrowFunction(initializer) || Node57.isFunctionExpression(initializer))) {
13989
15362
  return initializer;
13990
15363
  }
13991
15364
  return null;
@@ -14007,22 +15380,22 @@ function here(node, ctx) {
14007
15380
  return { node, scope: ctx.bindings };
14008
15381
  }
14009
15382
  function boundOnce(expr, ctx) {
14010
- const bound = Node56.isIdentifier(expr) ? ctx.bindings.get(expr.getText()) : void 0;
15383
+ const bound = Node57.isIdentifier(expr) ? ctx.bindings.get(expr.getText()) : void 0;
14011
15384
  return bound !== void 0 && bound.length === 1 ? bound[0] : null;
14012
15385
  }
14013
15386
  function readOrHole(expr, ctx) {
14014
15387
  const name = read(expr, ctx);
14015
- return name ?? `{${holeName(expr, ctx)}}`;
15388
+ return name ?? patternHole(holeName(expr, ctx));
14016
15389
  }
14017
15390
  function holeName(expr, ctx) {
14018
15391
  const bound = boundOnce(expr, ctx);
14019
15392
  if (bound !== null) {
14020
15393
  return holeName(bound.node, { ...ctx, bindings: bound.scope });
14021
15394
  }
14022
- if (Node56.isIdentifier(expr)) {
15395
+ if (Node57.isIdentifier(expr)) {
14023
15396
  return expr.getText();
14024
15397
  }
14025
- if (Node56.isPropertyAccessExpression(expr)) {
15398
+ if (Node57.isPropertyAccessExpression(expr)) {
14026
15399
  return expr.getName();
14027
15400
  }
14028
15401
  return UNNAMED_HOLE;
@@ -14030,6 +15403,7 @@ function holeName(expr, ctx) {
14030
15403
  export {
14031
15404
  ADAPTER_VERSION,
14032
15405
  ResolutionStore,
15406
+ WhySession,
14033
15407
  collectAncestorBranches,
14034
15408
  collectClientFieldAccesses,
14035
15409
  computeAdapterPacksDigest,
@@ -14055,5 +15429,6 @@ export {
14055
15429
  readName,
14056
15430
  resolveAliasedSymbol,
14057
15431
  resolveSubject,
14058
- rootIdentifier
15432
+ rootIdentifier2 as rootIdentifier,
15433
+ workspaceRootFor
14059
15434
  };