@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.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";
@@ -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;
@@ -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)) {
@@ -4909,7 +5017,8 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4909
5017
  ];
4910
5018
  const { byTerminal } = computePathConditions(
4911
5019
  func,
4912
- terminals.map(({ node }) => node)
5020
+ terminals.map(({ node }) => node),
5021
+ barriers
4913
5022
  );
4914
5023
  const wantsFallthrough = terminalPatterns.some(
4915
5024
  (p) => p.match.type === "functionFallthrough"
@@ -4921,7 +5030,11 @@ function extractRawBranches(func, terminalPatterns, invocationRecognizers = [],
4921
5030
  if (!hasDefaultTerminal) {
4922
5031
  const synthetic = makeFallthroughTerminal(func);
4923
5032
  terminals.push(synthetic);
4924
- const pathResult = computePathConditions(func, [synthetic.node]);
5033
+ const pathResult = computePathConditions(
5034
+ func,
5035
+ [synthetic.node],
5036
+ barriers
5037
+ );
4925
5038
  byTerminal.set(
4926
5039
  synthetic.node,
4927
5040
  pathResult.fallthrough.length > 0 ? pathResult.fallthrough : [[]]
@@ -5052,7 +5165,7 @@ function indexFunctions(sf) {
5052
5165
  import { createHash as createHash2 } from "crypto";
5053
5166
  import fs2 from "fs/promises";
5054
5167
  import path2 from "path";
5055
- var SCHEMA_VERSION = "5";
5168
+ var SCHEMA_VERSION = "6";
5056
5169
  var MAX_ENTRIES = 2;
5057
5170
  var ENTRY_PREFIX = "key-";
5058
5171
  var ENTRY_DIR_NAME = new RegExp(`^${ENTRY_PREFIX}[0-9a-f]{16}$`);
@@ -5064,6 +5177,7 @@ function createCacheLayer(cacheDir) {
5064
5177
  kind: "miss",
5065
5178
  diagnostic: { kind: "miss", missReason: "no-manifest" }
5066
5179
  }),
5180
+ plan: async () => null,
5067
5181
  write: async () => {
5068
5182
  }
5069
5183
  };
@@ -5091,20 +5205,36 @@ function createCacheLayer(cacheDir) {
5091
5205
  return {
5092
5206
  kind: "hit",
5093
5207
  summaries: manifest.summaries,
5208
+ ...manifest.projectRoot === void 0 ? {} : { projectRoot: manifest.projectRoot },
5094
5209
  diagnostic: { kind: "hit" }
5095
5210
  };
5096
5211
  },
5097
- async write(input, summaries) {
5212
+ async plan(input) {
5213
+ const entryDir = entryDirFor(cacheDir, input);
5214
+ const manifest = await readManifest(path2.join(entryDir, "manifest.json"));
5215
+ if (manifest === null || manifest.roots === void 0 || manifest.rootMeta === void 0 || manifest.owners === void 0) {
5216
+ return null;
5217
+ }
5218
+ const currentTsconfigStamp = await stampTsconfig(input.tsconfigPath);
5219
+ if (!fileStampEquals(manifest.tsconfigStamp, currentTsconfigStamp)) {
5220
+ return null;
5221
+ }
5222
+ await markUsed(entryDir);
5223
+ return buildPlan(manifest, await resolveFileStamps(input));
5224
+ },
5225
+ async write(input, summaries, attribution) {
5226
+ const entryDir = entryDirFor(cacheDir, input);
5227
+ const previous = await readManifest(path2.join(entryDir, "manifest.json"));
5098
5228
  const tsconfigStamp = await stampTsconfig(input.tsconfigPath);
5099
- const files = await resolveFileStamps(input);
5229
+ const files = await hashStamps(await resolveFileStamps(input), previous);
5100
5230
  const manifest = {
5101
5231
  schemaVersion: SCHEMA_VERSION,
5102
5232
  adapterPacksDigest: input.adapterPacksDigest,
5103
5233
  tsconfigStamp,
5104
5234
  files,
5105
- summaries
5235
+ summaries,
5236
+ ...attribution === void 0 ? {} : encodeAttribution(attribution)
5106
5237
  };
5107
- const entryDir = entryDirFor(cacheDir, input);
5108
5238
  await fs2.mkdir(entryDir, { recursive: true });
5109
5239
  await fs2.writeFile(
5110
5240
  path2.join(entryDir, "manifest.json"),
@@ -5114,6 +5244,171 @@ function createCacheLayer(cacheDir) {
5114
5244
  }
5115
5245
  };
5116
5246
  }
5247
+ async function buildPlan(manifest, currentStamps) {
5248
+ const stored = new Map(manifest.files.map((f) => [f.path, f]));
5249
+ const current2 = new Map(currentStamps.map((f) => [f.path, f]));
5250
+ const changed = /* @__PURE__ */ new Set();
5251
+ const removed = /* @__PURE__ */ new Set();
5252
+ for (const p of stored.keys()) {
5253
+ if (!current2.has(p)) {
5254
+ removed.add(p);
5255
+ }
5256
+ }
5257
+ const toVerify = [];
5258
+ for (const [p, stamp] of current2) {
5259
+ const before = stored.get(p);
5260
+ if (before === void 0) {
5261
+ changed.add(p);
5262
+ } else if (!fileStampEquals(before, stamp)) {
5263
+ if (before.contentHash === void 0) {
5264
+ changed.add(p);
5265
+ } else {
5266
+ toVerify.push(p);
5267
+ }
5268
+ }
5269
+ }
5270
+ await Promise.all(
5271
+ toVerify.map(async (p) => {
5272
+ const hash = await hashFile(p);
5273
+ if (hash === null || hash !== stored.get(p)?.contentHash) {
5274
+ changed.add(p);
5275
+ }
5276
+ })
5277
+ );
5278
+ const roots = /* @__PURE__ */ new Map();
5279
+ const rootNames = manifest.roots ?? [];
5280
+ const depPaths = manifest.depPaths ?? [];
5281
+ let rootsDeclined = 0;
5282
+ rootNames.forEach((rootPath, i) => {
5283
+ const meta = manifest.rootMeta?.[i];
5284
+ if (meta === void 0) {
5285
+ return;
5286
+ }
5287
+ if (!meta.cacheable) {
5288
+ rootsDeclined += 1;
5289
+ }
5290
+ roots.set(rootPath, {
5291
+ path: rootPath,
5292
+ cacheable: meta.cacheable,
5293
+ deps: meta.deps.flatMap((d) => {
5294
+ const p = depPaths[d];
5295
+ return p === void 0 ? [] : [p];
5296
+ }),
5297
+ claims: meta.claims,
5298
+ mountPrefixes: meta.mountPrefixes,
5299
+ packs: meta.packs
5300
+ });
5301
+ });
5302
+ const validRoots = /* @__PURE__ */ new Set();
5303
+ for (const [rootPath, record] of roots) {
5304
+ if (!record.cacheable || !current2.has(rootPath)) {
5305
+ continue;
5306
+ }
5307
+ if (changed.has(rootPath)) {
5308
+ continue;
5309
+ }
5310
+ const depMoved = record.deps.some((d) => changed.has(d) || removed.has(d));
5311
+ if (!depMoved) {
5312
+ validRoots.add(rootPath);
5313
+ }
5314
+ }
5315
+ const owners = manifest.owners ?? [];
5316
+ return {
5317
+ changed,
5318
+ removed,
5319
+ roots,
5320
+ validRoots,
5321
+ rootsDeclined,
5322
+ storedProjectRoot: manifest.projectRoot ?? null,
5323
+ reuse(valid) {
5324
+ const summaries = [];
5325
+ const reusedOwners = [];
5326
+ manifest.summaries.forEach((summary, i) => {
5327
+ const ownerPaths = (owners[i] ?? []).flatMap((o) => {
5328
+ const p = rootNames[o];
5329
+ return p === void 0 ? [] : [p];
5330
+ });
5331
+ if (ownerPaths.some((p) => valid.has(p))) {
5332
+ summaries.push(summary);
5333
+ reusedOwners.push(ownerPaths.filter((p) => valid.has(p)));
5334
+ }
5335
+ });
5336
+ return { summaries, owners: reusedOwners };
5337
+ },
5338
+ allSummaries() {
5339
+ return manifest.summaries;
5340
+ },
5341
+ attribution() {
5342
+ return {
5343
+ roots: [...roots.values()],
5344
+ owners: owners.map(
5345
+ (ownerIds) => ownerIds.flatMap((o) => {
5346
+ const p = rootNames[o];
5347
+ return p === void 0 ? [] : [p];
5348
+ })
5349
+ ),
5350
+ ...manifest.projectRoot === void 0 ? {} : { projectRoot: manifest.projectRoot }
5351
+ };
5352
+ }
5353
+ };
5354
+ }
5355
+ function encodeAttribution(attribution) {
5356
+ const roots = attribution.roots.map((r) => r.path);
5357
+ const rootIndex = new Map(roots.map((p, i) => [p, i]));
5358
+ const depIndex = /* @__PURE__ */ new Map();
5359
+ const depPaths = [];
5360
+ const depIdOf = (p) => {
5361
+ const existing = depIndex.get(p);
5362
+ if (existing !== void 0) {
5363
+ return existing;
5364
+ }
5365
+ const id = depPaths.length;
5366
+ depPaths.push(p);
5367
+ depIndex.set(p, id);
5368
+ return id;
5369
+ };
5370
+ const rootMeta = attribution.roots.map((r) => ({
5371
+ cacheable: r.cacheable,
5372
+ deps: r.deps.map(depIdOf),
5373
+ claims: r.claims,
5374
+ mountPrefixes: r.mountPrefixes,
5375
+ packs: r.packs
5376
+ }));
5377
+ const owners = attribution.owners.map(
5378
+ (ownerPaths) => ownerPaths.flatMap((p) => {
5379
+ const i = rootIndex.get(p);
5380
+ return i === void 0 ? [] : [i];
5381
+ })
5382
+ );
5383
+ return {
5384
+ roots,
5385
+ rootMeta,
5386
+ depPaths,
5387
+ owners,
5388
+ ...attribution.projectRoot === void 0 ? {} : { projectRoot: attribution.projectRoot }
5389
+ };
5390
+ }
5391
+ async function hashStamps(stamps, previous) {
5392
+ const before = new Map((previous?.files ?? []).map((f) => [f.path, f]));
5393
+ return Promise.all(
5394
+ stamps.map(async (stamp) => {
5395
+ const prior = before.get(stamp.path);
5396
+ if (prior !== void 0 && prior.contentHash !== void 0 && fileStampEquals(prior, stamp)) {
5397
+ return { ...stamp, contentHash: prior.contentHash };
5398
+ }
5399
+ const contentHash = await hashFile(stamp.path);
5400
+ return contentHash === null ? stamp : { ...stamp, contentHash };
5401
+ })
5402
+ );
5403
+ }
5404
+ async function hashFile(filePath) {
5405
+ try {
5406
+ const content = await fs2.readFile(filePath);
5407
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
5408
+ } catch {
5409
+ return null;
5410
+ }
5411
+ }
5117
5412
  async function describeAbsentEntry(cacheDir) {
5118
5413
  try {
5119
5414
  const entries = await fs2.readdir(cacheDir, { withFileTypes: true });
@@ -5127,7 +5422,7 @@ function entryDirFor(cacheDir, input) {
5127
5422
  SCHEMA_VERSION,
5128
5423
  input.adapterPacksDigest,
5129
5424
  input.tsconfigPath ?? ""
5130
- ].join("\0");
5425
+ ].join(" ");
5131
5426
  const name = createHash2("sha256").update(key).digest("hex").slice(0, 16);
5132
5427
  return path2.join(cacheDir, `${ENTRY_PREFIX}${name}`);
5133
5428
  }
@@ -6986,7 +7281,7 @@ function calleeOf(decorator) {
6986
7281
 
6987
7282
  // src/discovery/decoratedMethod.ts
6988
7283
  function resolverTypeName(args) {
6989
- return args.classTypeName ?? args.typeMap[args.decoratorName] ?? null;
7284
+ return args.typeMap[args.decoratorName] ?? args.classTypeName ?? null;
6990
7285
  }
6991
7286
  function resolveResolverClassTypeName(decoratorArg) {
6992
7287
  if (Node32.isArrowFunction(decoratorArg)) {
@@ -9550,7 +9845,10 @@ function discoverResolverMaps(sourceFile, match, kind, resolution) {
9550
9845
  resolverInfo: {
9551
9846
  typeName,
9552
9847
  fieldName,
9553
- ...schemaSdl !== null ? { schemaSdl } : {}
9848
+ ...schemaSdl !== null ? {
9849
+ schemaSdl,
9850
+ schemaDocument: config.getSourceFile().getFilePath()
9851
+ } : {}
9554
9852
  }
9555
9853
  });
9556
9854
  }
@@ -9747,7 +10045,15 @@ function discoverUnits(sourceFile, patterns, resolution, mountPrefixes) {
9747
10045
  }
9748
10046
 
9749
10047
  // src/discovery/mountPrefix.ts
9750
- var NO_MOUNTS = { effectivePrefixFor: () => "" };
10048
+ var NO_MOUNTS = {
10049
+ // Recorded even with no mounts anywhere: a mount added later in some
10050
+ // other file changes this router's prefix without touching its file.
10051
+ effectivePrefixFor: (routerNode) => {
10052
+ recordMountPrefix(nodeId(routerNode), "");
10053
+ return "";
10054
+ },
10055
+ prefixForId: () => ""
10056
+ };
9751
10057
  function buildMountPrefixIndex(packsByFile, resolution) {
9752
10058
  const subjectIdsByPack = /* @__PURE__ */ new Map();
9753
10059
  const mountWorkByPack = /* @__PURE__ */ new Map();
@@ -9804,10 +10110,15 @@ function buildMountPrefixIndex(packsByFile, resolution) {
9804
10110
  return NO_MOUNTS;
9805
10111
  }
9806
10112
  const memo = /* @__PURE__ */ new Map();
10113
+ const byId = (childId) => resolvePrefix(edgesByChild, memo, childId, /* @__PURE__ */ new Set()) ?? "";
9807
10114
  return {
9808
10115
  effectivePrefixFor(routerNode) {
9809
- return resolvePrefix(edgesByChild, memo, nodeId(routerNode), /* @__PURE__ */ new Set()) ?? "";
9810
- }
10116
+ const childId = nodeId(routerNode);
10117
+ const prefix = byId(childId);
10118
+ recordMountPrefix(childId, prefix);
10119
+ return prefix;
10120
+ },
10121
+ prefixForId: byId
9811
10122
  };
9812
10123
  }
9813
10124
  function recordEdge(edgesByChild, candidate) {
@@ -10086,6 +10397,8 @@ var ResolutionStore = class {
10086
10397
  fullyExtracted = /* @__PURE__ */ new Set();
10087
10398
  seededValues = /* @__PURE__ */ new Set();
10088
10399
  importedNames = /* @__PURE__ */ new Map();
10400
+ /** Files the most recent wave walk entered, for the memo to keep. */
10401
+ lastQueryWalked = [];
10089
10402
  declarations = /* @__PURE__ */ new Map();
10090
10403
  graph = new ModuleGraph();
10091
10404
  stale = true;
@@ -10146,7 +10459,10 @@ var ResolutionStore = class {
10146
10459
  const declaration = `${nodeId(refersTo)}|${modules.join(",")}`;
10147
10460
  const cached = this.importedNames.get(declaration);
10148
10461
  if (cached !== void 0) {
10149
- return cached;
10462
+ for (const walkedPath of cached.walked) {
10463
+ recordFileDependency(walkedPath);
10464
+ }
10465
+ return cached.names;
10150
10466
  }
10151
10467
  const target = factKeyOf(value);
10152
10468
  const found = this.resolveByWaves(
@@ -10154,7 +10470,10 @@ var ResolutionStore = class {
10154
10470
  "wantedOrigin",
10155
10471
  () => this.lookupImportedNames(target, modules)
10156
10472
  ) ?? [];
10157
- this.importedNames.set(declaration, found);
10473
+ this.importedNames.set(declaration, {
10474
+ names: found,
10475
+ walked: [...this.lastQueryWalked]
10476
+ });
10158
10477
  return found;
10159
10478
  }
10160
10479
  /**
@@ -10188,6 +10507,7 @@ var ResolutionStore = class {
10188
10507
  this.wantValue(question, value);
10189
10508
  this.seedValue(value);
10190
10509
  const walked = /* @__PURE__ */ new Set();
10510
+ this.lastQueryWalked = [];
10191
10511
  let frontier = alsoFrom === void 0 ? [value.getSourceFile()] : [value.getSourceFile(), alsoFrom];
10192
10512
  for (let hop = 0; hop <= MAX_MODULE_HOPS; hop++) {
10193
10513
  const next = [];
@@ -10196,6 +10516,8 @@ var ResolutionStore = class {
10196
10516
  continue;
10197
10517
  }
10198
10518
  walked.add(sourceFile.getFilePath());
10519
+ this.lastQueryWalked.push(sourceFile.getFilePath());
10520
+ recordFileDependency(sourceFile.getFilePath());
10199
10521
  this.extractFile(sourceFile);
10200
10522
  next.push(...this.graph.importedFilesOf(sourceFile));
10201
10523
  }
@@ -10495,6 +10817,27 @@ function moduleInitSummary(sourceFile, effects, options) {
10495
10817
  return assembleSummary(raw, options);
10496
10818
  }
10497
10819
 
10820
+ // src/referencedFiles.ts
10821
+ function createReferenceIndex(sourceFiles) {
10822
+ const direct = /* @__PURE__ */ new Map();
10823
+ for (const sourceFile of sourceFiles) {
10824
+ const referenced = /* @__PURE__ */ new Set();
10825
+ for (const target of sourceFile.getReferencedSourceFiles()) {
10826
+ if (target.isInNodeModules() || target.isDeclarationFile()) {
10827
+ continue;
10828
+ }
10829
+ referenced.add(target.getFilePath());
10830
+ }
10831
+ direct.set(sourceFile.getFilePath(), referenced);
10832
+ }
10833
+ const none = /* @__PURE__ */ new Set();
10834
+ return {
10835
+ directOf(filePath) {
10836
+ return direct.get(filePath) ?? none;
10837
+ }
10838
+ };
10839
+ }
10840
+
10498
10841
  // src/resolve/boundaryEffects.ts
10499
10842
  import { evaluate as evaluate3, lit as lit3, rule as rule3, variable } from "@suss/datalog";
10500
10843
  var EFFECT_RULES = [
@@ -10679,6 +11022,11 @@ function classifyStop(declarations) {
10679
11022
  if (declarations.length === 0) {
10680
11023
  return "noDeclaration";
10681
11024
  }
11025
+ for (const declaration of declarations) {
11026
+ if (!isInExternalCode(declaration.getSourceFile())) {
11027
+ recordFileDependency(declaration.getSourceFile().getFilePath());
11028
+ }
11029
+ }
10682
11030
  if (declarations.every(isExternalDeclaration)) {
10683
11031
  return "outsideRun";
10684
11032
  }
@@ -10885,7 +11233,7 @@ var REACHABLE_RULES = [
10885
11233
  )
10886
11234
  ];
10887
11235
  var NO_RECOGNIZERS = { invocation: [], access: [] };
10888
- function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS) {
11236
+ function expandReachableClosure(seeds, project, options, projectFileSet, facts, recognizers = NO_RECOGNIZERS, alreadySummarized = []) {
10889
11237
  const lookup = createSourceFileLookup(project);
10890
11238
  const db = facts?.db ?? new Database3();
10891
11239
  const functionByKey = /* @__PURE__ */ new Map();
@@ -10905,6 +11253,15 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10905
11253
  db.add("entry", [key]);
10906
11254
  }
10907
11255
  }
11256
+ const knownKeys = /* @__PURE__ */ new Set();
11257
+ for (const summary of alreadySummarized) {
11258
+ const func = lookup.functionAt(summary.location);
11259
+ if (func !== null) {
11260
+ const key = nodeKey2(func);
11261
+ knownKeys.add(key);
11262
+ facts?.unitKeyBySummary.set(summary, key);
11263
+ }
11264
+ }
10908
11265
  for (; ; ) {
10909
11266
  evaluate4(db, REACHABLE_RULES);
10910
11267
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
@@ -10922,7 +11279,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10922
11279
  ...recognizers.resolveCallable === void 0 ? {} : { resolveCallable: recognizers.resolveCallable },
10923
11280
  ...cameFrom === void 0 ? {} : { reachedFrom: cameFrom }
10924
11281
  };
10925
- const { candidates, stops } = collectReachable(source.func, scan);
11282
+ const { candidates, stops } = scanWithRecording(
11283
+ key,
11284
+ facts,
11285
+ () => collectReachable(source.func, scan)
11286
+ );
10926
11287
  if (stops.length > 0) {
10927
11288
  stopsByKey.set(key, stops);
10928
11289
  }
@@ -10941,7 +11302,7 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10941
11302
  const reached = [];
10942
11303
  for (const [keyAtom] of db.facts("reachable")) {
10943
11304
  const key = String(keyAtom);
10944
- if (seedKeys.has(key)) {
11305
+ if (seedKeys.has(key) || knownKeys.has(key)) {
10945
11306
  continue;
10946
11307
  }
10947
11308
  const candidate = functionByKey.get(key);
@@ -10955,7 +11316,11 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10955
11316
  candidate.func.getSourceFile().getFilePath()
10956
11317
  );
10957
11318
  }
10958
- const summary = extractReachableSummary(candidate, options, recognizers);
11319
+ const summary = scanWithRecording(
11320
+ key,
11321
+ facts,
11322
+ () => extractReachableSummary(candidate, options, recognizers)
11323
+ );
10959
11324
  facts?.unitKeyBySummary.set(summary, key);
10960
11325
  rememberSummary(summariesByKey, key, summary);
10961
11326
  reached.push(summary);
@@ -10963,6 +11328,20 @@ function expandReachableClosure(seeds, project, options, projectFileSet, facts,
10963
11328
  recordStops(stopsByKey, summariesByKey, options);
10964
11329
  return [...seeds, ...reached];
10965
11330
  }
11331
+ function scanWithRecording(key, facts, fn) {
11332
+ const filesByKey = facts?.filesByKey;
11333
+ if (filesByKey === void 0) {
11334
+ return fn();
11335
+ }
11336
+ const sink = createDependencySink();
11337
+ const result = withDependencySink(sink, fn);
11338
+ const bucket = filesByKey.get(key) ?? /* @__PURE__ */ new Set();
11339
+ for (const filePath of sink.files) {
11340
+ bucket.add(filePath);
11341
+ }
11342
+ filesByKey.set(key, bucket);
11343
+ return result;
11344
+ }
10966
11345
  function rememberSummary(summariesByKey, key, summary) {
10967
11346
  const bucket = summariesByKey.get(key) ?? [];
10968
11347
  bucket.push(summary);
@@ -12224,6 +12603,36 @@ function extractCodeStructure(unit, pack, invocationRecognizers = [], accessReco
12224
12603
  function responseAccessorNames(pack, kind) {
12225
12604
  return pack.responseSemantics?.filter((m) => m.semantics.type === kind).map((m) => m.name);
12226
12605
  }
12606
+ var ACCESSOR_FIELD_SEMANTICS = {
12607
+ bodyAccessors: "body",
12608
+ statusAccessors: "statusCode",
12609
+ successAccessors: "statusRange"
12610
+ };
12611
+ function responseAccessors(pack) {
12612
+ const out = {};
12613
+ for (const [field, kind] of Object.entries(ACCESSOR_FIELD_SEMANTICS)) {
12614
+ const names = responseAccessorNames(pack, kind);
12615
+ if (names !== void 0 && names.length > 0) {
12616
+ out[field] = names;
12617
+ }
12618
+ }
12619
+ return out;
12620
+ }
12621
+ var FUNCTION_FALLTHROUGH_TERMINAL = {
12622
+ kind: "return",
12623
+ match: { type: "functionFallthrough" },
12624
+ extraction: {}
12625
+ };
12626
+ function terminalsFor(unit, pack) {
12627
+ const declared = unit.terminals ?? pack.terminals;
12628
+ if (unit.callSite === void 0) {
12629
+ return declared;
12630
+ }
12631
+ if (declared.some((p) => p.match.type === "functionFallthrough")) {
12632
+ return declared;
12633
+ }
12634
+ return [...declared, FUNCTION_FALLTHROUGH_TERMINAL];
12635
+ }
12227
12636
  function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers, barriers, resolveWrittenValue) {
12228
12637
  const { func, kind, name } = unit;
12229
12638
  if (func === null) {
@@ -12235,7 +12644,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12235
12644
  );
12236
12645
  const extracted = extractRawBranches(
12237
12646
  func,
12238
- unit.terminals ?? pack.terminals,
12647
+ terminalsFor(unit, pack),
12239
12648
  invocationRecognizers,
12240
12649
  accessRecognizers,
12241
12650
  barriers,
@@ -12271,8 +12680,7 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12271
12680
  }
12272
12681
  }
12273
12682
  }
12274
- const bodyAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "body") : void 0;
12275
- const statusAccessors = unit.callSite !== void 0 ? responseAccessorNames(pack, "statusCode") : void 0;
12683
+ const accessors = unit.callSite !== void 0 ? responseAccessors(pack) : {};
12276
12684
  return {
12277
12685
  identity: {
12278
12686
  name,
@@ -12296,8 +12704,8 @@ function readCodeStructure(unit, pack, invocationRecognizers, accessRecognizers,
12296
12704
  bodyContent: bodyContentOf(func),
12297
12705
  dependencyCalls: depCalls,
12298
12706
  declaredContract: null,
12299
- ...bodyAccessors !== void 0 && bodyAccessors.length > 0 ? { bodyAccessors } : {},
12300
- ...statusAccessors !== void 0 && statusAccessors.length > 0 ? { statusAccessors } : {}
12707
+ ...accessors,
12708
+ ...unit.callSite !== void 0 && pack.failureDelivery !== void 0 ? { failureDelivery: pack.failureDelivery } : {}
12301
12709
  };
12302
12710
  }
12303
12711
  function extractConsumerBinding(unit, pattern, pack, resolution) {
@@ -12651,12 +13059,20 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12651
13059
  if (claimant.pack === pack.name && claimant.file === sourceFile.getFilePath() && tally !== void 0) {
12652
13060
  tally.selfCollisions += 1;
12653
13061
  }
13062
+ if (claimant.file !== sourceFile.getFilePath()) {
13063
+ recordFileDependency(claimant.file);
13064
+ }
12654
13065
  continue;
12655
13066
  }
12656
13067
  claimed.set(claimKey, {
12657
13068
  pack: pack.name,
12658
13069
  file: sourceFile.getFilePath()
12659
13070
  });
13071
+ recordUnitClaim(claimKey, pack.name);
13072
+ const declaringFile = unit.func?.getSourceFile().getFilePath();
13073
+ if (declaringFile !== void 0 && declaringFile !== sourceFile.getFilePath()) {
13074
+ recordFileDependency(declaringFile);
13075
+ }
12660
13076
  unitsWalkedHere += 1;
12661
13077
  if (tally !== void 0) {
12662
13078
  tally.unitsClaimed += 1;
@@ -12695,6 +13111,9 @@ function extractFromSourceFile(sourceFile, frameworks, claimedUnits, options, ta
12695
13111
  }
12696
13112
  if (unit.resolverInfo.schemaSdl !== void 0) {
12697
13113
  raw.graphqlSchemaSdl = unit.resolverInfo.schemaSdl;
13114
+ if (unit.resolverInfo.schemaDocument !== void 0) {
13115
+ raw.sourceDocumentLabel = unit.resolverInfo.schemaDocument;
13116
+ }
12698
13117
  if (unit.resolverInfo.typeName !== null) {
12699
13118
  const contract = deriveGraphqlContract(
12700
13119
  unit.resolverInfo.schemaSdl,
@@ -12883,6 +13302,37 @@ function fileImporting(project, modulePrefix) {
12883
13302
  }
12884
13303
  return void 0;
12885
13304
  }
13305
+ function liftSchemasOntoDocuments(summaries) {
13306
+ const sdlByDocument = /* @__PURE__ */ new Map();
13307
+ for (const summary of summaries) {
13308
+ const label = readSourceDocumentMetadata(summary)?.label;
13309
+ const graphql = readGraphqlMetadata2(summary);
13310
+ if (label === void 0 || graphql?.schemaSdl === void 0) {
13311
+ continue;
13312
+ }
13313
+ sdlByDocument.set(label, graphql.schemaSdl);
13314
+ const { schemaSdl: _movedToTheDocument, ...rest } = graphql;
13315
+ summary.metadata = withGraphqlMetadata2(summary.metadata, rest);
13316
+ }
13317
+ for (const [label, schemaSdl] of sdlByDocument) {
13318
+ summaries.push(schemaDocumentSummary(label, schemaSdl));
13319
+ }
13320
+ }
13321
+ function schemaDocumentSummary(label, schemaSdl) {
13322
+ return {
13323
+ kind: "library",
13324
+ location: { file: label, range: { start: 0, end: 0 }, exportName: null },
13325
+ identity: { name: label, exportPath: null, boundaryBinding: null },
13326
+ inputs: [],
13327
+ transitions: [],
13328
+ gaps: [],
13329
+ confidence: { source: "declared", level: "high" },
13330
+ metadata: withGraphqlMetadata2(
13331
+ withSourceDocumentMetadata(void 0, { label }),
13332
+ { schemaSdl }
13333
+ )
13334
+ };
13335
+ }
12886
13336
  function libraryEnvReadMarker(pack, declared, importer) {
12887
13337
  return {
12888
13338
  kind: "library",
@@ -13133,8 +13583,8 @@ function buildCallerSummary(wrapper, callerFunc, callExpr, path11, options) {
13133
13583
  summary.confidence = { source: "inferred_static", level: "low" };
13134
13584
  return summary;
13135
13585
  }
13136
- function named(summaries, workspace) {
13137
- const projectRoot = commonDirectoryOf(summaries.map((s) => s.location.file));
13586
+ function named(summaries, workspace, storedProjectRoot) {
13587
+ const projectRoot = storedProjectRoot ?? commonDirectoryOf(summaries.map((s) => s.location.file));
13138
13588
  nameSummaries(summaries, {
13139
13589
  workspace: workspace ?? workspaceNameFor(projectRoot),
13140
13590
  projectRoot
@@ -13265,7 +13715,29 @@ function createTypeScriptAdapter(config) {
13265
13715
  if (config.onTiming !== void 0) {
13266
13716
  config.onTiming(timer.report());
13267
13717
  }
13268
- return named(lookup.summaries, config.workspace);
13718
+ return named(lookup.summaries, config.workspace, lookup.projectRoot);
13719
+ }
13720
+ const plan = lookup.diagnostic.missReason === "files-changed" ? await timer.timeAsync("cache.plan", () => cache.plan(cacheInput)) : null;
13721
+ if (plan !== null && plan.changed.size === 0 && plan.removed.size === 0) {
13722
+ try {
13723
+ await cache.write(
13724
+ cacheInput,
13725
+ plan.allSummaries(),
13726
+ plan.attribution()
13727
+ );
13728
+ } catch {
13729
+ }
13730
+ if (config.onCacheDiagnostic !== void 0) {
13731
+ config.onCacheDiagnostic({ kind: "hit" });
13732
+ }
13733
+ if (config.onTiming !== void 0) {
13734
+ config.onTiming(timer.report());
13735
+ }
13736
+ return named(
13737
+ plan.allSummaries(),
13738
+ config.workspace,
13739
+ plan.storedProjectRoot ?? void 0
13740
+ );
13269
13741
  }
13270
13742
  let candidatePaths = null;
13271
13743
  if (lazyEligible) {
@@ -13296,16 +13768,58 @@ function createTypeScriptAdapter(config) {
13296
13768
  "mountPrefix",
13297
13769
  () => buildMountPrefixIndex(packsByFile, resolution)
13298
13770
  );
13771
+ const caching = cacheDir !== null;
13772
+ const validRoots = /* @__PURE__ */ new Set();
13773
+ if (plan !== null) {
13774
+ const packNamesByPath = /* @__PURE__ */ new Map();
13775
+ for (const [sf, packs] of packsByFile) {
13776
+ packNamesByPath.set(
13777
+ sf.getFilePath(),
13778
+ packs.map((p) => p.name).sort().join(",")
13779
+ );
13780
+ }
13781
+ for (const rootPath of plan.validRoots) {
13782
+ const record = plan.roots.get(rootPath);
13783
+ if (record === void 0) {
13784
+ continue;
13785
+ }
13786
+ if (!mountAssumptionsAgree(record, mountPrefixes)) {
13787
+ continue;
13788
+ }
13789
+ const stored = [...record.packs].sort().join(",");
13790
+ if ((packNamesByPath.get(rootPath) ?? "") !== stored) {
13791
+ continue;
13792
+ }
13793
+ validRoots.add(rootPath);
13794
+ }
13795
+ }
13796
+ const reused = plan !== null && validRoots.size > 0 ? plan.reuse(validRoots) : null;
13299
13797
  const claimedUnits = /* @__PURE__ */ new Map();
13798
+ if (reused !== null && plan !== null) {
13799
+ for (const rootPath of validRoots) {
13800
+ for (const claim of plan.roots.get(rootPath)?.claims ?? []) {
13801
+ claimedUnits.set(claim.key, { pack: claim.pack, file: rootPath });
13802
+ }
13803
+ }
13804
+ }
13805
+ const walkList = reused === null ? sourceFiles : sourceFiles.filter((sf) => !validRoots.has(sf.getFilePath()));
13806
+ const sinkByRoot = /* @__PURE__ */ new Map();
13807
+ const ownersBySummary = /* @__PURE__ */ new Map();
13300
13808
  timer.time("extract per-file", () => {
13301
- for (const sourceFile of sourceFiles) {
13809
+ for (const sourceFile of walkList) {
13810
+ const rootPath = sourceFile.getFilePath();
13811
+ const sink = createDependencySink();
13812
+ if (caching) {
13813
+ sinkByRoot.set(rootPath, sink);
13814
+ }
13302
13815
  const applicablePacks = packsByFile.get(sourceFile);
13303
13816
  if (applicablePacks === void 0) {
13304
13817
  continue;
13305
13818
  }
13306
13819
  try {
13307
- summaries.push(
13308
- ...extractFromSourceFile(
13820
+ const walked = withDependencySink(
13821
+ sink,
13822
+ () => extractFromSourceFile(
13309
13823
  sourceFile,
13310
13824
  applicablePacks,
13311
13825
  claimedUnits,
@@ -13315,6 +13829,10 @@ function createTypeScriptAdapter(config) {
13315
13829
  mountPrefixes
13316
13830
  )
13317
13831
  );
13832
+ for (const summary of walked) {
13833
+ ownersBySummary.set(summary, /* @__PURE__ */ new Set([rootPath]));
13834
+ }
13835
+ summaries.push(...walked);
13318
13836
  } catch (error) {
13319
13837
  if (!(error instanceof RangeError)) {
13320
13838
  throw error;
@@ -13323,23 +13841,32 @@ function createTypeScriptAdapter(config) {
13323
13841
  }
13324
13842
  }
13325
13843
  });
13844
+ const wrapperInput = reused === null ? summaries : [...summaries, ...reused.summaries];
13326
13845
  const withWrappers = timer.time(
13327
13846
  "expandWrapperCallers",
13328
- () => expandWrapperCallers(summaries, project, config.extractorOptions)
13847
+ () => expandWrapperCallers(wrapperInput, project, config.extractorOptions)
13329
13848
  );
13849
+ const pipeline = reused === null ? withWrappers : [...summaries, ...withWrappers.slice(wrapperInput.length)];
13330
13850
  const withSubUnits = timer.time(
13331
13851
  "synthesizeSubUnits",
13332
13852
  () => synthesizeSubUnits(
13333
- withWrappers,
13853
+ pipeline,
13334
13854
  project,
13335
13855
  config.frameworks,
13336
13856
  config.extractorOptions,
13337
- tallies
13857
+ tallies,
13858
+ (created, parent) => {
13859
+ const parentOwners = ownersBySummary.get(parent);
13860
+ if (parentOwners !== void 0) {
13861
+ ownersBySummary.set(created, new Set(parentOwners));
13862
+ }
13863
+ }
13338
13864
  )
13339
13865
  );
13340
13866
  const closureFacts = {
13341
13867
  db: new Database5(),
13342
- unitKeyBySummary: /* @__PURE__ */ new Map()
13868
+ unitKeyBySummary: /* @__PURE__ */ new Map(),
13869
+ ...caching ? { filesByKey: /* @__PURE__ */ new Map() } : {}
13343
13870
  };
13344
13871
  const withClosure = config.includeReachable !== false ? timer.time(
13345
13872
  "expandReachableClosure",
@@ -13357,12 +13884,28 @@ function createTypeScriptAdapter(config) {
13357
13884
  access: collectAccessRecognizers(config.frameworks, tallies),
13358
13885
  resolveWrittenValue: (value) => resolution.resolveWrittenValue(value),
13359
13886
  resolveCallable: (value, alsoFrom) => resolution.resolveCallable(value, alsoFrom)
13360
- }
13887
+ },
13888
+ // Reached units the cache already serves emit nothing,
13889
+ // the way a cold run's seeds do not.
13890
+ reused?.summaries ?? []
13361
13891
  )
13362
13892
  ) : withSubUnits;
13893
+ const closureOwnership = caching && config.includeReachable !== false ? timer.time(
13894
+ "cache.attributeClosure",
13895
+ () => attributeReachedSummaries(
13896
+ withSubUnits,
13897
+ withClosure.slice(withSubUnits.length),
13898
+ closureFacts,
13899
+ ownersBySummary
13900
+ )
13901
+ ) : null;
13902
+ const merged = timer.time(
13903
+ "cache.merge",
13904
+ () => mergeWithReused(reused, withClosure, ownersBySummary)
13905
+ );
13363
13906
  const enriched = timer.time(
13364
13907
  "enrichRethrows",
13365
- () => enrichRethrows(withClosure, project, closureFacts)
13908
+ () => enrichRethrows(merged.summaries, project, closureFacts)
13366
13909
  );
13367
13910
  if (config.includeReachable !== false) {
13368
13911
  timer.time(
@@ -13378,6 +13921,10 @@ function createTypeScriptAdapter(config) {
13378
13921
  "emitLibraryEnvReadMarkers",
13379
13922
  () => emitLibraryEnvReadMarkers(enriched, project, config.frameworks)
13380
13923
  );
13924
+ timer.time(
13925
+ "liftSchemasOntoDocuments",
13926
+ () => liftSchemasOntoDocuments(enriched)
13927
+ );
13381
13928
  timer.time(
13382
13929
  "stampGraphqlClientRefs",
13383
13930
  () => stampGraphqlClientRefs(
@@ -13387,12 +13934,45 @@ function createTypeScriptAdapter(config) {
13387
13934
  resolution
13388
13935
  )
13389
13936
  );
13937
+ if (config.onCacheDiagnostic !== void 0 && plan !== null) {
13938
+ config.onCacheDiagnostic({
13939
+ kind: "partial",
13940
+ partial: {
13941
+ filesChanged: plan.changed.size,
13942
+ filesRemoved: plan.removed.size,
13943
+ rootsReused: validRoots.size,
13944
+ rootsReextracted: walkList.length,
13945
+ rootsDeclined: plan.rootsDeclined,
13946
+ summariesReused: merged.reusedKept.length
13947
+ }
13948
+ });
13949
+ }
13950
+ const runProjectRoot = commonDirectoryOf(
13951
+ sourceFiles.map((f) => f.getFilePath())
13952
+ );
13390
13953
  await timer.timeAsync("cache.write", async () => {
13391
- if (enriched.length === 0) {
13954
+ if (!caching || enriched.length === 0) {
13392
13955
  return;
13393
13956
  }
13394
13957
  try {
13395
- await cache.write(cacheInput, enriched);
13958
+ const attribution = buildCacheAttribution({
13959
+ project,
13960
+ plan,
13961
+ validRoots,
13962
+ sinkByRoot,
13963
+ ownersBySummary,
13964
+ closureOwnership,
13965
+ unitKeyBySummary: closureFacts.unitKeyBySummary,
13966
+ filesByKey: closureFacts.filesByKey,
13967
+ packsByFile,
13968
+ reusedKept: merged.reusedKept,
13969
+ reusedOwners: merged.reusedOwners,
13970
+ // Everything after the reused prefix, the passes' own
13971
+ // additions (markers, schema documents) included.
13972
+ fresh: enriched.slice(merged.reusedKept.length),
13973
+ projectRoot: runProjectRoot
13974
+ });
13975
+ await cache.write(cacheInput, enriched, attribution);
13396
13976
  } catch {
13397
13977
  }
13398
13978
  });
@@ -13405,7 +13985,7 @@ function createTypeScriptAdapter(config) {
13405
13985
  packs: config.frameworks,
13406
13986
  tallies,
13407
13987
  filesInProject: tsconfigFileList?.length ?? null,
13408
- filesWalked: sourceFiles.length,
13988
+ filesWalked: walkList.length,
13409
13989
  summaries: enriched,
13410
13990
  tsConfigFilePath: config.tsConfigFilePath,
13411
13991
  projectRoot: commonDirectoryOf(
@@ -13425,7 +14005,200 @@ function createTypeScriptAdapter(config) {
13425
14005
  }
13426
14006
  };
13427
14007
  }
13428
- function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
14008
+ function mountAssumptionsAgree(record, index) {
14009
+ return Object.entries(record.mountPrefixes).every(
14010
+ ([childId, prefix]) => (index.prefixForId?.(childId) ?? "") === prefix
14011
+ );
14012
+ }
14013
+ function attributeReachedSummaries(seeds, reached, facts, owners) {
14014
+ const adjacency = /* @__PURE__ */ new Map();
14015
+ for (const [from, to] of facts.db.facts("calls")) {
14016
+ const key = String(from);
14017
+ const bucket = adjacency.get(key) ?? [];
14018
+ bucket.push(String(to));
14019
+ adjacency.set(key, bucket);
14020
+ }
14021
+ const seedKeysByRoot = /* @__PURE__ */ new Map();
14022
+ for (const seed of seeds) {
14023
+ const key = facts.unitKeyBySummary.get(seed);
14024
+ if (key === void 0) {
14025
+ continue;
14026
+ }
14027
+ for (const root of owners.get(seed) ?? []) {
14028
+ const bucket = seedKeysByRoot.get(root) ?? [];
14029
+ bucket.push(key);
14030
+ seedKeysByRoot.set(root, bucket);
14031
+ }
14032
+ }
14033
+ const reachableByRoot = /* @__PURE__ */ new Map();
14034
+ const rootsByKey = /* @__PURE__ */ new Map();
14035
+ for (const [root, seedKeys] of seedKeysByRoot) {
14036
+ const seen = new Set(seedKeys);
14037
+ const queue = [...seedKeys];
14038
+ while (queue.length > 0) {
14039
+ const key = queue.pop();
14040
+ if (key === void 0) {
14041
+ continue;
14042
+ }
14043
+ for (const next of adjacency.get(key) ?? []) {
14044
+ if (!seen.has(next)) {
14045
+ seen.add(next);
14046
+ queue.push(next);
14047
+ }
14048
+ }
14049
+ const keyRoots = rootsByKey.get(key) ?? /* @__PURE__ */ new Set();
14050
+ keyRoots.add(root);
14051
+ rootsByKey.set(key, keyRoots);
14052
+ }
14053
+ reachableByRoot.set(root, seen);
14054
+ }
14055
+ for (const summary of reached) {
14056
+ const key = facts.unitKeyBySummary.get(summary);
14057
+ if (key === void 0) {
14058
+ continue;
14059
+ }
14060
+ const keyRoots = rootsByKey.get(key);
14061
+ if (keyRoots !== void 0 && keyRoots.size > 0) {
14062
+ owners.set(summary, new Set(keyRoots));
14063
+ }
14064
+ }
14065
+ return { reachableByRoot, rootsByKey };
14066
+ }
14067
+ function fileOfNodeKey(key) {
14068
+ const cut = key.lastIndexOf(":");
14069
+ return cut <= 0 ? null : key.slice(0, cut);
14070
+ }
14071
+ function summaryMergeKey(summary) {
14072
+ return JSON.stringify([
14073
+ summary.location.file,
14074
+ summary.location.range.start,
14075
+ summary.location.range.end,
14076
+ summary.kind,
14077
+ summary.identity.name,
14078
+ summary.identity.exportPath,
14079
+ summary.identity.boundaryBinding
14080
+ ]);
14081
+ }
14082
+ function mergeWithReused(reused, fresh, ownersBySummary) {
14083
+ if (reused === null) {
14084
+ return { summaries: fresh, reusedKept: [], reusedOwners: [] };
14085
+ }
14086
+ const freshByKey = /* @__PURE__ */ new Map();
14087
+ for (const summary of fresh) {
14088
+ freshByKey.set(summaryMergeKey(summary), summary);
14089
+ }
14090
+ const reusedKept = [];
14091
+ const reusedOwners = [];
14092
+ reused.summaries.forEach((summary, i) => {
14093
+ const owners = reused.owners[i] ?? [];
14094
+ const supersededBy = freshByKey.get(summaryMergeKey(summary));
14095
+ if (supersededBy !== void 0) {
14096
+ const freshOwners = ownersBySummary.get(supersededBy) ?? /* @__PURE__ */ new Set();
14097
+ for (const owner of owners) {
14098
+ freshOwners.add(owner);
14099
+ }
14100
+ ownersBySummary.set(supersededBy, freshOwners);
14101
+ return;
14102
+ }
14103
+ reusedKept.push(summary);
14104
+ reusedOwners.push(owners);
14105
+ });
14106
+ return { summaries: [...reusedKept, ...fresh], reusedKept, reusedOwners };
14107
+ }
14108
+ function readsRunLevelJoins(summary) {
14109
+ if (readSourceDocumentMetadata(summary) !== void 0) {
14110
+ return true;
14111
+ }
14112
+ if (readGraphqlMetadata2(summary)?.client !== void 0) {
14113
+ return true;
14114
+ }
14115
+ const semantics = summary.identity.boundaryBinding?.semantics;
14116
+ return semantics !== void 0 && "operationType" in semantics;
14117
+ }
14118
+ function buildCacheAttribution(args) {
14119
+ const references = createReferenceIndex(
14120
+ args.project.getSourceFiles().filter((sf) => !sf.isDeclarationFile())
14121
+ );
14122
+ const packNamesByPath = /* @__PURE__ */ new Map();
14123
+ for (const [sf, packs] of args.packsByFile) {
14124
+ packNamesByPath.set(
14125
+ sf.getFilePath(),
14126
+ packs.map((p) => p.name)
14127
+ );
14128
+ }
14129
+ const summariesByRoot = /* @__PURE__ */ new Map();
14130
+ for (const summary of args.fresh) {
14131
+ for (const root of args.ownersBySummary.get(summary) ?? []) {
14132
+ const bucket = summariesByRoot.get(root) ?? [];
14133
+ bucket.push(summary);
14134
+ summariesByRoot.set(root, bucket);
14135
+ }
14136
+ }
14137
+ const roots = [];
14138
+ for (const [rootPath, sink] of args.sinkByRoot) {
14139
+ const deps = new Set(sink.files);
14140
+ for (const dep of references.directOf(rootPath)) {
14141
+ deps.add(dep);
14142
+ }
14143
+ let cacheable = true;
14144
+ for (const summary of summariesByRoot.get(rootPath) ?? []) {
14145
+ deps.add(summary.location.file);
14146
+ if (readsRunLevelJoins(summary)) {
14147
+ cacheable = false;
14148
+ }
14149
+ }
14150
+ const reachable = args.closureOwnership?.reachableByRoot.get(rootPath) ?? [];
14151
+ for (const key of reachable) {
14152
+ const keyFile = fileOfNodeKey(key);
14153
+ if (keyFile !== null) {
14154
+ deps.add(keyFile);
14155
+ for (const hop of references.directOf(keyFile)) {
14156
+ deps.add(hop);
14157
+ }
14158
+ }
14159
+ for (const filePath of args.filesByKey?.get(key) ?? []) {
14160
+ deps.add(filePath);
14161
+ }
14162
+ }
14163
+ deps.delete(rootPath);
14164
+ roots.push({
14165
+ path: rootPath,
14166
+ cacheable,
14167
+ deps: [...deps].sort(),
14168
+ claims: sink.claims,
14169
+ mountPrefixes: Object.fromEntries(sink.mountPrefixes),
14170
+ packs: packNamesByPath.get(rootPath) ?? []
14171
+ });
14172
+ }
14173
+ if (args.plan !== null) {
14174
+ for (const rootPath of args.validRoots) {
14175
+ const record = args.plan.roots.get(rootPath);
14176
+ if (record !== void 0) {
14177
+ roots.push(record);
14178
+ }
14179
+ }
14180
+ }
14181
+ const reusedOwners = args.reusedKept.map((summary, i) => {
14182
+ const combined = new Set(args.reusedOwners[i] ?? []);
14183
+ const key = args.unitKeyBySummary.get(summary);
14184
+ if (key !== void 0) {
14185
+ for (const root of args.closureOwnership?.rootsByKey.get(key) ?? []) {
14186
+ combined.add(root);
14187
+ }
14188
+ }
14189
+ return [...combined];
14190
+ });
14191
+ const owners = [
14192
+ ...reusedOwners,
14193
+ ...args.fresh.map((s) => [...args.ownersBySummary.get(s) ?? []])
14194
+ ];
14195
+ return {
14196
+ roots,
14197
+ owners,
14198
+ ...args.projectRoot === void 0 ? {} : { projectRoot: args.projectRoot }
14199
+ };
14200
+ }
14201
+ function synthesizeSubUnits(summaries, project, frameworks, options, tallies, onCreated) {
13429
14202
  const packByRecognition = /* @__PURE__ */ new Map();
13430
14203
  for (const pack of frameworks) {
13431
14204
  packByRecognition.set(pack.name, pack);
@@ -13468,6 +14241,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13468
14241
  );
13469
14242
  if (summary !== null) {
13470
14243
  synthesized.push(summary);
14244
+ onCreated?.(summary, parent);
13471
14245
  }
13472
14246
  }
13473
14247
  }
@@ -13476,14 +14250,7 @@ function synthesizeSubUnits(summaries, project, frameworks, options, tallies) {
13476
14250
  var DEFAULT_SUB_UNIT_TERMINALS = [
13477
14251
  { kind: "return", match: { type: "returnStatement" }, extraction: {} },
13478
14252
  { 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
- }
14253
+ FUNCTION_FALLTHROUGH_TERMINAL
13487
14254
  ];
13488
14255
  var DEFAULT_SUB_UNIT_INPUT_MAPPING = {
13489
14256
  type: "positionalParams",
@@ -13795,6 +14562,7 @@ function formatPackHealth(checks, audiences) {
13795
14562
 
13796
14563
  // src/resolve/readName.ts
13797
14564
  import { Node as Node56, SyntaxKind as SyntaxKind12 } from "ts-morph";
14565
+ import { patternHole, referenceName } from "@suss/behavioral-ir";
13798
14566
  var MAX_HELPER_HOPS = 2;
13799
14567
  var UNNAMED_HOLE = "param";
13800
14568
  function readName(expr, options = {}) {
@@ -13808,6 +14576,14 @@ function readName(expr, options = {}) {
13808
14576
  if (name !== null || options.unsettled !== "reference") {
13809
14577
  return name;
13810
14578
  }
14579
+ const reference = referenceIn(expr);
14580
+ return reference === null ? null : referenceName(reference);
14581
+ }
14582
+ function referenceIn(expr) {
14583
+ const fromParameter = parameterPath(expr);
14584
+ if (fromParameter !== null) {
14585
+ return fromParameter;
14586
+ }
13811
14587
  const asked = holeName(expr, {
13812
14588
  resolve: () => null,
13813
14589
  bindings: /* @__PURE__ */ new Map(),
@@ -13815,7 +14591,25 @@ function readName(expr, options = {}) {
13815
14591
  seen: /* @__PURE__ */ new Set(),
13816
14592
  insideHole: true
13817
14593
  });
13818
- return asked === UNNAMED_HOLE ? null : `{${asked}}`;
14594
+ return asked === UNNAMED_HOLE ? null : { root: asked, fields: [] };
14595
+ }
14596
+ function parameterPath(expr) {
14597
+ const fields = [];
14598
+ let step = expr;
14599
+ while (Node56.isPropertyAccessExpression(step)) {
14600
+ fields.unshift(step.getName());
14601
+ step = step.getExpression();
14602
+ }
14603
+ if (!Node56.isIdentifier(step) || !isParameter(step)) {
14604
+ return null;
14605
+ }
14606
+ return { root: step.getText(), fields };
14607
+ }
14608
+ function isParameter(identifier) {
14609
+ const declarations = identifier.getSymbol()?.getDeclarations() ?? [];
14610
+ return declarations.some(
14611
+ (declaration) => Node56.isParameterDeclaration(declaration)
14612
+ );
13819
14613
  }
13820
14614
  function read(expr, ctx) {
13821
14615
  if (ctx.seen.has(expr)) {
@@ -14012,7 +14806,7 @@ function boundOnce(expr, ctx) {
14012
14806
  }
14013
14807
  function readOrHole(expr, ctx) {
14014
14808
  const name = read(expr, ctx);
14015
- return name ?? `{${holeName(expr, ctx)}}`;
14809
+ return name ?? patternHole(holeName(expr, ctx));
14016
14810
  }
14017
14811
  function holeName(expr, ctx) {
14018
14812
  const bound = boundOnce(expr, ctx);