@suss/adapter-typescript 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1076,24 +1076,64 @@ function collectFallthroughCaseLabels(caseClause) {
1076
1076
  }
1077
1077
 
1078
1078
  // src/paths/pathConditions.ts
1079
- var import_ts_morph8 = require("ts-morph");
1079
+ var import_ts_morph9 = require("ts-morph");
1080
1080
  var import_extractor2 = require("@suss/extractor");
1081
1081
 
1082
- // src/paths/lowering.ts
1082
+ // src/walk/descent.ts
1083
1083
  var import_ts_morph7 = require("ts-morph");
1084
+ var NO_BARRIERS = /* @__PURE__ */ new Set();
1085
+ function isDescentStop(node, func, barriers = NO_BARRIERS) {
1086
+ if (node === func) {
1087
+ return false;
1088
+ }
1089
+ if (import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node)) {
1090
+ return true;
1091
+ }
1092
+ if (import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node)) {
1093
+ return barriers.has(node);
1094
+ }
1095
+ return false;
1096
+ }
1097
+ function isInlineCallback(node, func, barriers = NO_BARRIERS) {
1098
+ if (node === func) {
1099
+ return false;
1100
+ }
1101
+ if (!(import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node))) {
1102
+ return false;
1103
+ }
1104
+ return !barriers.has(node);
1105
+ }
1106
+ function isModuleScopeStop(node) {
1107
+ return import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node) || import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isClassDeclaration(node) || import_ts_morph7.Node.isClassExpression(node);
1108
+ }
1109
+ function crossesNestedFunctionScope(node, func) {
1110
+ let current2 = node;
1111
+ while (current2 !== void 0 && current2 !== func) {
1112
+ if (startsItsOwnScope(current2)) {
1113
+ return true;
1114
+ }
1115
+ current2 = current2.getParent();
1116
+ }
1117
+ return false;
1118
+ }
1119
+ function startsItsOwnScope(node) {
1120
+ return import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isMethodDeclaration(node) || import_ts_morph7.Node.isGetAccessorDeclaration(node) || import_ts_morph7.Node.isSetAccessorDeclaration(node) || import_ts_morph7.Node.isConstructorDeclaration(node);
1121
+ }
1122
+
1123
+ // src/paths/lowering.ts
1124
+ var import_ts_morph8 = require("ts-morph");
1084
1125
  var import_extractor = require("@suss/extractor");
1085
- var isFunctionBoundary = (node) => import_ts_morph7.Node.isFunctionDeclaration(node) || import_ts_morph7.Node.isFunctionExpression(node) || import_ts_morph7.Node.isArrowFunction(node) || import_ts_morph7.Node.isMethodDeclaration(node);
1086
1126
  function exitKindOf(stmt) {
1087
1127
  let sawReturn = false;
1088
1128
  let sawThrow = false;
1089
1129
  const visit = (node) => {
1090
- if (node !== stmt && isFunctionBoundary(node)) {
1130
+ if (node !== stmt && startsItsOwnScope(node)) {
1091
1131
  return;
1092
1132
  }
1093
- if (import_ts_morph7.Node.isReturnStatement(node)) {
1133
+ if (import_ts_morph8.Node.isReturnStatement(node)) {
1094
1134
  sawReturn = true;
1095
1135
  }
1096
- if (import_ts_morph7.Node.isThrowStatement(node)) {
1136
+ if (import_ts_morph8.Node.isThrowStatement(node)) {
1097
1137
  sawThrow = true;
1098
1138
  }
1099
1139
  for (const child of node.getChildren()) {
@@ -1110,14 +1150,14 @@ var statementsOf = (stmt) => {
1110
1150
  if (stmt === void 0) {
1111
1151
  return [];
1112
1152
  }
1113
- if (import_ts_morph7.Node.isBlock(stmt)) {
1153
+ if (import_ts_morph8.Node.isBlock(stmt)) {
1114
1154
  return stmt.getStatements();
1115
1155
  }
1116
1156
  return [stmt];
1117
1157
  };
1118
- var isLoop = (stmt) => import_ts_morph7.Node.isForOfStatement(stmt) || import_ts_morph7.Node.isForInStatement(stmt) || import_ts_morph7.Node.isForStatement(stmt) || import_ts_morph7.Node.isWhileStatement(stmt) || import_ts_morph7.Node.isDoStatement(stmt);
1158
+ var isLoop = (stmt) => import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt) || import_ts_morph8.Node.isForStatement(stmt) || import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt);
1119
1159
  function getLoopBody(stmt) {
1120
- if (import_ts_morph7.Node.isForOfStatement(stmt) || import_ts_morph7.Node.isForInStatement(stmt) || import_ts_morph7.Node.isForStatement(stmt) || import_ts_morph7.Node.isWhileStatement(stmt) || import_ts_morph7.Node.isDoStatement(stmt)) {
1160
+ if (import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt) || import_ts_morph8.Node.isForStatement(stmt) || import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt)) {
1121
1161
  return stmt.getStatement();
1122
1162
  }
1123
1163
  return void 0;
@@ -1132,37 +1172,97 @@ var conditionOf = (expr) => ({
1132
1172
  sourceText: expr.getText(),
1133
1173
  expression: expr
1134
1174
  });
1135
- function lowerStatement(stmt, rawToStructured) {
1136
- const structured = buildStructured(stmt, rawToStructured);
1137
- rawToStructured.set(stmt, structured);
1175
+ function lowerStatement(stmt, ctx) {
1176
+ const structured = {
1177
+ ...buildStructured(stmt, ctx),
1178
+ callbacks: lowerCallbacksIn(ownExpressionsOf(stmt), ctx)
1179
+ };
1180
+ ctx.rawToStructured.set(stmt, structured);
1138
1181
  return structured;
1139
1182
  }
1140
- function lowerList(stmts, rawToStructured) {
1141
- return spliceBlocks(stmts).map((s) => lowerStatement(s, rawToStructured));
1183
+ function lowerList(stmts, ctx) {
1184
+ return spliceBlocks(stmts).map((s) => lowerStatement(s, ctx));
1185
+ }
1186
+ function ownExpressionsOf(stmt) {
1187
+ if (import_ts_morph8.Node.isIfStatement(stmt) || import_ts_morph8.Node.isSwitchStatement(stmt)) {
1188
+ return [stmt.getExpression()];
1189
+ }
1190
+ if (import_ts_morph8.Node.isWhileStatement(stmt) || import_ts_morph8.Node.isDoStatement(stmt)) {
1191
+ return [stmt.getExpression()];
1192
+ }
1193
+ if (import_ts_morph8.Node.isForOfStatement(stmt) || import_ts_morph8.Node.isForInStatement(stmt)) {
1194
+ return [stmt.getInitializer(), stmt.getExpression()];
1195
+ }
1196
+ if (import_ts_morph8.Node.isForStatement(stmt)) {
1197
+ return [
1198
+ stmt.getInitializer(),
1199
+ stmt.getCondition(),
1200
+ stmt.getIncrementor()
1201
+ ].filter((n) => n !== void 0);
1202
+ }
1203
+ if (import_ts_morph8.Node.isTryStatement(stmt)) {
1204
+ return [];
1205
+ }
1206
+ if (import_ts_morph8.Node.isReturnStatement(stmt) || import_ts_morph8.Node.isThrowStatement(stmt)) {
1207
+ const value = stmt.getExpression();
1208
+ return value === void 0 ? [] : [value];
1209
+ }
1210
+ if (import_ts_morph8.Node.isBreakStatement(stmt) || import_ts_morph8.Node.isContinueStatement(stmt)) {
1211
+ return [];
1212
+ }
1213
+ return [stmt];
1214
+ }
1215
+ function lowerCallbacksIn(parts, ctx) {
1216
+ const bodies = [];
1217
+ const visit = (node, root) => {
1218
+ if (node !== root && isDescentStop(node, ctx.func, ctx.barriers)) {
1219
+ return;
1220
+ }
1221
+ if (node !== root && (import_ts_morph8.Node.isArrowFunction(node) || import_ts_morph8.Node.isFunctionExpression(node)) && isInlineCallback(node, ctx.func, ctx.barriers)) {
1222
+ const body = node.getBody();
1223
+ if (body !== void 0 && import_ts_morph8.Node.isBlock(body)) {
1224
+ bodies.push(lowerList(body.getStatements(), ctx));
1225
+ return;
1226
+ }
1227
+ } else if (node !== root && startsItsOwnScope(node)) {
1228
+ return;
1229
+ }
1230
+ for (const child of node.getChildren()) {
1231
+ visit(child, root);
1232
+ }
1233
+ };
1234
+ for (const part of parts) {
1235
+ visit(part, part);
1236
+ }
1237
+ return bodies;
1238
+ }
1239
+ function lowerExpressionBodyCallbacks(expression, func, barriers = NO_BARRIERS) {
1240
+ return lowerCallbacksIn([expression], {
1241
+ func,
1242
+ barriers,
1243
+ rawToStructured: /* @__PURE__ */ new Map()
1244
+ });
1142
1245
  }
1143
1246
  function spliceBlocks(stmts) {
1144
1247
  return stmts.flatMap(
1145
- (s) => import_ts_morph7.Node.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1248
+ (s) => import_ts_morph8.Node.isBlock(s) ? spliceBlocks(s.getStatements()) : [s]
1146
1249
  );
1147
1250
  }
1148
- function buildStructured(stmt, rawToStructured) {
1149
- if (import_ts_morph7.Node.isIfStatement(stmt)) {
1251
+ function buildStructured(stmt, ctx) {
1252
+ if (import_ts_morph8.Node.isIfStatement(stmt)) {
1150
1253
  const elseBranch = stmt.getElseStatement();
1151
1254
  return {
1152
1255
  kind: "if",
1153
1256
  condition: conditionOf(stmt.getExpression()),
1154
- thenBody: lowerList(
1155
- statementsOf(stmt.getThenStatement()),
1156
- rawToStructured
1157
- ),
1158
- elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), rawToStructured),
1257
+ thenBody: lowerList(statementsOf(stmt.getThenStatement()), ctx),
1258
+ elseBody: elseBranch === void 0 ? null : lowerList(statementsOf(elseBranch), ctx),
1159
1259
  exitKind: exitKindOf(stmt)
1160
1260
  };
1161
1261
  }
1162
- if (import_ts_morph7.Node.isSwitchStatement(stmt)) {
1262
+ if (import_ts_morph8.Node.isSwitchStatement(stmt)) {
1163
1263
  return {
1164
1264
  kind: "switch",
1165
- groups: lowerSwitchGroups(stmt, rawToStructured),
1265
+ groups: lowerSwitchGroups(stmt, ctx),
1166
1266
  exitKind: exitKindOf(stmt)
1167
1267
  };
1168
1268
  }
@@ -1170,36 +1270,36 @@ function buildStructured(stmt, rawToStructured) {
1170
1270
  return {
1171
1271
  kind: "loop",
1172
1272
  condition: { sourceText: loopHeaderText(stmt), expression: null },
1173
- body: lowerList(statementsOf(getLoopBody(stmt)), rawToStructured),
1273
+ body: lowerList(statementsOf(getLoopBody(stmt)), ctx),
1174
1274
  exitKind: exitKindOf(stmt)
1175
1275
  };
1176
1276
  }
1177
- if (import_ts_morph7.Node.isTryStatement(stmt)) {
1277
+ if (import_ts_morph8.Node.isTryStatement(stmt)) {
1178
1278
  const catchClause = stmt.getCatchClause();
1179
1279
  const finallyBlock = stmt.getFinallyBlock();
1180
1280
  return {
1181
1281
  kind: "try",
1182
- tryBody: lowerList(stmt.getTryBlock().getStatements(), rawToStructured),
1183
- catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), rawToStructured),
1184
- finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), rawToStructured),
1282
+ tryBody: lowerList(stmt.getTryBlock().getStatements(), ctx),
1283
+ catchBody: catchClause === void 0 ? null : lowerList(catchClause.getBlock().getStatements(), ctx),
1284
+ finallyBody: finallyBlock === void 0 ? null : lowerList(finallyBlock.getStatements(), ctx),
1185
1285
  exitKind: exitKindOf(stmt)
1186
1286
  };
1187
1287
  }
1188
- if (import_ts_morph7.Node.isReturnStatement(stmt)) {
1288
+ if (import_ts_morph8.Node.isReturnStatement(stmt)) {
1189
1289
  return { kind: "exit", exit: "return", exitKind: exitKindOf(stmt) };
1190
1290
  }
1191
- if (import_ts_morph7.Node.isThrowStatement(stmt)) {
1291
+ if (import_ts_morph8.Node.isThrowStatement(stmt)) {
1192
1292
  return { kind: "exit", exit: "throw", exitKind: exitKindOf(stmt) };
1193
1293
  }
1194
- if (import_ts_morph7.Node.isBreakStatement(stmt)) {
1294
+ if (import_ts_morph8.Node.isBreakStatement(stmt)) {
1195
1295
  return { kind: "exit", exit: "break", exitKind: exitKindOf(stmt) };
1196
1296
  }
1197
- if (import_ts_morph7.Node.isContinueStatement(stmt)) {
1297
+ if (import_ts_morph8.Node.isContinueStatement(stmt)) {
1198
1298
  return { kind: "exit", exit: "continue", exitKind: exitKindOf(stmt) };
1199
1299
  }
1200
1300
  return { kind: "opaque", exitKind: exitKindOf(stmt) };
1201
1301
  }
1202
- function lowerSwitchGroups(stmt, rawToStructured) {
1302
+ function lowerSwitchGroups(stmt, ctx) {
1203
1303
  const switchText = stmt.getExpression().getText();
1204
1304
  const groups = [];
1205
1305
  let pendingLabels = [];
@@ -1208,7 +1308,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1208
1308
  if (sawDefault) {
1209
1309
  throw new import_extractor.UnmodeledFlow("default clause is not last");
1210
1310
  }
1211
- if (import_ts_morph7.Node.isDefaultClause(clause)) {
1311
+ if (import_ts_morph8.Node.isDefaultClause(clause)) {
1212
1312
  sawDefault = true;
1213
1313
  const stmts2 = clause.getStatements();
1214
1314
  if (stmts2.length === 0) {
@@ -1216,11 +1316,11 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1216
1316
  }
1217
1317
  groups.push({
1218
1318
  condition: null,
1219
- ...lowerGroupBody(stmts2, rawToStructured)
1319
+ ...lowerGroupBody(stmts2, ctx)
1220
1320
  });
1221
1321
  continue;
1222
1322
  }
1223
- if (!import_ts_morph7.Node.isCaseClause(clause)) {
1323
+ if (!import_ts_morph8.Node.isCaseClause(clause)) {
1224
1324
  continue;
1225
1325
  }
1226
1326
  pendingLabels.push(clause.getExpression().getText());
@@ -1233,7 +1333,7 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1233
1333
  const sourceText = labels.map((l) => `${switchText} === ${l}`).join(" || ");
1234
1334
  groups.push({
1235
1335
  condition: { sourceText, expression: null },
1236
- ...lowerGroupBody(stmts, rawToStructured)
1336
+ ...lowerGroupBody(stmts, ctx)
1237
1337
  });
1238
1338
  }
1239
1339
  return groups;
@@ -1241,41 +1341,42 @@ function lowerSwitchGroups(stmt, rawToStructured) {
1241
1341
  function validateClauseBreaks(stmts) {
1242
1342
  const last = stmts[stmts.length - 1];
1243
1343
  for (const stmt of stmts) {
1244
- if (stmt === last && import_ts_morph7.Node.isBreakStatement(stmt)) {
1344
+ if (stmt === last && import_ts_morph8.Node.isBreakStatement(stmt)) {
1245
1345
  continue;
1246
1346
  }
1247
- if (import_ts_morph7.Node.isSwitchStatement(stmt) || isLoop(stmt)) {
1347
+ if (import_ts_morph8.Node.isSwitchStatement(stmt) || isLoop(stmt)) {
1248
1348
  continue;
1249
1349
  }
1250
1350
  stmt.forEachDescendant((node, traversal) => {
1251
- if (isFunctionBoundary(node) || import_ts_morph7.Node.isSwitchStatement(node) || isLoop(node)) {
1351
+ if (startsItsOwnScope(node) || import_ts_morph8.Node.isSwitchStatement(node) || isLoop(node)) {
1252
1352
  traversal.skip();
1253
1353
  return;
1254
1354
  }
1255
- if (import_ts_morph7.Node.isBreakStatement(node)) {
1355
+ if (import_ts_morph8.Node.isBreakStatement(node)) {
1256
1356
  throw new import_extractor.UnmodeledFlow("non-trailing break in switch clause");
1257
1357
  }
1258
1358
  });
1259
- if (import_ts_morph7.Node.isBreakStatement(stmt)) {
1359
+ if (import_ts_morph8.Node.isBreakStatement(stmt)) {
1260
1360
  throw new import_extractor.UnmodeledFlow("non-trailing break in switch clause");
1261
1361
  }
1262
1362
  }
1263
1363
  }
1264
- function lowerGroupBody(rawStmts, rawToStructured) {
1364
+ function lowerGroupBody(rawStmts, ctx) {
1265
1365
  const stmts = spliceBlocks(rawStmts);
1266
1366
  validateClauseBreaks(stmts);
1267
1367
  const last = stmts[stmts.length - 1];
1268
- const hasTrailingBreak = last !== void 0 && import_ts_morph7.Node.isBreakStatement(last);
1368
+ const hasTrailingBreak = last !== void 0 && import_ts_morph8.Node.isBreakStatement(last);
1269
1369
  const kept = hasTrailingBreak ? stmts.slice(0, -1) : stmts;
1270
- const body = lowerList(kept, rawToStructured);
1370
+ const body = lowerList(kept, ctx);
1271
1371
  if (hasTrailingBreak && last !== void 0) {
1272
- lowerStatement(last, rawToStructured);
1372
+ lowerStatement(last, ctx);
1273
1373
  }
1274
1374
  return { hasTrailingBreak, body };
1275
1375
  }
1276
- function lowerFunctionBody(body, terminalNodes) {
1376
+ function lowerFunctionBody(body, terminalNodes, func = body, barriers = NO_BARRIERS) {
1277
1377
  const rawToStructured = /* @__PURE__ */ new Map();
1278
- const statements = lowerList(body.getStatements(), rawToStructured);
1378
+ const ctx = { func, barriers, rawToStructured };
1379
+ const statements = lowerList(body.getStatements(), ctx);
1279
1380
  const terminalsByStmt = /* @__PURE__ */ new Map();
1280
1381
  const terminalHomeRaw = /* @__PURE__ */ new Map();
1281
1382
  for (const terminal of terminalNodes) {
@@ -1297,7 +1398,7 @@ function lowerFunctionBody(body, terminalNodes) {
1297
1398
  function enclosingLoweredStatement(terminal, body, rawToStructured) {
1298
1399
  let current2 = terminal;
1299
1400
  while (current2 !== void 0 && current2 !== body) {
1300
- if (import_ts_morph7.Node.isStatement(current2) && rawToStructured.has(current2)) {
1401
+ if (import_ts_morph8.Node.isStatement(current2) && rawToStructured.has(current2)) {
1301
1402
  return current2;
1302
1403
  }
1303
1404
  current2 = current2.getParent();
@@ -1306,39 +1407,42 @@ function enclosingLoweredStatement(terminal, body, rawToStructured) {
1306
1407
  }
1307
1408
 
1308
1409
  // src/paths/pathConditions.ts
1309
- function containsUnmodeledFlow(body) {
1410
+ function containsUnmodeledFlow(body, func, barriers) {
1310
1411
  let found = false;
1311
1412
  body.forEachDescendant((node, traversal) => {
1312
- if (isFunctionBoundary(node)) {
1413
+ if (isDescentStop(node, func, barriers)) {
1313
1414
  traversal.skip();
1314
1415
  return;
1315
1416
  }
1316
- if (import_ts_morph8.Node.isLabeledStatement(node)) {
1417
+ if (import_ts_morph9.Node.isLabeledStatement(node)) {
1317
1418
  found = true;
1318
1419
  traversal.stop();
1319
1420
  }
1320
1421
  });
1321
1422
  return found;
1322
1423
  }
1323
- function computePathConditions(func, terminalNodes) {
1424
+ function loweredExpressionBody(body, terminalNodes, func, barriers) {
1425
+ const statement = {
1426
+ kind: "opaque",
1427
+ exitKind: null,
1428
+ callbacks: lowerExpressionBodyCallbacks(body, func, barriers)
1429
+ };
1430
+ return {
1431
+ statements: [statement],
1432
+ terminalsByStmt: /* @__PURE__ */ new Map([[statement, terminalNodes]]),
1433
+ terminalHomeRaw: /* @__PURE__ */ new Map()
1434
+ };
1435
+ }
1436
+ function computePathConditions(func, terminalNodes, barriers = NO_BARRIERS) {
1324
1437
  const body = func.getBody();
1325
1438
  if (body === void 0) {
1326
1439
  return { byTerminal: /* @__PURE__ */ new Map(), fallthrough: [] };
1327
1440
  }
1328
- if (!import_ts_morph8.Node.isBlock(body)) {
1329
- const byTerminal = /* @__PURE__ */ new Map();
1330
- for (const terminal of terminalNodes) {
1331
- byTerminal.set(terminal, [
1332
- collectAncestorConditionInfosBelow(terminal, func)
1333
- ]);
1334
- }
1335
- return { byTerminal, fallthrough: [] };
1336
- }
1337
- if (containsUnmodeledFlow(body)) {
1441
+ if (containsUnmodeledFlow(body, func, barriers)) {
1338
1442
  return degradedResult(func, terminalNodes, "labeled statement");
1339
1443
  }
1340
1444
  try {
1341
- const lowered = lowerFunctionBody(body, terminalNodes);
1445
+ const lowered = import_ts_morph9.Node.isBlock(body) ? lowerFunctionBody(body, terminalNodes, func, barriers) : loweredExpressionBody(body, terminalNodes, func, barriers);
1342
1446
  const result = (0, import_extractor2.enumerateStructuredPaths)({
1343
1447
  statements: lowered.statements,
1344
1448
  terminalsByStmt: lowered.terminalsByStmt
@@ -1405,12 +1509,12 @@ function lineAt(node, pos) {
1405
1509
  }
1406
1510
 
1407
1511
  // src/moduleExports.ts
1408
- var import_ts_morph10 = require("ts-morph");
1512
+ var import_ts_morph11 = require("ts-morph");
1409
1513
 
1410
1514
  // src/bootstrap/lazyProjectInit.ts
1411
1515
  var import_promises = __toESM(require("fs/promises"), 1);
1412
1516
  var import_node_path = __toESM(require("path"), 1);
1413
- var import_ts_morph9 = require("ts-morph");
1517
+ var import_ts_morph10 = require("ts-morph");
1414
1518
 
1415
1519
  // src/facts/moduleGraph.ts
1416
1520
  var import_datalog = require("@suss/datalog");
@@ -1828,10 +1932,10 @@ function readSpecifierPaths(project, filePath, options, host) {
1828
1932
  if (text === null) {
1829
1933
  return [];
1830
1934
  }
1831
- const scanned = import_ts_morph9.ts.preProcessFile(text, true, true);
1935
+ const scanned = import_ts_morph10.ts.preProcessFile(text, true, true);
1832
1936
  const paths = [];
1833
1937
  for (const imported of scanned.importedFiles) {
1834
- const resolved = import_ts_morph9.ts.resolveModuleName(
1938
+ const resolved = import_ts_morph10.ts.resolveModuleName(
1835
1939
  imported.fileName,
1836
1940
  filePath,
1837
1941
  options,
@@ -1855,13 +1959,13 @@ function readFileText(project, filePath) {
1855
1959
  }
1856
1960
  }
1857
1961
  function parseTsconfig(tsConfigFilePath) {
1858
- const configFile = import_ts_morph9.ts.readConfigFile(tsConfigFilePath, import_ts_morph9.ts.sys.readFile);
1962
+ const configFile = import_ts_morph10.ts.readConfigFile(tsConfigFilePath, import_ts_morph10.ts.sys.readFile);
1859
1963
  if (configFile.error !== void 0) {
1860
1964
  return { fileNames: [], options: {} };
1861
1965
  }
1862
- const parsed = import_ts_morph9.ts.parseJsonConfigFileContent(
1966
+ const parsed = import_ts_morph10.ts.parseJsonConfigFileContent(
1863
1967
  configFile.config,
1864
- import_ts_morph9.ts.sys,
1968
+ import_ts_morph10.ts.sys,
1865
1969
  import_node_path.default.dirname(tsConfigFilePath),
1866
1970
  /*existingOptions*/
1867
1971
  void 0,
@@ -1921,17 +2025,17 @@ function addImportersOfMatches(fileImports, matched, options) {
1921
2025
  }
1922
2026
  function internalSpecifierResolver(files, options) {
1923
2027
  const fileSet = new Set(files);
1924
- const cache = import_ts_morph9.ts.createModuleResolutionCache(
1925
- import_ts_morph9.ts.sys.getCurrentDirectory(),
2028
+ const cache = import_ts_morph10.ts.createModuleResolutionCache(
2029
+ import_ts_morph10.ts.sys.getCurrentDirectory(),
1926
2030
  (name) => name,
1927
2031
  options
1928
2032
  );
1929
2033
  return (fromFile, spec) => {
1930
- const resolved = import_ts_morph9.ts.resolveModuleName(
2034
+ const resolved = import_ts_morph10.ts.resolveModuleName(
1931
2035
  spec,
1932
2036
  fromFile,
1933
2037
  options,
1934
- import_ts_morph9.ts.sys,
2038
+ import_ts_morph10.ts.sys,
1935
2039
  cache
1936
2040
  ).resolvedModule?.resolvedFileName;
1937
2041
  if (resolved === void 0) {
@@ -1940,7 +2044,7 @@ function internalSpecifierResolver(files, options) {
1940
2044
  if (fileSet.has(resolved)) {
1941
2045
  return resolved;
1942
2046
  }
1943
- const real = import_ts_morph9.ts.sys.realpath?.(resolved) ?? resolved;
2047
+ const real = import_ts_morph10.ts.sys.realpath?.(resolved) ?? resolved;
1944
2048
  return fileSet.has(real) ? real : null;
1945
2049
  };
1946
2050
  }
@@ -1966,7 +2070,7 @@ async function readImportsConcurrently(paths) {
1966
2070
  const p = paths[i];
1967
2071
  try {
1968
2072
  const text = await import_promises.default.readFile(p, "utf-8");
1969
- const pre = import_ts_morph9.ts.preProcessFile(text, true, false);
2073
+ const pre = import_ts_morph10.ts.preProcessFile(text, true, false);
1970
2074
  results[i] = {
1971
2075
  path: p,
1972
2076
  importedModules: pre.importedFiles.map((f) => f.fileName)
@@ -1984,6 +2088,30 @@ async function readImportsConcurrently(paths) {
1984
2088
  return results;
1985
2089
  }
1986
2090
 
2091
+ // src/depTracking.ts
2092
+ function createDependencySink() {
2093
+ return { files: /* @__PURE__ */ new Set(), mountPrefixes: /* @__PURE__ */ new Map(), claims: [] };
2094
+ }
2095
+ var activeSink = null;
2096
+ function withDependencySink(sink, fn) {
2097
+ const previous = activeSink;
2098
+ activeSink = sink;
2099
+ try {
2100
+ return fn();
2101
+ } finally {
2102
+ activeSink = previous;
2103
+ }
2104
+ }
2105
+ function recordFileDependency(filePath) {
2106
+ activeSink?.files.add(filePath);
2107
+ }
2108
+ function recordMountPrefix(childId, prefix) {
2109
+ activeSink?.mountPrefixes.set(childId, prefix);
2110
+ }
2111
+ function recordUnitClaim(key, pack) {
2112
+ activeSink?.claims.push({ key, pack });
2113
+ }
2114
+
1987
2115
  // src/perFileCache.ts
1988
2116
  function createPerFileCache() {
1989
2117
  const byParse = /* @__PURE__ */ new WeakMap();
@@ -1999,6 +2127,7 @@ function createPerFileCache() {
1999
2127
 
2000
2128
  // src/moduleExports.ts
2001
2129
  function exportedDeclarationsOf(sourceFile) {
2130
+ recordFileDependency(sourceFile.getFilePath());
2002
2131
  const cached = exportsByFile.get(sourceFile);
2003
2132
  if (cached !== void 0) {
2004
2133
  return cached;
@@ -2013,7 +2142,14 @@ function resolveAliasedSymbol(symbol) {
2013
2142
  warmReExportChains(declaration.getSourceFile(), "every import");
2014
2143
  }
2015
2144
  try {
2016
- return symbol.getAliasedSymbol();
2145
+ const aliased = symbol.getAliasedSymbol();
2146
+ for (const declaration of aliased?.getDeclarations() ?? []) {
2147
+ const file = declaration.getSourceFile();
2148
+ if (!file.isInNodeModules()) {
2149
+ recordFileDependency(file.getFilePath());
2150
+ }
2151
+ }
2152
+ return aliased;
2017
2153
  } catch (error) {
2018
2154
  if (!(error instanceof RangeError)) {
2019
2155
  throw error;
@@ -2128,7 +2264,7 @@ function reExportShapeOf(file, reach) {
2128
2264
  for (const assignment of file.getExportAssignments()) {
2129
2265
  exportNodes.push(assignment);
2130
2266
  const expression = assignment.getExpression();
2131
- if (import_ts_morph10.Node.isIdentifier(expression)) {
2267
+ if (import_ts_morph11.Node.isIdentifier(expression)) {
2132
2268
  localNames.add(expression.getText());
2133
2269
  }
2134
2270
  }
@@ -2204,38 +2340,6 @@ function reportUnreadableExports(file) {
2204
2340
  );
2205
2341
  }
2206
2342
 
2207
- // src/walk/descent.ts
2208
- var import_ts_morph11 = require("ts-morph");
2209
- var NO_BARRIERS = /* @__PURE__ */ new Set();
2210
- function isDescentStop(node, func, barriers = NO_BARRIERS) {
2211
- if (node === func) {
2212
- return false;
2213
- }
2214
- if (import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node)) {
2215
- return true;
2216
- }
2217
- if (import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node)) {
2218
- return barriers.has(node);
2219
- }
2220
- return false;
2221
- }
2222
- function isModuleScopeStop(node) {
2223
- return import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node) || import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node) || import_ts_morph11.Node.isClassDeclaration(node) || import_ts_morph11.Node.isClassExpression(node);
2224
- }
2225
- function crossesNestedFunctionScope(node, func) {
2226
- let current2 = node;
2227
- while (current2 !== void 0 && current2 !== func) {
2228
- if (startsItsOwnScope(current2)) {
2229
- return true;
2230
- }
2231
- current2 = current2.getParent();
2232
- }
2233
- return false;
2234
- }
2235
- function startsItsOwnScope(node) {
2236
- return import_ts_morph11.Node.isArrowFunction(node) || import_ts_morph11.Node.isFunctionExpression(node) || import_ts_morph11.Node.isFunctionDeclaration(node) || import_ts_morph11.Node.isMethodDeclaration(node) || import_ts_morph11.Node.isGetAccessorDeclaration(node) || import_ts_morph11.Node.isSetAccessorDeclaration(node) || import_ts_morph11.Node.isConstructorDeclaration(node);
2237
- }
2238
-
2239
2343
  // src/resolve/invocationEffects.ts
2240
2344
  function isImportedFrom(identifier, expectedModule) {
2241
2345
  if (!import_ts_morph12.Node.isIdentifier(identifier)) {
@@ -4940,7 +5044,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4940
5044
  ];
4941
5045
  const { byTerminal } = computePathConditions(
4942
5046
  func,
4943
- terminals.map(({ node }) => node)
5047
+ terminals.map(({ node }) => node),
5048
+ barriers
4944
5049
  );
4945
5050
  const wantsFallthrough = terminalPatterns.some(
4946
5051
  (p) => p.match.type === "functionFallthrough"
@@ -4952,7 +5057,11 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4952
5057
  if (!hasDefaultTerminal) {
4953
5058
  const synthetic = makeFallthroughTerminal(func);
4954
5059
  terminals.push(synthetic);
4955
- const pathResult = computePathConditions(func, [synthetic.node]);
5060
+ const pathResult = computePathConditions(
5061
+ func,
5062
+ [synthetic.node],
5063
+ barriers
5064
+ );
4956
5065
  byTerminal.set(
4957
5066
  synthetic.node,
4958
5067
  pathResult.fallthrough.length > 0 ? pathResult.fallthrough : [[]]
@@ -5083,7 +5192,7 @@ function indexFunctions(sf) {
5083
5192
  var import_node_crypto2 = require("crypto");
5084
5193
  var import_promises2 = __toESM(require("fs/promises"), 1);
5085
5194
  var import_node_path2 = __toESM(require("path"), 1);
5086
- var SCHEMA_VERSION = "5";
5195
+ var SCHEMA_VERSION = "6";
5087
5196
  var MAX_ENTRIES = 2;
5088
5197
  var ENTRY_PREFIX = "key-";
5089
5198
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
@@ -5095,6 +5204,7 @@ function createCacheLayer(cacheDir) {
5095
5204
  kind: "miss",
5096
5205
  diagnostic: { kind: "miss", missReason: "no-manifest" }
5097
5206
  }),
5207
+ plan: async () => null,
5098
5208
  write: async () => {
5099
5209
  }
5100
5210
  };
@@ -5122,20 +5232,36 @@ function createCacheLayer(cacheDir) {
5122
5232
  return {
5123
5233
  kind: "hit",
5124
5234
  summaries: manifest.summaries,
5235
+ ...manifest.projectRoot === void 0 ? {} : { projectRoot: manifest.projectRoot },
5125
5236
  diagnostic: { kind: "hit" }
5126
5237
  };
5127
5238
  },
5128
- async write(input, summaries) {
5239
+ async plan(input) {
5240
+ const entryDir = entryDirFor(cacheDir, input);
5241
+ const manifest = await readManifest(import_node_path2.default.join(entryDir, "manifest.json"));
5242
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
5243
+ return null;
5244
+ }
5245
+ const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
5246
+ if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
5247
+ return null;
5248
+ }
5249
+ await markUsed(entryDir);
5250
+ return buildPlan(manifest, await resolveFileStamps(input));
5251
+ },
5252
+ async write(input, summaries, attribution) {
5253
+ const entryDir = entryDirFor(cacheDir, input);
5254
+ const previous = await readManifest(import_node_path2.default.join(entryDir, "manifest.json"));
5129
5255
  const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
5130
- const files = await resolveFileStamps(input);
5256
+ const files = await hashStamps(await resolveFileStamps(input), previous);
5131
5257
  const manifest = {
5132
5258
  schemaVersion: SCHEMA_VERSION,
5133
5259
  adapterPacksDigest: input.adapterPacksDigest,
5134
5260
  tsconfigStamp,
5135
5261
  files,
5136
- summaries
5262
+ summaries,
5263
+ ...attribution === void 0 ? {} : encodeAttribution(attribution)
5137
5264
  };
5138
- const entryDir = entryDirFor(cacheDir, input);
5139
5265
  await import_promises2.default.mkdir(entryDir, { recursive: true });
5140
5266
  await import_promises2.default.writeFile(
5141
5267
  import_node_path2.default.join(entryDir, "manifest.json"),
@@ -5145,6 +5271,171 @@ function createCacheLayer(cacheDir) {
5145
5271
  }
5146
5272
  };
5147
5273
  }
5274
+ async function buildPlan(manifest, currentStamps) {
5275
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
5276
+ const current2 = new Map(currentStamps.map((f) => [f.path, f]));
5277
+ const changed = /* @__PURE__ */ new Set();
5278
+ const removed = /* @__PURE__ */ new Set();
5279
+ for (const p of stored.keys()) {
5280
+ if (!current2.has(p)) {
5281
+ removed.add(p);
5282
+ }
5283
+ }
5284
+ const toVerify = [];
5285
+ for (const [p, stamp] of current2) {
5286
+ const before = stored.get(p);
5287
+ if (before === void 0) {
5288
+ changed.add(p);
5289
+ } else if (!fileStampEquals(before, stamp)) {
5290
+ if (before.contentHash === void 0) {
5291
+ changed.add(p);
5292
+ } else {
5293
+ toVerify.push(p);
5294
+ }
5295
+ }
5296
+ }
5297
+ await Promise.all(
5298
+ toVerify.map(async (p) => {
5299
+ const hash = await hashFile(p);
5300
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
5301
+ changed.add(p);
5302
+ }
5303
+ })
5304
+ );
5305
+ const roots = /* @__PURE__ */ new Map();
5306
+ const rootNames = manifest.roots ?? [];
5307
+ const depPaths = manifest.depPaths ?? [];
5308
+ let rootsDeclined = 0;
5309
+ rootNames.forEach((rootPath, i) => {
5310
+ const meta = manifest.rootMeta?.[i];
5311
+ if (meta === void 0) {
5312
+ return;
5313
+ }
5314
+ if (!meta.cacheable) {
5315
+ rootsDeclined += 1;
5316
+ }
5317
+ roots.set(rootPath, {
5318
+ path: rootPath,
5319
+ cacheable: meta.cacheable,
5320
+ deps: meta.deps.flatMap((d) => {
5321
+ const p = depPaths[d];
5322
+ return p === void 0 ? [] : [p];
5323
+ }),
5324
+ claims: meta.claims,
5325
+ mountPrefixes: meta.mountPrefixes,
5326
+ packs: meta.packs
5327
+ });
5328
+ });
5329
+ const validRoots = /* @__PURE__ */ new Set();
5330
+ for (const [rootPath, record] of roots) {
5331
+ if (!record.cacheable || !current2.has(rootPath)) {
5332
+ continue;
5333
+ }
5334
+ if (changed.has(rootPath)) {
5335
+ continue;
5336
+ }
5337
+ const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
5338
+ if (!depMoved) {
5339
+ validRoots.add(rootPath);
5340
+ }
5341
+ }
5342
+ const owners = manifest.owners ?? [];
5343
+ return {
5344
+ changed,
5345
+ removed,
5346
+ roots,
5347
+ validRoots,
5348
+ rootsDeclined,
5349
+ storedProjectRoot: manifest.projectRoot ?? null,
5350
+ reuse(valid) {
5351
+ const summaries = [];
5352
+ const reusedOwners = [];
5353
+ manifest.summaries.forEach((summary, i) => {
5354
+ const ownerPaths = (owners[i] ?? []).flatMap((o) => {
5355
+ const p = rootNames[o];
5356
+ return p === void 0 ? [] : [p];
5357
+ });
5358
+ if (ownerPaths.some((p) => valid.has(p))) {
5359
+ summaries.push(summary);
5360
+ reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
5361
+ }
5362
+ });
5363
+ return { summaries, owners: reusedOwners };
5364
+ },
5365
+ allSummaries() {
5366
+ return manifest.summaries;
5367
+ },
5368
+ attribution() {
5369
+ return {
5370
+ roots: [...roots.values()],
5371
+ owners: owners.map(
5372
+ (ownerIds) => ownerIds.flatMap((o) => {
5373
+ const p = rootNames[o];
5374
+ return p === void 0 ? [] : [p];
5375
+ })
5376
+ ),
5377
+ ...manifest.projectRoot === void 0 ? {} : { projectRoot: manifest.projectRoot }
5378
+ };
5379
+ }
5380
+ };
5381
+ }
5382
+ function encodeAttribution(attribution) {
5383
+ const roots = attribution.roots.map((r) => r.path);
5384
+ const rootIndex = new Map(roots.map((p, i) => [p, i]));
5385
+ const depIndex = /* @__PURE__ */ new Map();
5386
+ const depPaths = [];
5387
+ const depIdOf = (p) => {
5388
+ const existing = depIndex.get(p);
5389
+ if (existing !== void 0) {
5390
+ return existing;
5391
+ }
5392
+ const id = depPaths.length;
5393
+ depPaths.push(p);
5394
+ depIndex.set(p, id);
5395
+ return id;
5396
+ };
5397
+ const rootMeta = attribution.roots.map((r) => ({
5398
+ cacheable: r.cacheable,
5399
+ deps: r.deps.map(depIdOf),
5400
+ claims: r.claims,
5401
+ mountPrefixes: r.mountPrefixes,
5402
+ packs: r.packs
5403
+ }));
5404
+ const owners = attribution.owners.map(
5405
+ (ownerPaths) => ownerPaths.flatMap((p) => {
5406
+ const i = rootIndex.get(p);
5407
+ return i === void 0 ? [] : [i];
5408
+ })
5409
+ );
5410
+ return {
5411
+ roots,
5412
+ rootMeta,
5413
+ depPaths,
5414
+ owners,
5415
+ ...attribution.projectRoot === void 0 ? {} : { projectRoot: attribution.projectRoot }
5416
+ };
5417
+ }
5418
+ async function hashStamps(stamps, previous) {
5419
+ const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
5420
+ return Promise.all(
5421
+ stamps.map(async (stamp) => {
5422
+ const prior = before.get(stamp.path);
5423
+ if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
5424
+ return { ...stamp, contentHash: prior.contentHash };
5425
+ }
5426
+ const contentHash = await hashFile(stamp.path);
5427
+ return contentHash === null ? stamp : { ...stamp, contentHash };
5428
+ })
5429
+ );
5430
+ }
5431
+ async function hashFile(filePath) {
5432
+ try {
5433
+ const content = await import_promises2.default.readFile(filePath);
5434
+ return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").slice(0, 16);
5435
+ } catch {
5436
+ return null;
5437
+ }
5438
+ }
5148
5439
  async function describeAbsentEntry(cacheDir) {
5149
5440
  try {
5150
5441
  const entries = await import_promises2.default.readdir(cacheDir, { withFileTypes: true });
@@ -5158,7 +5449,7 @@ function entryDirFor(cacheDir, input) {
5158
5449
  SCHEMA_VERSION,
5159
5450
  input.adapterPacksDigest,
5160
5451
  input.tsconfigPath ?? ""
5161
- ].join("\0");
5452
+ ].join(" ");
5162
5453
  const name = (0, import_node_crypto2.createHash)("sha256").update(key).digest("hex").slice(0, 16);
5163
5454
  return import_node_path2.default.join(cacheDir, `${ENTRY_PREFIX}${name}`);
5164
5455
  }
@@ -7010,7 +7301,7 @@ function calleeOf(decorator) {
7010
7301
 
7011
7302
  // src/discovery/decoratedMethod.ts
7012
7303
  function resolverTypeName(args) {
7013
- return args.classTypeName ?? args.typeMap[args.decoratorName] ?? null;
7304
+ return args.typeMap[args.decoratorName] ?? args.classTypeName ?? null;
7014
7305
  }
7015
7306
  function resolveResolverClassTypeName(decoratorArg) {
7016
7307
  if (import_ts_morph34.Node.isArrowFunction(decoratorArg)) {
@@ -9568,7 +9859,10 @@ function discoverResolverMaps(sourceFile, match, kind, resolution) {
9568
9859
  resolverInfo: {
9569
9860
  typeName,
9570
9861
  fieldName,
9571
- ...schemaSdl !== null ? { schemaSdl } : {}
9862
+ ...schemaSdl !== null ? {
9863
+ schemaSdl,
9864
+ schemaDocument: config.getSourceFile().getFilePath()
9865
+ } : {}
9572
9866
  }
9573
9867
  });
9574
9868
  }
@@ -9765,7 +10059,15 @@ function discoverUnits(sourceFile, patterns, resolution, mountPrefixes) {
9765
10059
  }
9766
10060
 
9767
10061
  // src/discovery/mountPrefix.ts
9768
- var NO_MOUNTS = { effectivePrefixFor: () => "" };
10062
+ var NO_MOUNTS = {
10063
+ // Recorded even with no mounts anywhere: a mount added later in some
10064
+ // other file changes this router's prefix without touching its file.
10065
+ effectivePrefixFor: (routerNode) => {
10066
+ recordMountPrefix(nodeId(routerNode), "");
10067
+ return "";
10068
+ },
10069
+ prefixForId: () => ""
10070
+ };
9769
10071
  function buildMountPrefixIndex(packsByFile, resolution) {
9770
10072
  const subjectIdsByPack = /* @__PURE__ */ new Map();
9771
10073
  const mountWorkByPack = /* @__PURE__ */ new Map();
@@ -9822,10 +10124,15 @@ function buildMountPrefixIndex(packsByFile, resolution) {
9822
10124
  return NO_MOUNTS;
9823
10125
  }
9824
10126
  const memo = /* @__PURE__ */ new Map();
10127
+ const byId = (childId) => resolvePrefix(edgesByChild, memo, childId, /* @__PURE__ */ new Set()) ?? "";
9825
10128
  return {
9826
10129
  effectivePrefixFor(routerNode) {
9827
- return resolvePrefix(edgesByChild, memo, nodeId(routerNode), /* @__PURE__ */ new Set()) ?? "";
9828
- }
10130
+ const childId = nodeId(routerNode);
10131
+ const prefix = byId(childId);
10132
+ recordMountPrefix(childId, prefix);
10133
+ return prefix;
10134
+ },
10135
+ prefixForId: byId
9829
10136
  };
9830
10137
  }
9831
10138
  function recordEdge(edgesByChild, candidate) {
@@ -10089,6 +10396,8 @@ var ResolutionStore = class {
10089
10396
  fullyExtracted = /* @__PURE__ */ new Set();
10090
10397
  seededValues = /* @__PURE__ */ new Set();
10091
10398
  importedNames = /* @__PURE__ */ new Map();
10399
+ /** Files the most recent wave walk entered, for the memo to keep. */
10400
+ lastQueryWalked = [];
10092
10401
  declarations = /* @__PURE__ */ new Map();
10093
10402
  graph = new ModuleGraph();
10094
10403
  stale = true;
@@ -10149,7 +10458,10 @@ var ResolutionStore = class {
10149
10458
  const declaration = `${nodeId(refersTo)}|${modules.join(",")}`;
10150
10459
  const cached = this.importedNames.get(declaration);
10151
10460
  if (cached !== void 0) {
10152
- return cached;
10461
+ for (const walkedPath of cached.walked) {
10462
+ recordFileDependency(walkedPath);
10463
+ }
10464
+ return cached.names;
10153
10465
  }
10154
10466
  const target = factKeyOf(value);
10155
10467
  const found = this.resolveByWaves(
@@ -10157,7 +10469,10 @@ var ResolutionStore = class {
10157
10469
  "wantedOrigin",
10158
10470
  () => this.lookupImportedNames(target, modules)
10159
10471
  ) ?? [];
10160
- this.importedNames.set(declaration, found);
10472
+ this.importedNames.set(declaration, {
10473
+ names: found,
10474
+ walked: [...this.lastQueryWalked]
10475
+ });
10161
10476
  return found;
10162
10477
  }
10163
10478
  /**
@@ -10191,6 +10506,7 @@ var ResolutionStore = class {
10191
10506
  this.wantValue(question, value);
10192
10507
  this.seedValue(value);
10193
10508
  const walked = /* @__PURE__ */ new Set();
10509
+ this.lastQueryWalked = [];
10194
10510
  let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10195
10511
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10196
10512
  const next = [];
@@ -10199,6 +10515,8 @@ var ResolutionStore = class {
10199
10515
  continue;
10200
10516
  }
10201
10517
  walked.add(sourceFile.getFilePath());
10518
+ this.lastQueryWalked.push(sourceFile.getFilePath());
10519
+ recordFileDependency(sourceFile.getFilePath());
10202
10520
  this.extractFile(sourceFile);
10203
10521
  next.push(...this.graph.importedFilesOf(sourceFile));
10204
10522
  }
@@ -10495,6 +10813,27 @@ function moduleInitSummary(sourceFile, effects, options) {
10495
10813
  return (0, import_extractor3.assembleSummary)(raw, options);
10496
10814
  }
10497
10815
 
10816
+ // src/referencedFiles.ts
10817
+ function createReferenceIndex(sourceFiles) {
10818
+ const direct = /* @__PURE__ */ new Map();
10819
+ for (const sourceFile of sourceFiles) {
10820
+ const referenced = /* @__PURE__ */ new Set();
10821
+ for (const target of sourceFile.getReferencedSourceFiles()) {
10822
+ if (target.isInNodeModules() || target.isDeclarationFile()) {
10823
+ continue;
10824
+ }
10825
+ referenced.add(target.getFilePath());
10826
+ }
10827
+ direct.set(sourceFile.getFilePath(), referenced);
10828
+ }
10829
+ const none = /* @__PURE__ */ new Set();
10830
+ return {
10831
+ directOf(filePath) {
10832
+ return direct.get(filePath) ?? none;
10833
+ }
10834
+ };
10835
+ }
10836
+
10498
10837
  // src/resolve/boundaryEffects.ts
10499
10838
  var import_datalog3 = require("@suss/datalog");
10500
10839
  var EFFECT_RULES = [
@@ -10676,6 +11015,11 @@ function classifyStop(declarations) {
10676
11015
  if (declarations.length === 0) {
10677
11016
  return "noDeclaration";
10678
11017
  }
11018
+ for (const declaration of declarations) {
11019
+ if (!isInExternalCode(declaration.getSourceFile())) {
11020
+ recordFileDependency(declaration.getSourceFile().getFilePath());
11021
+ }
11022
+ }
10679
11023
  if (declarations.every(isExternalDeclaration)) {
10680
11024
  return "outsideRun";
10681
11025
  }
@@ -10882,7 +11226,7 @@ var REACHABLE_RULES = [
10882
11226
  )
10883
11227
  ];
10884
11228
  var NO_RECOGNIZERS = { invocation: [], access: [] };
10885
- function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS) {
11229
+ function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = []) {
10886
11230
  const lookup = createSourceFileLookup(project);
10887
11231
  const db = facts?.db ?? new import_datalog4.Database();
10888
11232
  const functionByKey = /* @__PURE__ */ new Map();
@@ -10902,6 +11246,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10902
11246
  db.add("entry", [key]);
10903
11247
  }
10904
11248
  }
11249
+ const knownKeys = /* @__PURE__ */ new Set();
11250
+ for (const summary of alreadySummarized) {
11251
+ const func = lookup.functionAt(summary.location);
11252
+ if (func !== null) {
11253
+ const key = nodeKey2(func);
11254
+ knownKeys.add(key);
11255
+ facts?.unitKeyBySummary.set(summary, key);
11256
+ }
11257
+ }
10905
11258
  for (; ; ) {
10906
11259
  (0, import_datalog4.evaluate)(db, REACHABLE_RULES);
10907
11260
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
@@ -10919,7 +11272,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10919
11272
  ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
10920
11273
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10921
11274
  };
10922
- const { candidates, stops } = collectReachable(source.func, scan);
11275
+ const { candidates, stops } = scanWithRecording(
11276
+ key,
11277
+ facts,
11278
+ () => collectReachable(source.func, scan)
11279
+ );
10923
11280
  if (stops.length > 0) {
10924
11281
  stopsByKey.set(key, stops);
10925
11282
  }
@@ -10938,7 +11295,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10938
11295
  const reached = [];
10939
11296
  for (const [keyAtom] of db.facts("reachable")) {
10940
11297
  const key = String(keyAtom);
10941
- if (seedKeys.has(key)) {
11298
+ if (seedKeys.has(key) || knownKeys.has(key)) {
10942
11299
  continue;
10943
11300
  }
10944
11301
  const candidate = functionByKey.get(key);
@@ -10952,7 +11309,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10952
11309
  candidate.func.getSourceFile().getFilePath()
10953
11310
  );
10954
11311
  }
10955
- const summary = extractReachableSummary(candidate, options, recognizers);
11312
+ const summary = scanWithRecording(
11313
+ key,
11314
+ facts,
11315
+ () => extractReachableSummary(candidate, options, recognizers)
11316
+ );
10956
11317
  facts?.unitKeyBySummary.set(summary, key);
10957
11318
  rememberSummary(summariesByKey, key, summary);
10958
11319
  reached.push(summary);
@@ -10960,6 +11321,20 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10960
11321
  recordStops(stopsByKey, summariesByKey, options);
10961
11322
  return [...seeds, ...reached];
10962
11323
  }
11324
+ function scanWithRecording(key, facts, fn) {
11325
+ const filesByKey = facts?.filesByKey;
11326
+ if (filesByKey === void 0) {
11327
+ return fn();
11328
+ }
11329
+ const sink = createDependencySink();
11330
+ const result = withDependencySink(sink, fn);
11331
+ const bucket = filesByKey.get(key) ?? /* @__PURE__ */ new Set();
11332
+ for (const filePath of sink.files) {
11333
+ bucket.add(filePath);
11334
+ }
11335
+ filesByKey.set(key, bucket);
11336
+ return result;
11337
+ }
10963
11338
  function rememberSummary(summariesByKey, key, summary) {
10964
11339
  const bucket = summariesByKey.get(key) ?? [];
10965
11340
  bucket.push(summary);
@@ -12215,6 +12590,36 @@ function extractCodeStructure(unit, pack, invocationRecognizers = [], accessReco
12215
12590
  function responseAccessorNames(pack, kind) {
12216
12591
  return pack.responseSemantics?.filter((m) => m.semantics.type === kind).map((m) => m.name);
12217
12592
  }
12593
+ var ACCESSOR_FIELD_SEMANTICS = {
12594
+ bodyAccessors: "body",
12595
+ statusAccessors: "statusCode",
12596
+ successAccessors: "statusRange"
12597
+ };
12598
+ function responseAccessors(pack) {
12599
+ const out = {};
12600
+ for (const [field, kind] of Object.entries(ACCESSOR_FIELD_SEMANTICS)) {
12601
+ const names = responseAccessorNames(pack, kind);
12602
+ if (names !== void 0 && names.length > 0) {
12603
+ out[field] = names;
12604
+ }
12605
+ }
12606
+ return out;
12607
+ }
12608
+ var FUNCTION_FALLTHROUGH_TERMINAL = {
12609
+ kind: "return",
12610
+ match: { type: "functionFallthrough" },
12611
+ extraction: {}
12612
+ };
12613
+ function terminalsFor(unit, pack) {
12614
+ const declared = unit.terminals ?? pack.terminals;
12615
+ if (unit.callSite === void 0) {
12616
+ return declared;
12617
+ }
12618
+ if (declared.some((p) => p.match.type === "functionFallthrough")) {
12619
+ return declared;
12620
+ }
12621
+ return [...declared, FUNCTION_FALLTHROUGH_TERMINAL];
12622
+ }
12218
12623
  function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers, barriers, resolveWrittenValue) {
12219
12624
  const { func, kind, name } = unit;
12220
12625
  if (func === null) {
@@ -12226,7 +12631,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12226
12631
  );
12227
12632
  const extracted = extractRawBranches(
12228
12633
  func,
12229
- unit.terminals ?? pack.terminals,
12634
+ terminalsFor(unit, pack),
12230
12635
  invocationRecognizers,
12231
12636
  accessRecognizers,
12232
12637
  barriers,
@@ -12262,8 +12667,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12262
12667
  }
12263
12668
  }
12264
12669
  }
12265
- const bodyAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "body") : void 0;
12266
- const statusAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "statusCode") : void 0;
12670
+ const accessors = unit.callSite !== void 0 ? responseAccessors(pack) : {};
12267
12671
  return {
12268
12672
  identity: {
12269
12673
  name,
@@ -12287,8 +12691,8 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12287
12691
  bodyContent: bodyContentOf(func),
12288
12692
  dependencyCalls: depCalls,
12289
12693
  declaredContract: null,
12290
- ...bodyAccessors !== void 0 && bodyAccessors.length > 0 ? { bodyAccessors } : {},
12291
- ...statusAccessors !== void 0 && statusAccessors.length > 0 ? { statusAccessors } : {}
12694
+ ...accessors,
12695
+ ...unit.callSite !== void 0 && pack.failureDelivery !== void 0 ? { failureDelivery: pack.failureDelivery } : {}
12292
12696
  };
12293
12697
  }
12294
12698
  function extractConsumerBinding(unit, pattern, pack, resolution) {
@@ -12642,12 +13046,20 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12642
13046
  if (claimant.pack === pack.name && claimant.file === sourceFile.getFilePath() && tally !== void 0) {
12643
13047
  tally.selfCollisions += 1;
12644
13048
  }
13049
+ if (claimant.file !== sourceFile.getFilePath()) {
13050
+ recordFileDependency(claimant.file);
13051
+ }
12645
13052
  continue;
12646
13053
  }
12647
13054
  claimed.set(claimKey, {
12648
13055
  pack: pack.name,
12649
13056
  file: sourceFile.getFilePath()
12650
13057
  });
13058
+ recordUnitClaim(claimKey, pack.name);
13059
+ const declaringFile = unit.func?.getSourceFile().getFilePath();
13060
+ if (declaringFile !== void 0 && declaringFile !== sourceFile.getFilePath()) {
13061
+ recordFileDependency(declaringFile);
13062
+ }
12651
13063
  unitsWalkedHere += 1;
12652
13064
  if (tally !== void 0) {
12653
13065
  tally.unitsClaimed += 1;
@@ -12686,6 +13098,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12686
13098
  }
12687
13099
  if (unit.resolverInfo.schemaSdl !== void 0) {
12688
13100
  raw.graphqlSchemaSdl = unit.resolverInfo.schemaSdl;
13101
+ if (unit.resolverInfo.schemaDocument !== void 0) {
13102
+ raw.sourceDocumentLabel = unit.resolverInfo.schemaDocument;
13103
+ }
12689
13104
  if (unit.resolverInfo.typeName !== null) {
12690
13105
  const contract = deriveGraphqlContract(
12691
13106
  unit.resolverInfo.schemaSdl,
@@ -12874,6 +13289,37 @@ function fileImporting(project, modulePrefix) {
12874
13289
  }
12875
13290
  return void 0;
12876
13291
  }
13292
+ function liftSchemasOntoDocuments(summaries) {
13293
+ const sdlByDocument = /* @__PURE__ */ new Map();
13294
+ for (const summary of summaries) {
13295
+ const label = (0, import_behavioral_ir6.readSourceDocumentMetadata)(summary)?.label;
13296
+ const graphql = (0, import_behavioral_ir6.readGraphqlMetadata)(summary);
13297
+ if (label === void 0 || graphql?.schemaSdl === void 0) {
13298
+ continue;
13299
+ }
13300
+ sdlByDocument.set(label, graphql.schemaSdl);
13301
+ const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
13302
+ summary.metadata = (0, import_behavioral_ir6.withGraphqlMetadata)(summary.metadata, rest);
13303
+ }
13304
+ for (const [label, schemaSdl] of sdlByDocument) {
13305
+ summaries.push(schemaDocumentSummary(label, schemaSdl));
13306
+ }
13307
+ }
13308
+ function schemaDocumentSummary(label, schemaSdl) {
13309
+ return {
13310
+ kind: "library",
13311
+ location: { file: label, range: { start: 0, end: 0 }, exportName: null },
13312
+ identity: { name: label, exportPath: null, boundaryBinding: null },
13313
+ inputs: [],
13314
+ transitions: [],
13315
+ gaps: [],
13316
+ confidence: { source: "declared", level: "high" },
13317
+ metadata: (0, import_behavioral_ir6.withGraphqlMetadata)(
13318
+ (0, import_behavioral_ir6.withSourceDocumentMetadata)(void 0, { label }),
13319
+ { schemaSdl }
13320
+ )
13321
+ };
13322
+ }
12877
13323
  function libraryEnvReadMarker(pack, declared, importer) {
12878
13324
  return {
12879
13325
  kind: "library",
@@ -13124,8 +13570,8 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13124
13570
  summary.confidence = { source: "inferred_static", level: "low" };
13125
13571
  return summary;
13126
13572
  }
13127
- function named(summaries, workspace) {
13128
- const projectRoot = commonDirectoryOf(summaries.map((s) => s.location.file));
13573
+ function named(summaries, workspace, storedProjectRoot) {
13574
+ const projectRoot = storedProjectRoot ?? commonDirectoryOf(summaries.map((s) => s.location.file));
13129
13575
  nameSummaries(summaries, {
13130
13576
  workspace: workspace ?? workspaceNameFor(projectRoot),
13131
13577
  projectRoot
@@ -13256,7 +13702,29 @@ function createTypeScriptAdapter(config) {
13256
13702
  if (config.onTiming !== void 0) {
13257
13703
  config.onTiming(timer.report());
13258
13704
  }
13259
- return named(lookup.summaries, config.workspace);
13705
+ return named(lookup.summaries, config.workspace, lookup.projectRoot);
13706
+ }
13707
+ const plan = lookup.diagnostic.missReason === "files-changed" ? await timer.timeAsync("cache.plan", () => cache.plan(cacheInput)) : null;
13708
+ if (plan !== null && plan.changed.size === 0 && plan.removed.size === 0) {
13709
+ try {
13710
+ await cache.write(
13711
+ cacheInput,
13712
+ plan.allSummaries(),
13713
+ plan.attribution()
13714
+ );
13715
+ } catch {
13716
+ }
13717
+ if (config.onCacheDiagnostic !== void 0) {
13718
+ config.onCacheDiagnostic({ kind: "hit" });
13719
+ }
13720
+ if (config.onTiming !== void 0) {
13721
+ config.onTiming(timer.report());
13722
+ }
13723
+ return named(
13724
+ plan.allSummaries(),
13725
+ config.workspace,
13726
+ plan.storedProjectRoot ?? void 0
13727
+ );
13260
13728
  }
13261
13729
  let candidatePaths = null;
13262
13730
  if (lazyEligible) {
@@ -13287,16 +13755,58 @@ function createTypeScriptAdapter(config) {
13287
13755
  "mountPrefix",
13288
13756
  () => buildMountPrefixIndex(packsByFile, resolution)
13289
13757
  );
13758
+ const caching = cacheDir !== null;
13759
+ const validRoots = /* @__PURE__ */ new Set();
13760
+ if (plan !== null) {
13761
+ const packNamesByPath = /* @__PURE__ */ new Map();
13762
+ for (const [sf, packs] of packsByFile) {
13763
+ packNamesByPath.set(
13764
+ sf.getFilePath(),
13765
+ packs.map((p) => p.name).sort().join(",")
13766
+ );
13767
+ }
13768
+ for (const rootPath of plan.validRoots) {
13769
+ const record = plan.roots.get(rootPath);
13770
+ if (record === void 0) {
13771
+ continue;
13772
+ }
13773
+ if (!mountAssumptionsAgree(record, mountPrefixes)) {
13774
+ continue;
13775
+ }
13776
+ const stored = [...record.packs].sort().join(",");
13777
+ if ((packNamesByPath.get(rootPath) ?? "") !== stored) {
13778
+ continue;
13779
+ }
13780
+ validRoots.add(rootPath);
13781
+ }
13782
+ }
13783
+ const reused = plan !== null && validRoots.size > 0 ? plan.reuse(validRoots) : null;
13290
13784
  const claimedUnits = /* @__PURE__ */ new Map();
13785
+ if (reused !== null && plan !== null) {
13786
+ for (const rootPath of validRoots) {
13787
+ for (const claim of plan.roots.get(rootPath)?.claims ?? []) {
13788
+ claimedUnits.set(claim.key, { pack: claim.pack, file: rootPath });
13789
+ }
13790
+ }
13791
+ }
13792
+ const walkList = reused === null ? sourceFiles : sourceFiles.filter((sf) => !validRoots.has(sf.getFilePath()));
13793
+ const sinkByRoot = /* @__PURE__ */ new Map();
13794
+ const ownersBySummary = /* @__PURE__ */ new Map();
13291
13795
  timer.time("extract per-file", () => {
13292
- for (const sourceFile of sourceFiles) {
13796
+ for (const sourceFile of walkList) {
13797
+ const rootPath = sourceFile.getFilePath();
13798
+ const sink = createDependencySink();
13799
+ if (caching) {
13800
+ sinkByRoot.set(rootPath, sink);
13801
+ }
13293
13802
  const applicablePacks = packsByFile.get(sourceFile);
13294
13803
  if (applicablePacks === void 0) {
13295
13804
  continue;
13296
13805
  }
13297
13806
  try {
13298
- summaries.push(
13299
- ...extractFromSourceFile(
13807
+ const walked = withDependencySink(
13808
+ sink,
13809
+ () => extractFromSourceFile(
13300
13810
  sourceFile,
13301
13811
  applicablePacks,
13302
13812
  claimedUnits,
@@ -13306,6 +13816,10 @@ function createTypeScriptAdapter(config) {
13306
13816
  mountPrefixes
13307
13817
  )
13308
13818
  );
13819
+ for (const summary of walked) {
13820
+ ownersBySummary.set(summary, /* @__PURE__ */ new Set([rootPath]));
13821
+ }
13822
+ summaries.push(...walked);
13309
13823
  } catch (error) {
13310
13824
  if (!(error instanceof RangeError)) {
13311
13825
  throw error;
@@ -13314,23 +13828,32 @@ function createTypeScriptAdapter(config) {
13314
13828
  }
13315
13829
  }
13316
13830
  });
13831
+ const wrapperInput = reused === null ? summaries : [...summaries, ...reused.summaries];
13317
13832
  const withWrappers = timer.time(
13318
13833
  "expandWrapperCallers",
13319
- () => expandWrapperCallers(summaries, project, config.extractorOptions)
13834
+ () => expandWrapperCallers(wrapperInput, project, config.extractorOptions)
13320
13835
  );
13836
+ const pipeline = reused === null ? withWrappers : [...summaries, ...withWrappers.slice(wrapperInput.length)];
13321
13837
  const withSubUnits = timer.time(
13322
13838
  "synthesizeSubUnits",
13323
13839
  () => synthesizeSubUnits(
13324
- withWrappers,
13840
+ pipeline,
13325
13841
  project,
13326
13842
  config.frameworks,
13327
13843
  config.extractorOptions,
13328
- tallies
13844
+ tallies,
13845
+ (created, parent) => {
13846
+ const parentOwners = ownersBySummary.get(parent);
13847
+ if (parentOwners !== void 0) {
13848
+ ownersBySummary.set(created, new Set(parentOwners));
13849
+ }
13850
+ }
13329
13851
  )
13330
13852
  );
13331
13853
  const closureFacts = {
13332
13854
  db: new import_datalog6.Database(),
13333
- unitKeyBySummary: /* @__PURE__ */ new Map()
13855
+ unitKeyBySummary: /* @__PURE__ */ new Map(),
13856
+ ...caching ? { filesByKey: /* @__PURE__ */ new Map() } : {}
13334
13857
  };
13335
13858
  const withClosure = config.includeReachable !== false ? timer.time(
13336
13859
  "expandReachableClosure",
@@ -13348,12 +13871,28 @@ function createTypeScriptAdapter(config) {
13348
13871
  access: collectAccessRecognizers(config.frameworks, tallies),
13349
13872
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13350
13873
  resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13351
- }
13874
+ },
13875
+ // Reached units the cache already serves emit nothing,
13876
+ // the way a cold run's seeds do not.
13877
+ reused?.summaries ?? []
13352
13878
  )
13353
13879
  ) : withSubUnits;
13880
+ const closureOwnership = caching && config.includeReachable !== false ? timer.time(
13881
+ "cache.attributeClosure",
13882
+ () => attributeReachedSummaries(
13883
+ withSubUnits,
13884
+ withClosure.slice(withSubUnits.length),
13885
+ closureFacts,
13886
+ ownersBySummary
13887
+ )
13888
+ ) : null;
13889
+ const merged = timer.time(
13890
+ "cache.merge",
13891
+ () => mergeWithReused(reused, withClosure, ownersBySummary)
13892
+ );
13354
13893
  const enriched = timer.time(
13355
13894
  "enrichRethrows",
13356
- () => enrichRethrows(withClosure, project, closureFacts)
13895
+ () => enrichRethrows(merged.summaries, project, closureFacts)
13357
13896
  );
13358
13897
  if (config.includeReachable !== false) {
13359
13898
  timer.time(
@@ -13369,6 +13908,10 @@ function createTypeScriptAdapter(config) {
13369
13908
  "emitLibraryEnvReadMarkers",
13370
13909
  () => emitLibraryEnvReadMarkers(enriched, project, config.frameworks)
13371
13910
  );
13911
+ timer.time(
13912
+ "liftSchemasOntoDocuments",
13913
+ () => liftSchemasOntoDocuments(enriched)
13914
+ );
13372
13915
  timer.time(
13373
13916
  "stampGraphqlClientRefs",
13374
13917
  () => stampGraphqlClientRefs(
@@ -13378,12 +13921,45 @@ function createTypeScriptAdapter(config) {
13378
13921
  resolution
13379
13922
  )
13380
13923
  );
13924
+ if (config.onCacheDiagnostic !== void 0 && plan !== null) {
13925
+ config.onCacheDiagnostic({
13926
+ kind: "partial",
13927
+ partial: {
13928
+ filesChanged: plan.changed.size,
13929
+ filesRemoved: plan.removed.size,
13930
+ rootsReused: validRoots.size,
13931
+ rootsReextracted: walkList.length,
13932
+ rootsDeclined: plan.rootsDeclined,
13933
+ summariesReused: merged.reusedKept.length
13934
+ }
13935
+ });
13936
+ }
13937
+ const runProjectRoot = commonDirectoryOf(
13938
+ sourceFiles.map((f) => f.getFilePath())
13939
+ );
13381
13940
  await timer.timeAsync("cache.write", async () => {
13382
- if (enriched.length === 0) {
13941
+ if (!caching || enriched.length === 0) {
13383
13942
  return;
13384
13943
  }
13385
13944
  try {
13386
- await cache.write(cacheInput, enriched);
13945
+ const attribution = buildCacheAttribution({
13946
+ project,
13947
+ plan,
13948
+ validRoots,
13949
+ sinkByRoot,
13950
+ ownersBySummary,
13951
+ closureOwnership,
13952
+ unitKeyBySummary: closureFacts.unitKeyBySummary,
13953
+ filesByKey: closureFacts.filesByKey,
13954
+ packsByFile,
13955
+ reusedKept: merged.reusedKept,
13956
+ reusedOwners: merged.reusedOwners,
13957
+ // Everything after the reused prefix, the passes' own
13958
+ // additions (markers, schema documents) included.
13959
+ fresh: enriched.slice(merged.reusedKept.length),
13960
+ projectRoot: runProjectRoot
13961
+ });
13962
+ await cache.write(cacheInput, enriched, attribution);
13387
13963
  } catch {
13388
13964
  }
13389
13965
  });
@@ -13396,7 +13972,7 @@ function createTypeScriptAdapter(config) {
13396
13972
  packs: config.frameworks,
13397
13973
  tallies,
13398
13974
  filesInProject: tsconfigFileList?.length ?? null,
13399
- filesWalked: sourceFiles.length,
13975
+ filesWalked: walkList.length,
13400
13976
  summaries: enriched,
13401
13977
  tsConfigFilePath: config.tsConfigFilePath,
13402
13978
  projectRoot: commonDirectoryOf(
@@ -13416,7 +13992,200 @@ function createTypeScriptAdapter(config) {
13416
13992
  }
13417
13993
  };
13418
13994
  }
13419
- function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13995
+ function mountAssumptionsAgree(record, index) {
13996
+ return Object.entries(record.mountPrefixes).every(
13997
+ ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
13998
+ );
13999
+ }
14000
+ function attributeReachedSummaries(seeds, reached, facts, owners) {
14001
+ const adjacency = /* @__PURE__ */ new Map();
14002
+ for (const [from, to] of facts.db.facts("calls")) {
14003
+ const key = String(from);
14004
+ const bucket = adjacency.get(key) ?? [];
14005
+ bucket.push(String(to));
14006
+ adjacency.set(key, bucket);
14007
+ }
14008
+ const seedKeysByRoot = /* @__PURE__ */ new Map();
14009
+ for (const seed of seeds) {
14010
+ const key = facts.unitKeyBySummary.get(seed);
14011
+ if (key === void 0) {
14012
+ continue;
14013
+ }
14014
+ for (const root of owners.get(seed) ?? []) {
14015
+ const bucket = seedKeysByRoot.get(root) ?? [];
14016
+ bucket.push(key);
14017
+ seedKeysByRoot.set(root, bucket);
14018
+ }
14019
+ }
14020
+ const reachableByRoot = /* @__PURE__ */ new Map();
14021
+ const rootsByKey = /* @__PURE__ */ new Map();
14022
+ for (const [root, seedKeys] of seedKeysByRoot) {
14023
+ const seen = new Set(seedKeys);
14024
+ const queue = [...seedKeys];
14025
+ while (queue.length > 0) {
14026
+ const key = queue.pop();
14027
+ if (key === void 0) {
14028
+ continue;
14029
+ }
14030
+ for (const next of adjacency.get(key) ?? []) {
14031
+ if (!seen.has(next)) {
14032
+ seen.add(next);
14033
+ queue.push(next);
14034
+ }
14035
+ }
14036
+ const keyRoots = rootsByKey.get(key) ?? /* @__PURE__ */ new Set();
14037
+ keyRoots.add(root);
14038
+ rootsByKey.set(key, keyRoots);
14039
+ }
14040
+ reachableByRoot.set(root, seen);
14041
+ }
14042
+ for (const summary of reached) {
14043
+ const key = facts.unitKeyBySummary.get(summary);
14044
+ if (key === void 0) {
14045
+ continue;
14046
+ }
14047
+ const keyRoots = rootsByKey.get(key);
14048
+ if (keyRoots !== void 0 && keyRoots.size > 0) {
14049
+ owners.set(summary, new Set(keyRoots));
14050
+ }
14051
+ }
14052
+ return { reachableByRoot, rootsByKey };
14053
+ }
14054
+ function fileOfNodeKey(key) {
14055
+ const cut = key.lastIndexOf(":");
14056
+ return cut <= 0 ? null : key.slice(0, cut);
14057
+ }
14058
+ function summaryMergeKey(summary) {
14059
+ return JSON.stringify([
14060
+ summary.location.file,
14061
+ summary.location.range.start,
14062
+ summary.location.range.end,
14063
+ summary.kind,
14064
+ summary.identity.name,
14065
+ summary.identity.exportPath,
14066
+ summary.identity.boundaryBinding
14067
+ ]);
14068
+ }
14069
+ function mergeWithReused(reused, fresh, ownersBySummary) {
14070
+ if (reused === null) {
14071
+ return { summaries: fresh, reusedKept: [], reusedOwners: [] };
14072
+ }
14073
+ const freshByKey = /* @__PURE__ */ new Map();
14074
+ for (const summary of fresh) {
14075
+ freshByKey.set(summaryMergeKey(summary), summary);
14076
+ }
14077
+ const reusedKept = [];
14078
+ const reusedOwners = [];
14079
+ reused.summaries.forEach((summary, i) => {
14080
+ const owners = reused.owners[i] ?? [];
14081
+ const supersededBy = freshByKey.get(summaryMergeKey(summary));
14082
+ if (supersededBy !== void 0) {
14083
+ const freshOwners = ownersBySummary.get(supersededBy) ?? /* @__PURE__ */ new Set();
14084
+ for (const owner of owners) {
14085
+ freshOwners.add(owner);
14086
+ }
14087
+ ownersBySummary.set(supersededBy, freshOwners);
14088
+ return;
14089
+ }
14090
+ reusedKept.push(summary);
14091
+ reusedOwners.push(owners);
14092
+ });
14093
+ return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
14094
+ }
14095
+ function readsRunLevelJoins(summary) {
14096
+ if ((0, import_behavioral_ir6.readSourceDocumentMetadata)(summary) !== void 0) {
14097
+ return true;
14098
+ }
14099
+ if ((0, import_behavioral_ir6.readGraphqlMetadata)(summary)?.client !== void 0) {
14100
+ return true;
14101
+ }
14102
+ const semantics = summary.identity.boundaryBinding?.semantics;
14103
+ return semantics !== void 0 && "operationType" in semantics;
14104
+ }
14105
+ function buildCacheAttribution(args) {
14106
+ const references = createReferenceIndex(
14107
+ args.project.getSourceFiles().filter((sf) => !sf.isDeclarationFile())
14108
+ );
14109
+ const packNamesByPath = /* @__PURE__ */ new Map();
14110
+ for (const [sf, packs] of args.packsByFile) {
14111
+ packNamesByPath.set(
14112
+ sf.getFilePath(),
14113
+ packs.map((p) => p.name)
14114
+ );
14115
+ }
14116
+ const summariesByRoot = /* @__PURE__ */ new Map();
14117
+ for (const summary of args.fresh) {
14118
+ for (const root of args.ownersBySummary.get(summary) ?? []) {
14119
+ const bucket = summariesByRoot.get(root) ?? [];
14120
+ bucket.push(summary);
14121
+ summariesByRoot.set(root, bucket);
14122
+ }
14123
+ }
14124
+ const roots = [];
14125
+ for (const [rootPath, sink] of args.sinkByRoot) {
14126
+ const deps = new Set(sink.files);
14127
+ for (const dep of references.directOf(rootPath)) {
14128
+ deps.add(dep);
14129
+ }
14130
+ let cacheable = true;
14131
+ for (const summary of summariesByRoot.get(rootPath) ?? []) {
14132
+ deps.add(summary.location.file);
14133
+ if (readsRunLevelJoins(summary)) {
14134
+ cacheable = false;
14135
+ }
14136
+ }
14137
+ const reachable = args.closureOwnership?.reachableByRoot.get(rootPath) ?? [];
14138
+ for (const key of reachable) {
14139
+ const keyFile = fileOfNodeKey(key);
14140
+ if (keyFile !== null) {
14141
+ deps.add(keyFile);
14142
+ for (const hop of references.directOf(keyFile)) {
14143
+ deps.add(hop);
14144
+ }
14145
+ }
14146
+ for (const filePath of args.filesByKey?.get(key) ?? []) {
14147
+ deps.add(filePath);
14148
+ }
14149
+ }
14150
+ deps.delete(rootPath);
14151
+ roots.push({
14152
+ path: rootPath,
14153
+ cacheable,
14154
+ deps: [...deps].sort(),
14155
+ claims: sink.claims,
14156
+ mountPrefixes: Object.fromEntries(sink.mountPrefixes),
14157
+ packs: packNamesByPath.get(rootPath) ?? []
14158
+ });
14159
+ }
14160
+ if (args.plan !== null) {
14161
+ for (const rootPath of args.validRoots) {
14162
+ const record = args.plan.roots.get(rootPath);
14163
+ if (record !== void 0) {
14164
+ roots.push(record);
14165
+ }
14166
+ }
14167
+ }
14168
+ const reusedOwners = args.reusedKept.map((summary, i) => {
14169
+ const combined = new Set(args.reusedOwners[i] ?? []);
14170
+ const key = args.unitKeyBySummary.get(summary);
14171
+ if (key !== void 0) {
14172
+ for (const root of args.closureOwnership?.rootsByKey.get(key) ?? []) {
14173
+ combined.add(root);
14174
+ }
14175
+ }
14176
+ return [...combined];
14177
+ });
14178
+ const owners = [
14179
+ ...reusedOwners,
14180
+ ...args.fresh.map((s) => [...args.ownersBySummary.get(s) ?? []])
14181
+ ];
14182
+ return {
14183
+ roots,
14184
+ owners,
14185
+ ...args.projectRoot === void 0 ? {} : { projectRoot: args.projectRoot }
14186
+ };
14187
+ }
14188
+ function synthesizeSubUnits(summaries, project, frameworks, options, tallies, onCreated) {
13420
14189
  const packByRecognition = /* @__PURE__ */ new Map();
13421
14190
  for (const pack of frameworks) {
13422
14191
  packByRecognition.set(pack.name, pack);
@@ -13459,6 +14228,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13459
14228
  );
13460
14229
  if (summary !== null) {
13461
14230
  synthesized.push(summary);
14231
+ onCreated?.(summary, parent);
13462
14232
  }
13463
14233
  }
13464
14234
  }
@@ -13467,14 +14237,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13467
14237
  var DEFAULT_SUB_UNIT_TERMINALS = [
13468
14238
  { kind: "return", match: { type: "returnStatement" }, extraction: {} },
13469
14239
  { kind: "throw", match: { type: "throwExpression" }, extraction: {} },
13470
- // A callback routinely falls off the end returning `undefined`, so
13471
- // fall-through gives it a default transition. HTTP packs do not opt in:
13472
- // a handler that sends no response should come out with none.
13473
- {
13474
- kind: "return",
13475
- match: { type: "functionFallthrough" },
13476
- extraction: {}
13477
- }
14240
+ FUNCTION_FALLTHROUGH_TERMINAL
13478
14241
  ];
13479
14242
  var DEFAULT_SUB_UNIT_INPUT_MAPPING = {
13480
14243
  type: "positionalParams",
@@ -13781,6 +14544,7 @@ function formatPackHealth(checks, audiences) {
13781
14544
 
13782
14545
  // src/resolve/readName.ts
13783
14546
  var import_ts_morph59 = require("ts-morph");
14547
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
13784
14548
  var MAX_HELPER_HOPS = 2;
13785
14549
  var UNNAMED_HOLE = "param";
13786
14550
  function readName(expr, options = {}) {
@@ -13794,6 +14558,14 @@ function readName(expr, options = {}) {
13794
14558
  if (name !== null || options.unsettled !== "reference") {
13795
14559
  return name;
13796
14560
  }
14561
+ const reference = referenceIn(expr);
14562
+ return reference === null ? null : (0, import_behavioral_ir7.referenceName)(reference);
14563
+ }
14564
+ function referenceIn(expr) {
14565
+ const fromParameter = parameterPath(expr);
14566
+ if (fromParameter !== null) {
14567
+ return fromParameter;
14568
+ }
13797
14569
  const asked = holeName(expr, {
13798
14570
  resolve: () => null,
13799
14571
  bindings: /* @__PURE__ */ new Map(),
@@ -13801,7 +14573,25 @@ function readName(expr, options = {}) {
13801
14573
  seen: /* @__PURE__ */ new Set(),
13802
14574
  insideHole: true
13803
14575
  });
13804
- return asked === UNNAMED_HOLE ? null : `{${asked}}`;
14576
+ return asked === UNNAMED_HOLE ? null : { root: asked, fields: [] };
14577
+ }
14578
+ function parameterPath(expr) {
14579
+ const fields = [];
14580
+ let step = expr;
14581
+ while (import_ts_morph59.Node.isPropertyAccessExpression(step)) {
14582
+ fields.unshift(step.getName());
14583
+ step = step.getExpression();
14584
+ }
14585
+ if (!import_ts_morph59.Node.isIdentifier(step) || !isParameter(step)) {
14586
+ return null;
14587
+ }
14588
+ return { root: step.getText(), fields };
14589
+ }
14590
+ function isParameter(identifier) {
14591
+ const declarations = identifier.getSymbol()?.getDeclarations() ?? [];
14592
+ return declarations.some(
14593
+ (declaration) => import_ts_morph59.Node.isParameterDeclaration(declaration)
14594
+ );
13805
14595
  }
13806
14596
  function read(expr, ctx) {
13807
14597
  if (ctx.seen.has(expr)) {
@@ -13998,7 +14788,7 @@ function boundOnce(expr, ctx) {
13998
14788
  }
13999
14789
  function readOrHole(expr, ctx) {
14000
14790
  const name = read(expr, ctx);
14001
- return name ?? `{${holeName(expr, ctx)}}`;
14791
+ return name ?? (0, import_behavioral_ir7.patternHole)(holeName(expr, ctx));
14002
14792
  }
14003
14793
  function holeName(expr, ctx) {
14004
14794
  const bound = boundOnce(expr, ctx);