@suss/adapter-python 0.34.0 → 0.34.1

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
@@ -153,10 +153,7 @@ function stringLiteralValue(node) {
153
153
  return null;
154
154
  }
155
155
  let content = "";
156
- for (const child of node.namedChildren) {
157
- if (child === null) {
158
- continue;
159
- }
156
+ for (const child of children(node)) {
160
157
  if (child.type === "string_content") {
161
158
  content += stringContentValue(child);
162
159
  continue;
@@ -168,7 +165,29 @@ function stringLiteralValue(node) {
168
165
  return content;
169
166
  }
170
167
  function children(node) {
171
- return node.namedChildren.filter((child) => child !== null);
168
+ if (node === null || node === void 0) {
169
+ return [];
170
+ }
171
+ const found = [];
172
+ const count = node.namedChildCount;
173
+ for (let index = 0; index < count; index++) {
174
+ const child = node.namedChild(index);
175
+ if (child !== null) {
176
+ found.push(child);
177
+ }
178
+ }
179
+ return found;
180
+ }
181
+ function allChildren(node) {
182
+ const found = [];
183
+ const count = node.childCount;
184
+ for (let index = 0; index < count; index++) {
185
+ const child = node.child(index);
186
+ if (child !== null) {
187
+ found.push(child);
188
+ }
189
+ }
190
+ return found;
172
191
  }
173
192
  function bodyStatements(body) {
174
193
  return children(body);
@@ -200,11 +219,59 @@ function stripDecorators(node) {
200
219
  if (definition === null) {
201
220
  throw new Error("decorated_definition with no definition field");
202
221
  }
203
- const decorators = node.namedChildren.filter(
204
- (child) => child !== null && child.type === "decorator"
222
+ const decorators = children(node).filter(
223
+ (child) => child.type === "decorator"
205
224
  );
206
225
  return { definition, decorators };
207
226
  }
227
+ var isChild = (fieldName) => (parent, child) => field(parent, fieldName)?.id === child.id;
228
+ var always = () => true;
229
+ var asCondition = isChild("condition");
230
+ var isConditionalTest = (parent, child) => parent.namedChild(1)?.id === child.id;
231
+ var VALUE_USED_UNDER = {
232
+ assignment: isChild("right"),
233
+ augmented_assignment: isChild("right"),
234
+ named_expression: isChild("value"),
235
+ attribute: isChild("object"),
236
+ subscript: always,
237
+ argument_list: always,
238
+ keyword_argument: isChild("value"),
239
+ pair: always,
240
+ list: always,
241
+ tuple: always,
242
+ set: always,
243
+ dictionary: always,
244
+ interpolation: always,
245
+ comparison_operator: always,
246
+ binary_operator: always,
247
+ unary_operator: always,
248
+ not_operator: always,
249
+ if_statement: asCondition,
250
+ elif_clause: asCondition,
251
+ while_statement: asCondition,
252
+ conditional_expression: isConditionalTest,
253
+ assert_statement: always,
254
+ for_statement: isChild("right"),
255
+ for_in_clause: isChild("right"),
256
+ with_item: always,
257
+ as_pattern: always
258
+ };
259
+ var PASSES_VALUE_ON = {
260
+ parenthesized_expression: always,
261
+ await: always,
262
+ boolean_operator: always,
263
+ conditional_expression: (parent, child) => !isConditionalTest(parent, child)
264
+ };
265
+ function resultUsedAsValue(node) {
266
+ let child = node;
267
+ let parent = node.parent;
268
+ while (parent !== null && PASSES_VALUE_ON[parent.type]?.(parent, child) === true) {
269
+ child = parent;
270
+ parent = parent.parent;
271
+ }
272
+ const used = parent === null ? void 0 : VALUE_USED_UNDER[parent.type];
273
+ return parent !== null && used !== void 0 && used(parent, child);
274
+ }
208
275
  var NodeSet = class extends import_extractor.IdSet {
209
276
  };
210
277
  var NodeMap = class extends import_extractor.IdMap {
@@ -266,7 +333,7 @@ function bindWithStatement(stmt, scope, ctx) {
266
333
  }
267
334
  }
268
335
  function asPatternsIn(stmt, found = []) {
269
- for (const child of stmt.namedChildren) {
336
+ for (const child of children(stmt)) {
270
337
  if (child === null || child.type === "block") {
271
338
  continue;
272
339
  }
@@ -295,12 +362,12 @@ function bindDeleteStatement(stmt, scope) {
295
362
  return;
296
363
  }
297
364
  if (node.type === "expression_list") {
298
- for (const child of node.namedChildren) {
365
+ for (const child of children(node)) {
299
366
  unbind(child);
300
367
  }
301
368
  }
302
369
  };
303
- for (const child of stmt.namedChildren) {
370
+ for (const child of children(stmt)) {
304
371
  unbind(child);
305
372
  }
306
373
  }
@@ -341,10 +408,7 @@ function bindImportStatement(stmt, scope) {
341
408
  function relativeImportParts(node) {
342
409
  let relativeLevel = 0;
343
410
  let module2 = "";
344
- for (const child of node.namedChildren) {
345
- if (child === null) {
346
- continue;
347
- }
411
+ for (const child of children(node)) {
348
412
  if (child.type === "import_prefix") {
349
413
  relativeLevel += child.text.length;
350
414
  continue;
@@ -361,8 +425,8 @@ function bindImportFromStatement(stmt, scope, ctx) {
361
425
  return;
362
426
  }
363
427
  const { module: module2, relativeLevel } = moduleNode.type === "relative_import" ? relativeImportParts(moduleNode) : { module: moduleNode.text, relativeLevel: 0 };
364
- const hasWildcard = stmt.namedChildren.some(
365
- (child) => child !== null && child.type === "wildcard_import"
428
+ const hasWildcard = children(stmt).some(
429
+ (child) => child.type === "wildcard_import"
366
430
  );
367
431
  if (hasWildcard) {
368
432
  ctx.openImports.push(
@@ -420,10 +484,7 @@ function bindFunctionDefinition(stmt, scope, ctx) {
420
484
  }
421
485
  }
422
486
  function bindParameters(parametersNode, functionScope) {
423
- for (const param of parametersNode.namedChildren) {
424
- if (param === null) {
425
- continue;
426
- }
487
+ for (const param of children(parametersNode)) {
427
488
  const name = parameterName(param);
428
489
  if (name !== null) {
429
490
  bindName(functionScope, name, { kind: "parameter" });
@@ -440,9 +501,7 @@ function parameterName(param) {
440
501
  "list_splat_pattern",
441
502
  "dictionary_splat_pattern"
442
503
  )) {
443
- const inner = param.namedChildren.find(
444
- (child) => child !== null && child.type === "identifier"
445
- );
504
+ const inner = children(param).find((child) => child.type === "identifier");
446
505
  return inner?.text ?? null;
447
506
  }
448
507
  if (isType(param, "default_parameter", "typed_default_parameter")) {
@@ -469,8 +528,8 @@ function bindClassDefinition(stmt, scope, ctx) {
469
528
  }
470
529
  }
471
530
  function bindExpressionStatement(stmt, scope) {
472
- const assignment2 = stmt.namedChildren.find(
473
- (child) => child !== null && child.type === "assignment"
531
+ const assignment2 = children(stmt).find(
532
+ (child) => child.type === "assignment"
474
533
  );
475
534
  if (assignment2 === void 0 || assignment2 === null) {
476
535
  return;
@@ -487,15 +546,15 @@ function bindExpressionStatement(stmt, scope) {
487
546
  }
488
547
  }
489
548
  function bindGlobalStatement(stmt, scope) {
490
- for (const child of stmt.namedChildren) {
491
- if (child !== null && child.type === "identifier") {
549
+ for (const child of children(stmt)) {
550
+ if (child.type === "identifier") {
492
551
  scope.bindings.set(child.text, { kind: "global" });
493
552
  }
494
553
  }
495
554
  }
496
555
  function bindNonlocalStatement(stmt, scope) {
497
- for (const child of stmt.namedChildren) {
498
- if (child !== null && child.type === "identifier") {
556
+ for (const child of children(stmt)) {
557
+ if (child.type === "identifier") {
499
558
  scope.bindings.set(child.text, { kind: "nonlocal" });
500
559
  }
501
560
  }
@@ -644,18 +703,9 @@ function shapeFromName(name, scope, ctx) {
644
703
  if (DICT_NAMES.has(name)) {
645
704
  return { type: "dictionary", values: { type: "unknown" } };
646
705
  }
647
- const binding = resolveName(scope, name);
648
- if (binding?.kind === "classDef") {
649
- return recordShapeRef(name, binding.node, ctx);
650
- }
651
- if (binding?.kind === "import" || binding?.kind === "importFrom") {
652
- const imported = ctx.importedDefinition?.(scope, name) ?? null;
653
- if (imported?.node.type === "class_definition") {
654
- if (!ctx.scopeMaps.includes(imported.scopeFor)) {
655
- ctx.scopeMaps.push(imported.scopeFor);
656
- }
657
- return recordShapeRef(name, imported.node, ctx);
658
- }
706
+ const classNode = projectClassNamed(name, scope, ctx);
707
+ if (classNode !== null) {
708
+ return recordShapeRef(name, classNode, ctx);
659
709
  }
660
710
  const alias = aliasValueOf(name, scope, ctx);
661
711
  if (alias !== null) {
@@ -663,6 +713,23 @@ function shapeFromName(name, scope, ctx) {
663
713
  }
664
714
  return { type: "ref", name };
665
715
  }
716
+ function projectClassNamed(name, scope, ctx) {
717
+ const binding = resolveName(scope, name);
718
+ if (binding?.kind === "classDef") {
719
+ return binding.node;
720
+ }
721
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
722
+ return null;
723
+ }
724
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
725
+ if (imported?.node.type !== "class_definition") {
726
+ return null;
727
+ }
728
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
729
+ ctx.scopeMaps.push(imported.scopeFor);
730
+ }
731
+ return imported.node;
732
+ }
666
733
  function shapeFromAliasValue(name, alias, ctx) {
667
734
  if (ctx.expanding.has(alias.node.id)) {
668
735
  return { type: "ref", name };
@@ -689,8 +756,9 @@ function shapeFromSubscript(node, scope, ctx) {
689
756
  );
690
757
  }
691
758
  function annotationTarget(annotation) {
692
- if (annotation.type === "type" && annotation.namedChildren[0]) {
693
- return annotationTarget(annotation.namedChildren[0]);
759
+ const inner = annotation.type === "type" ? annotation.namedChild(0) : null;
760
+ if (inner !== null) {
761
+ return annotationTarget(inner);
694
762
  }
695
763
  if (annotation.type === "binary_operator") {
696
764
  const named = [field(annotation, "left"), field(annotation, "right")].find(
@@ -699,7 +767,7 @@ function annotationTarget(annotation) {
699
767
  return named === void 0 || named === null ? null : annotationTarget(named);
700
768
  }
701
769
  if (annotation.type === "generic_type") {
702
- const outer = annotation.namedChildren[0];
770
+ const outer = annotation.namedChild(0);
703
771
  const first = genericTypeArgs(annotation)[0];
704
772
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
705
773
  return annotationTarget(first);
@@ -716,12 +784,10 @@ function typeNameOf(annotation) {
716
784
  return target.type === "identifier" ? target.text : stringLiteralValue(target);
717
785
  }
718
786
  function genericTypeArgs(node) {
719
- const typeParameter = node.namedChildren.find(
720
- (child) => child !== null && child.type === "type_parameter"
787
+ const typeParameter = children(node).find(
788
+ (child) => child.type === "type_parameter"
721
789
  );
722
- return typeParameter?.namedChildren.filter(
723
- (child) => child !== null && child.type === "type"
724
- ) ?? [];
790
+ return children(typeParameter).filter((child) => child.type === "type");
725
791
  }
726
792
  function shapeFromGenericType(node, scope, ctx) {
727
793
  const base = node.namedChild(0);
@@ -773,22 +839,44 @@ function recordShapeRef(name, classNode, ctx) {
773
839
  return ref;
774
840
  }
775
841
  ctx.definitions.set(key, null);
842
+ ctx.definitions.set(key, {
843
+ type: "record",
844
+ properties: classFields(classNode, ctx, /* @__PURE__ */ new Set())
845
+ });
846
+ return ref;
847
+ }
848
+ function classFields(classNode, ctx, visiting) {
776
849
  const bodyNode = field(classNode, "body");
777
850
  const classScope = scopeOfNode(ctx, classNode);
778
- ctx.definitions.set(
779
- key,
780
- bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
851
+ if (bodyNode === null || classScope === void 0 || visiting.has(classNode.id)) {
852
+ return {};
853
+ }
854
+ visiting.add(classNode.id);
855
+ const inherited = baseClassNames(classNode).flatMap((name) => {
856
+ const base = projectClassNamed(name, classScope, ctx);
857
+ return base === null ? [] : [classFields(base, ctx, visiting)];
858
+ });
859
+ const own = recordShapeOf(bodyNode, classScope, ctx);
860
+ return Object.assign(
861
+ {},
862
+ ...inherited.reverse(),
863
+ own.type === "record" ? own.properties : {}
864
+ );
865
+ }
866
+ function baseClassNames(classNode) {
867
+ const superclasses = field(classNode, "superclasses");
868
+ return children(superclasses).flatMap(
869
+ (child) => child.type === "identifier" ? [child.text] : []
781
870
  );
782
- return ref;
783
871
  }
784
872
  function recordShapeOf(bodyNode, classScope, ctx) {
785
873
  const properties = {};
786
- for (const stmt of bodyNode.namedChildren) {
874
+ for (const stmt of children(bodyNode)) {
787
875
  if (stmt === null || stmt.type !== "expression_statement") {
788
876
  continue;
789
877
  }
790
- const assignment2 = stmt.namedChildren.find(
791
- (child) => child !== null && child.type === "assignment"
878
+ const assignment2 = children(stmt).find(
879
+ (child) => child.type === "assignment"
792
880
  );
793
881
  if (assignment2 === void 0 || assignment2 === null) {
794
882
  continue;
@@ -798,6 +886,9 @@ function recordShapeOf(bodyNode, classScope, ctx) {
798
886
  if (left === null || left.type !== "identifier" || typeNode === null) {
799
887
  continue;
800
888
  }
889
+ if (typeNameOf(typeNode) === "ClassVar") {
890
+ continue;
891
+ }
801
892
  properties[left.text] = annotationToShape(typeNode, classScope, ctx);
802
893
  }
803
894
  return { type: "record", properties };
@@ -807,13 +898,14 @@ function shortHash(text2) {
807
898
  }
808
899
 
809
900
  // src/decorators.ts
810
- var import_behavioral_ir = require("@suss/behavioral-ir");
901
+ var import_behavioral_ir2 = require("@suss/behavioral-ir");
811
902
 
812
903
  // src/values/evaluator.ts
813
904
  var import_resolution4 = require("@suss/resolution");
814
905
  var import_values4 = require("@suss/values");
815
906
 
816
907
  // src/facts/resolve.ts
908
+ var import_behavioral_ir = require("@suss/behavioral-ir");
817
909
  var import_resolution = require("@suss/resolution");
818
910
  var import_resolution2 = require("@suss/resolution");
819
911
  var evaluationsUnderSite = /* @__PURE__ */ new WeakMap();
@@ -859,7 +951,13 @@ function constructionSites(db, classKey) {
859
951
  }
860
952
  function writtenValueOf(db, key) {
861
953
  resolveWritten(db, [key]);
862
- return (0, import_resolution.writtenValueOf)(db, key, (keys) => resolveWritten(db, keys));
954
+ return (0, import_resolution.writtenValueOf)(
955
+ db,
956
+ key,
957
+ (keys) => resolveWritten(db, keys),
958
+ void 0,
959
+ (0, import_resolution.answersFromTestsLeftOut)(key, import_behavioral_ir.isTestFile)
960
+ );
863
961
  }
864
962
  function writtenValuesOf(db, key) {
865
963
  resolveWritten(db, [key]);
@@ -1480,10 +1578,10 @@ function emitExpressionFacts(emitter, node) {
1480
1578
  });
1481
1579
  }
1482
1580
  function yieldedValue(node) {
1483
- if (node.children.some((child) => child?.type === "from")) {
1581
+ if (allChildren(node).some((child) => child.type === "from")) {
1484
1582
  return null;
1485
1583
  }
1486
- return node.namedChildren.find((child) => child?.type !== "comment") ?? null;
1584
+ return children(node).find((child) => child?.type !== "comment") ?? null;
1487
1585
  }
1488
1586
  function emitFunctionFacts(emitter, fn, classKey) {
1489
1587
  const funcKey = nodeId(emitter.filePath, fn);
@@ -1555,7 +1653,7 @@ function emitFunctionFacts(emitter, fn, classKey) {
1555
1653
  const stores = /* @__PURE__ */ new Map();
1556
1654
  const visit = (child, type) => {
1557
1655
  if (type === "return_statement") {
1558
- const returned = child.namedChildren[0];
1656
+ const returned = child.namedChild(0);
1559
1657
  if (returned != null) {
1560
1658
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1561
1659
  statesReturn = true;
@@ -2593,9 +2691,7 @@ function stringExpression(node) {
2593
2691
  }
2594
2692
  function comparisonExpression(node) {
2595
2693
  const operands = children(node);
2596
- const operators = node.children.filter(
2597
- (child) => child !== null && !child.isNamed
2598
- );
2694
+ const operators = allChildren(node).filter((child) => !child.isNamed);
2599
2695
  const operator = operators[0];
2600
2696
  if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2601
2697
  return { kind: "opaque" };
@@ -2689,6 +2785,7 @@ var STATEMENT_TYPES = {
2689
2785
  kind: "return",
2690
2786
  value: children(node)[0] ?? null
2691
2787
  }),
2788
+ raise_statement: (node) => caughtInSameFunction(node) ? OPAQUE : { kind: "throw" },
2692
2789
  try_statement: (node) => ({
2693
2790
  kind: "block",
2694
2791
  body: [
@@ -2777,6 +2874,19 @@ function blockStatements(block) {
2777
2874
  function isRoot(node) {
2778
2875
  return ROOT_TYPES.has(node.type);
2779
2876
  }
2877
+ var EXCEPT_CLAUSE_TYPES = /* @__PURE__ */ new Set(["except_clause", "except_group_clause"]);
2878
+ function caughtInSameFunction(node) {
2879
+ let child = node;
2880
+ let parent = node.parent;
2881
+ while (parent !== null && !isRoot(parent)) {
2882
+ if (parent.type === "try_statement" && field(parent, "body")?.id === child.id && children(parent).some((clause) => EXCEPT_CLAUSE_TYPES.has(clause.type))) {
2883
+ return true;
2884
+ }
2885
+ child = parent;
2886
+ parent = parent.parent;
2887
+ }
2888
+ return false;
2889
+ }
2780
2890
  function isPathStatement(node, parent) {
2781
2891
  return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2782
2892
  }
@@ -3167,6 +3277,10 @@ function writtenNodeOf(node, db) {
3167
3277
  const bound = db === void 0 ? void 0 : projects.get(db);
3168
3278
  return bound?.context.writtenTo(node) ?? null;
3169
3279
  }
3280
+ function calledDefinition(call, db) {
3281
+ const bound = db === void 0 ? void 0 : projects.get(db);
3282
+ return bound?.context.callable(call) ?? null;
3283
+ }
3170
3284
  function resolutionKeyOf(node, db) {
3171
3285
  const bound = db === void 0 ? void 0 : projects.get(db);
3172
3286
  return bound?.keyOf(node) ?? null;
@@ -3389,7 +3503,7 @@ function argShapeOf(node) {
3389
3503
  if (node.type === "list") {
3390
3504
  return {
3391
3505
  kind: "list",
3392
- items: node.namedChildren.filter((child) => child !== null).map(readArg)
3506
+ items: children(node).map(readArg)
3393
3507
  };
3394
3508
  }
3395
3509
  return { kind: "other" };
@@ -3400,10 +3514,7 @@ function readCallArguments(argumentList) {
3400
3514
  if (argumentList === null) {
3401
3515
  return { args, keywordArgs };
3402
3516
  }
3403
- for (const child of argumentList.namedChildren) {
3404
- if (child === null) {
3405
- continue;
3406
- }
3517
+ for (const child of children(argumentList)) {
3407
3518
  if (child.type === "keyword_argument") {
3408
3519
  const nameNode = field(child, "name");
3409
3520
  const valueNode = field(child, "value");
@@ -3447,7 +3558,7 @@ function builtObjectCallee(built, attributeName, objectName) {
3447
3558
  return { importedName: attributeName, module: agreed, objectName };
3448
3559
  }
3449
3560
  };
3450
- return (0, import_behavioral_ir.dispatchByType)(table, built);
3561
+ return (0, import_behavioral_ir2.dispatchByType)(table, built);
3451
3562
  }
3452
3563
  function resolveCallee(expr, module2, facts) {
3453
3564
  if (expr.type === "identifier") {
@@ -3529,10 +3640,10 @@ function unwrapDecorator(decoratorNode, facts) {
3529
3640
  }
3530
3641
  function passesParametersThrough(def, inner) {
3531
3642
  const params = field(def, "parameters");
3532
- const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
3643
+ const names = children(params).filter((child) => child.type === "identifier").map((child) => child.text);
3533
3644
  const argumentList = field(inner, "arguments");
3534
- const positional = (argumentList?.namedChildren ?? []).filter(
3535
- (child) => child !== null && child.type !== "keyword_argument"
3645
+ const positional = children(argumentList).filter(
3646
+ (child) => child.type !== "keyword_argument"
3536
3647
  );
3537
3648
  return positional.length === names.length && positional.every(
3538
3649
  (argument, at) => argument.type === "identifier" && argument.text === names[at]
@@ -3540,13 +3651,14 @@ function passesParametersThrough(def, inner) {
3540
3651
  }
3541
3652
 
3542
3653
  // src/discovery.ts
3543
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
3544
- var import_extractor7 = require("@suss/extractor");
3545
- var import_values19 = require("@suss/values");
3654
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
3655
+ var import_extractor9 = require("@suss/extractor");
3656
+ var import_values20 = require("@suss/values");
3546
3657
 
3547
3658
  // src/clientCalls.ts
3548
- var import_behavioral_ir2 = require("@suss/behavioral-ir");
3549
- var import_values12 = require("@suss/values");
3659
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
3660
+ var import_extractor6 = require("@suss/extractor");
3661
+ var import_values14 = require("@suss/values");
3550
3662
 
3551
3663
  // src/paths/bodyBranches.ts
3552
3664
  var import_extractor4 = require("@suss/extractor");
@@ -3567,7 +3679,7 @@ var COMPARISONS = {
3567
3679
  "<=": "lte"
3568
3680
  };
3569
3681
  function operatorText(node) {
3570
- return node.children.filter((child) => child !== null && !child.isNamed).map((child) => child.text).join(" ");
3682
+ return allChildren(node).filter((child) => !child.isNamed).map((child) => child.text).join(" ");
3571
3683
  }
3572
3684
  function valueRefOf(node, facts, inputs) {
3573
3685
  const value = evaluatedValue(node, facts);
@@ -3619,7 +3731,7 @@ function nullCheckOf(subject, negated2, facts, inputs) {
3619
3731
  };
3620
3732
  }
3621
3733
  function comparisonOf(node, facts, inputs) {
3622
- const [left, right] = node.namedChildren.filter(
3734
+ const [left, right] = children(node).filter(
3623
3735
  (child) => child !== null
3624
3736
  );
3625
3737
  if (left === void 0 || right === void 0) {
@@ -3648,14 +3760,14 @@ function comparisonOf(node, facts, inputs) {
3648
3760
  }
3649
3761
  function predicateOf(node, facts, inputs) {
3650
3762
  if (node.type === "parenthesized_expression") {
3651
- const inner = node.namedChildren[0];
3763
+ const inner = node.namedChild(0);
3652
3764
  return inner == null ? opaqueOf(node) : predicateOf(inner, facts, inputs);
3653
3765
  }
3654
3766
  if (node.type === "comparison_operator") {
3655
3767
  return comparisonOf(node, facts, inputs);
3656
3768
  }
3657
3769
  if (node.type === "not_operator") {
3658
- const operand2 = field(node, "argument") ?? node.namedChildren[0];
3770
+ const operand2 = field(node, "argument") ?? node.namedChild(0);
3659
3771
  return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2, facts, inputs) };
3660
3772
  }
3661
3773
  if (node.type === "identifier" || node.type === "attribute" || inputs?.has(node.id) === true) {
@@ -3670,11 +3782,11 @@ function predicateOf(node, facts, inputs) {
3670
3782
  var SUBJECT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "subscript"]);
3671
3783
  function operandsOf(node) {
3672
3784
  if (node.type === "parenthesized_expression" || node.type === "not_operator") {
3673
- const inner = field(node, "argument") ?? node.namedChildren[0];
3785
+ const inner = field(node, "argument") ?? node.namedChild(0);
3674
3786
  return inner == null ? [] : operandsOf(inner);
3675
3787
  }
3676
3788
  if (node.type === "comparison_operator") {
3677
- return node.namedChildren.filter(
3789
+ return children(node).filter(
3678
3790
  (child) => child !== null && SUBJECT_TYPES.has(child.type)
3679
3791
  );
3680
3792
  }
@@ -3700,7 +3812,7 @@ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lam
3700
3812
  function exitKindOf(node, thrown) {
3701
3813
  let sawReturn = false;
3702
3814
  const raises = (current) => {
3703
- for (const child of current.namedChildren) {
3815
+ for (const child of children(current)) {
3704
3816
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
3705
3817
  continue;
3706
3818
  }
@@ -3728,12 +3840,10 @@ function blockStatements2(block) {
3728
3840
  if (block === null) {
3729
3841
  return [];
3730
3842
  }
3731
- return block.namedChildren.filter((child) => child !== null);
3843
+ return children(block);
3732
3844
  }
3733
3845
  function childBlock(node) {
3734
- return node.namedChildren.find(
3735
- (child) => child !== null && child.type === "block"
3736
- ) ?? null;
3846
+ return children(node).find((child) => child.type === "block") ?? null;
3737
3847
  }
3738
3848
  var Lowerer = class {
3739
3849
  // tree-sitter hands back a fresh wrapper object each time a child is
@@ -3782,7 +3892,7 @@ var Lowerer = class {
3782
3892
  return [nested];
3783
3893
  }
3784
3894
  lowerIf(node) {
3785
- const clauses = node.namedChildren.filter(
3895
+ const clauses = children(node).filter(
3786
3896
  (child) => child !== null && (child.type === "elif_clause" || child.type === "else_clause")
3787
3897
  );
3788
3898
  return {
@@ -3794,10 +3904,10 @@ var Lowerer = class {
3794
3904
  };
3795
3905
  }
3796
3906
  lowerTry(node) {
3797
- const excepts = node.namedChildren.filter(
3907
+ const excepts = children(node).filter(
3798
3908
  (child) => child !== null && (child.type === "except_clause" || child.type === "except_group_clause")
3799
3909
  );
3800
- const finallyClause = node.namedChildren.find(
3910
+ const finallyClause = children(node).find(
3801
3911
  (child) => child !== null && child.type === "finally_clause"
3802
3912
  ) ?? null;
3803
3913
  return {
@@ -3813,7 +3923,7 @@ var Lowerer = class {
3813
3923
  (child) => child.type === "case_clause"
3814
3924
  );
3815
3925
  const groups = cases.map((clause) => {
3816
- const pattern = clause.namedChildren.find(
3926
+ const pattern = children(clause).find(
3817
3927
  (child) => child !== null && child.type === "case_pattern"
3818
3928
  ) ?? null;
3819
3929
  return {
@@ -3894,7 +4004,7 @@ function literalArgOf(node, facts) {
3894
4004
  return null;
3895
4005
  }
3896
4006
  function bodyCalls(node, found = []) {
3897
- for (const child of node.namedChildren) {
4007
+ for (const child of children(node)) {
3898
4008
  if (child === null || NESTED_DEFINITION_TYPES.has(child.type)) {
3899
4009
  continue;
3900
4010
  }
@@ -3906,7 +4016,7 @@ function bodyCalls(node, found = []) {
3906
4016
  return found;
3907
4017
  }
3908
4018
  function moduleLoadCalls(node, found = []) {
3909
- for (const child of node.namedChildren) {
4019
+ for (const child of children(node)) {
3910
4020
  if (child === null || !runsAtModuleLoad(child)) {
3911
4021
  continue;
3912
4022
  }
@@ -3946,7 +4056,7 @@ function argumentNodes(call) {
3946
4056
  if (args === null) {
3947
4057
  return [];
3948
4058
  }
3949
- return args.namedChildren.filter((child) => child !== null).map(
4059
+ return children(args).map(
3950
4060
  (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3951
4061
  );
3952
4062
  }
@@ -3957,6 +4067,9 @@ function calleeText(call) {
3957
4067
  return field(call, "function")?.text ?? call.text;
3958
4068
  }
3959
4069
  function invocationEffects(definitionNode, facts) {
4070
+ return invocationSites(definitionNode, facts).map((site) => site.effect);
4071
+ }
4072
+ function invocationSites(definitionNode, facts) {
3960
4073
  const body = field(definitionNode, "body");
3961
4074
  if (body === null) {
3962
4075
  return [];
@@ -3964,14 +4077,18 @@ function invocationEffects(definitionNode, facts) {
3964
4077
  return invocationEffectsIn(body, bodyCalls(body), facts);
3965
4078
  }
3966
4079
  function moduleLoadInvocationEffects(moduleNode, facts) {
3967
- return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
4080
+ return invocationEffectsIn(
4081
+ moduleNode,
4082
+ moduleLoadCalls(moduleNode),
4083
+ facts
4084
+ ).map((site) => site.effect);
3968
4085
  }
3969
4086
  var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3970
4087
  function askedNodesUnder(node, found = []) {
3971
4088
  if (ASKED_ABOUT_TYPES.has(node.type)) {
3972
4089
  found.push(node);
3973
4090
  }
3974
- for (const child of node.namedChildren) {
4091
+ for (const child of children(node)) {
3975
4092
  if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3976
4093
  askedNodesUnder(child, found);
3977
4094
  }
@@ -4045,11 +4162,14 @@ function invocationEffectsIn(body, written, facts) {
4045
4162
  (condition) => predicateOf(condition, facts, inputs)
4046
4163
  );
4047
4164
  return {
4048
- type: "invocation",
4049
- callee: calleeText(call),
4050
- args: argsOf(call, facts),
4051
- async: false,
4052
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4165
+ call,
4166
+ effect: {
4167
+ type: "invocation",
4168
+ callee: calleeText(call),
4169
+ args: argsOf(call, facts),
4170
+ async: false,
4171
+ ...conditions.length > 0 ? { preconditions: conditions } : {}
4172
+ }
4053
4173
  };
4054
4174
  });
4055
4175
  }
@@ -4084,7 +4204,7 @@ function statedBy(statement, name) {
4084
4204
  }
4085
4205
  if (statement.type === "as_pattern") {
4086
4206
  const alias = field(statement, "alias");
4087
- const value = statement.namedChildren[0] ?? null;
4207
+ const value = statement.namedChild(0) ?? null;
4088
4208
  const callee = value?.type === "call" ? field(value, "function") : null;
4089
4209
  return alias?.text === name ? callee : null;
4090
4210
  }
@@ -4280,7 +4400,7 @@ function importedFunctionMatch(call, options) {
4280
4400
  return null;
4281
4401
  }
4282
4402
  const args = field(call, "arguments");
4283
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
4403
+ const first = children(args)[0] ?? null;
4284
4404
  const statement = first === null ? null : writtenStatement(first, void 0, options.facts);
4285
4405
  if (statement === null) {
4286
4406
  return null;
@@ -4644,7 +4764,7 @@ function columnComparisons(node) {
4644
4764
  const args = field(node, "arguments");
4645
4765
  return (args === null ? [] : children(args)).flatMap(columnComparisons);
4646
4766
  }
4647
- const operator = node.children.find((child) => !child?.isNamed);
4767
+ const operator = allChildren(node).find((child) => !child.isNamed);
4648
4768
  const [left, right, ...rest] = children(node);
4649
4769
  if (node.type !== "comparison_operator" || operator?.type !== "==" || left === void 0 || right === void 0 || rest.length > 0) {
4650
4770
  return [];
@@ -5017,10 +5137,7 @@ function literalAt(key, facts) {
5017
5137
 
5018
5138
  // src/paths/bodyBranches.ts
5019
5139
  function returnStatements(node, found = []) {
5020
- for (const child of node?.namedChildren ?? []) {
5021
- if (child === null) {
5022
- continue;
5023
- }
5140
+ for (const child of children(node)) {
5024
5141
  if (child.type === "function_definition" || child.type === "lambda") {
5025
5142
  continue;
5026
5143
  }
@@ -5077,7 +5194,8 @@ function enumerateBodyBranches(options) {
5077
5194
  const enumerated = (0, import_extractor4.enumerateOrDegrade)(
5078
5195
  {
5079
5196
  statements: lowered.statements,
5080
- terminalsByStmt: lowered.terminalsByStmt
5197
+ terminalsByStmt: lowered.terminalsByStmt,
5198
+ ...options.keepsArms === void 0 ? {} : { keepsArms: options.keepsArms }
5081
5199
  },
5082
5200
  statements
5083
5201
  );
@@ -5114,6 +5232,236 @@ function enumerateBodyBranches(options) {
5114
5232
  return branches;
5115
5233
  }
5116
5234
 
5235
+ // src/paths/raisedResponses.ts
5236
+ var import_values12 = require("@suss/values");
5237
+ function statusNumberOf(node, options) {
5238
+ const value = (0, import_values12.constantOf)(evaluatedValue(node, options.facts));
5239
+ if (typeof value === "number") {
5240
+ return value;
5241
+ }
5242
+ const origin = originOf(node, options.module);
5243
+ return origin === null ? null : options.constants?.[`${origin.module}.${origin.name}`] ?? null;
5244
+ }
5245
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
5246
+ function raisedResponses(body, options) {
5247
+ if (options.calls.length === 0) {
5248
+ return [];
5249
+ }
5250
+ return raisesIn(body, options);
5251
+ }
5252
+ function raisesIn(body, options) {
5253
+ if (body === null) {
5254
+ return [];
5255
+ }
5256
+ const found = [];
5257
+ const walk = (node) => {
5258
+ for (const child of children(node)) {
5259
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
5260
+ continue;
5261
+ }
5262
+ const response = raisedResponseOf(child, options);
5263
+ if (response !== null) {
5264
+ found.push(response);
5265
+ }
5266
+ walk(child);
5267
+ }
5268
+ };
5269
+ walk(body);
5270
+ return found;
5271
+ }
5272
+ var ENDING_EXPRESSION = {
5273
+ raise_statement: (statement) => statement.namedChild(0) ?? null,
5274
+ expression_statement: (statement) => {
5275
+ const only = statement.namedChild(0);
5276
+ return only?.type === "call" ? only : null;
5277
+ }
5278
+ };
5279
+ function raisedResponseOf(statement, options) {
5280
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
5281
+ if (expression === null) {
5282
+ return null;
5283
+ }
5284
+ const thrownByCall = statement.type !== "raise_statement";
5285
+ const prebuilt = thrownByCall ? null : prebuiltCallOf(expression, options);
5286
+ if (prebuilt !== null) {
5287
+ return {
5288
+ statement,
5289
+ thrownByCall,
5290
+ terminal: terminalOf(
5291
+ statement,
5292
+ calleeOf(prebuilt.call, prebuilt.call),
5293
+ prebuilt.call,
5294
+ prebuilt.declared,
5295
+ prebuilt.options
5296
+ )
5297
+ };
5298
+ }
5299
+ const call = expression.type === "call" ? expression : null;
5300
+ const callee = calleeOf(expression, call);
5301
+ const declared = declaredCallFor(callee, options);
5302
+ if (declared === void 0 && thrownByCall) {
5303
+ return null;
5304
+ }
5305
+ return {
5306
+ statement,
5307
+ thrownByCall,
5308
+ terminal: terminalOf(statement, callee, call, declared, options)
5309
+ };
5310
+ }
5311
+ function prebuiltCallOf(expression, options) {
5312
+ if (expression.type !== "identifier" && expression.type !== "attribute") {
5313
+ return null;
5314
+ }
5315
+ const written = writtenNodeOf(expression, options.facts);
5316
+ if (written === null || written.type !== "call") {
5317
+ return null;
5318
+ }
5319
+ const inItsFile = { ...options, module: moduleOf(written) };
5320
+ const declared = declaredCallFor(calleeOf(written, written), inItsFile);
5321
+ return declared === void 0 ? null : { call: written, declared, options: inItsFile };
5322
+ }
5323
+ function returnedResponseStatus(statement, options) {
5324
+ const returned = statement.namedChild(0);
5325
+ if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
5326
+ return null;
5327
+ }
5328
+ const callee = field(returned, "function");
5329
+ if (callee === null) {
5330
+ return null;
5331
+ }
5332
+ const declared = declaredCallFor(callee, options);
5333
+ return declared === void 0 ? null : statusOf(declared, returned, options);
5334
+ }
5335
+ function calleeOf(expression, call) {
5336
+ return call === null ? expression : field(call, "function") ?? expression;
5337
+ }
5338
+ function declaredCallFor(callee, options) {
5339
+ const origin = originOf(callee, options.module);
5340
+ if (origin === null) {
5341
+ return void 0;
5342
+ }
5343
+ const qualified = `${origin.module}.${origin.name}`;
5344
+ return options.calls.find((call) => call.callee === qualified);
5345
+ }
5346
+ function terminalOf(statement, callee, call, declared, options) {
5347
+ return {
5348
+ kind: "throw",
5349
+ statusCode: declared === void 0 ? null : statusOf(declared, call, options),
5350
+ body: null,
5351
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
5352
+ message: call === null ? null : writtenMessageOf(call),
5353
+ ...declared === void 0 ? {} : { producesResponse: true },
5354
+ component: null,
5355
+ renderTree: null,
5356
+ delegateTarget: null,
5357
+ emitEvent: null,
5358
+ location: rangeOf(statement)
5359
+ };
5360
+ }
5361
+ function lastSegmentOf(dotted) {
5362
+ const segment = dotted.split(".").pop();
5363
+ return segment === void 0 || segment === "" ? null : segment;
5364
+ }
5365
+ function statusOf(declared, call, options) {
5366
+ const written = call === null ? null : statusArgumentOf(declared, call);
5367
+ if (written === null) {
5368
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
5369
+ }
5370
+ const value = statusNumberOf(written, options);
5371
+ return value === null ? { type: "dynamic", sourceText: written.text } : { type: "literal", value };
5372
+ }
5373
+ function argumentsOf2(call) {
5374
+ return children(field(call, "arguments"));
5375
+ }
5376
+ function statusArgumentOf(declared, call) {
5377
+ const args = argumentsOf2(call);
5378
+ const keyed = args.find(
5379
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
5380
+ );
5381
+ if (keyed !== void 0) {
5382
+ return field(keyed, "value");
5383
+ }
5384
+ if (declared.statusArgument === void 0) {
5385
+ return null;
5386
+ }
5387
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
5388
+ return positional[declared.statusArgument] ?? null;
5389
+ }
5390
+ function writtenMessageOf(call) {
5391
+ for (const arg of argumentsOf2(call)) {
5392
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
5393
+ const literal = value === null ? null : stringLiteralValue(value);
5394
+ if (literal !== null) {
5395
+ return literal;
5396
+ }
5397
+ }
5398
+ return null;
5399
+ }
5400
+
5401
+ // src/responseHandOff.ts
5402
+ var import_extractor5 = require("@suss/extractor");
5403
+ function throughResponseHelpers(branches, sites, options) {
5404
+ const handOffs = [];
5405
+ for (const { call, effect } of sites) {
5406
+ const helper = helperBranchesAt(call, options);
5407
+ if (helper !== null) {
5408
+ handOffs.push({
5409
+ helper,
5410
+ preconditions: effect.preconditions,
5411
+ line: rangeOf(call).start,
5412
+ at: rangeOf(call)
5413
+ });
5414
+ }
5415
+ }
5416
+ return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => (0, import_extractor5.splitAtHandOffs)(branch, handOffs));
5417
+ }
5418
+ function helperBranchesAt(call, options) {
5419
+ const handed = callArguments(call).filter(
5420
+ (argument) => argument.node.type === "identifier" && refersToResponse(argument.node, options)
5421
+ );
5422
+ if (handed.length === 0) {
5423
+ return null;
5424
+ }
5425
+ const helper = calledDefinition(call, options.facts);
5426
+ if (helper === null || helper.type !== "function_definition") {
5427
+ return null;
5428
+ }
5429
+ const passed = /* @__PURE__ */ new Map();
5430
+ const parameters = parameterShapes(helper, isMethod(helper) ? 1 : 0);
5431
+ for (const argument of handed) {
5432
+ const parameter = parameters.find(
5433
+ (one) => argument.kind === "positional" ? one.position === argument.position : one.name === argument.name
5434
+ );
5435
+ if (parameter !== void 0) {
5436
+ passed.set(parameter.name, argument.node.text);
5437
+ }
5438
+ }
5439
+ return passed.size === 0 ? null : (0, import_extractor5.helperPathsOnVariables)(options.readHelper(helper), passed);
5440
+ }
5441
+ function refersToResponse(name, options) {
5442
+ const written = awaited(writtenNodeOf(name, options.facts));
5443
+ if (written === null) {
5444
+ return false;
5445
+ }
5446
+ if (sameNode(written, options.request)) {
5447
+ return true;
5448
+ }
5449
+ const callee = written.type === "call" ? field(written, "function") : null;
5450
+ const method = callee === null ? null : field(callee, "attribute");
5451
+ const receiver = callee === null ? null : field(callee, "object");
5452
+ if (method === null || receiver?.type !== "identifier" || !options.bodyMethods.includes(method.text)) {
5453
+ return false;
5454
+ }
5455
+ const parsedFrom = awaited(writtenNodeOf(receiver, options.facts));
5456
+ return parsedFrom !== null && sameNode(parsedFrom, options.request);
5457
+ }
5458
+ function awaited(node) {
5459
+ return node?.type === "await" ? node.namedChild(0) ?? null : node;
5460
+ }
5461
+ function sameNode(a, b) {
5462
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.tree === b.tree;
5463
+ }
5464
+
5117
5465
  // src/clientCalls.ts
5118
5466
  function clientCallUnits(root, module2, pack, pattern, options) {
5119
5467
  const units = [];
@@ -5164,7 +5512,7 @@ function requestCalls(call, pattern, module2, options) {
5164
5512
  return [];
5165
5513
  }
5166
5514
  const plain = requestCall(call, attribute, pattern, options);
5167
- if (plain !== null && !(0, import_behavioral_ir2.hasNameHole)(plain.path)) {
5515
+ if (plain !== null && !(0, import_behavioral_ir3.hasNameHole)(plain.path)) {
5168
5516
  return [plain];
5169
5517
  }
5170
5518
  const byBoundary = /* @__PURE__ */ new Map();
@@ -5173,7 +5521,7 @@ function requestCalls(call, pattern, module2, options) {
5173
5521
  if (stated === null) {
5174
5522
  continue;
5175
5523
  }
5176
- const key = `${stated.method} ${stated.path}`;
5524
+ const key = `${stated.method} ${stated.host ?? ""} ${stated.path}`;
5177
5525
  if (!byBoundary.has(key)) {
5178
5526
  byBoundary.set(key, stated);
5179
5527
  }
@@ -5184,16 +5532,33 @@ function requestCalls(call, pattern, module2, options) {
5184
5532
  return plain === null ? [] : [plain];
5185
5533
  }
5186
5534
  function requestCall(call, attribute, pattern, options, site) {
5535
+ const stated = methodAndPath(call, attribute, pattern, options, site);
5536
+ if (stated === null) {
5537
+ return null;
5538
+ }
5539
+ const redirectDelivery = redirectDeliveryAt(call, pattern, options, site);
5540
+ return redirectDelivery === void 0 ? stated : { ...stated, redirectDelivery };
5541
+ }
5542
+ function redirectDeliveryAt(call, pattern, options, site) {
5543
+ const option = pattern.response?.redirectOption;
5544
+ const argument = option === void 0 ? null : argumentAt(call, null, option.name);
5545
+ if (option === void 0 || argument === null) {
5546
+ return void 0;
5547
+ }
5548
+ const value = (0, import_values14.scalarOf)(evaluatedValue(argument, options.facts, site));
5549
+ return value === null ? void 0 : (0, import_extractor6.redirectDeliveryWhenSet)(option, value);
5550
+ }
5551
+ function methodAndPath(call, attribute, pattern, options, site) {
5187
5552
  const verb = pattern.verbAttributeNames[attribute];
5188
5553
  if (verb !== void 0) {
5189
- const path9 = urlAt(
5554
+ const target2 = urlAt(
5190
5555
  call,
5191
5556
  pattern.url.position,
5192
5557
  pattern.url.keyword,
5193
5558
  options,
5194
5559
  site
5195
5560
  );
5196
- return path9 === null ? null : { method: verb, path: path9, range: rangeOf(call) };
5561
+ return target2 === null ? null : { method: verb, ...target2, range: rangeOf(call), node: call };
5197
5562
  }
5198
5563
  const methodCall = pattern.methodCall;
5199
5564
  if (methodCall === void 0 || methodCall.attribute !== attribute) {
@@ -5208,14 +5573,19 @@ function requestCall(call, attribute, pattern, options, site) {
5208
5573
  if (method === null) {
5209
5574
  return null;
5210
5575
  }
5211
- const path8 = urlAt(
5576
+ const target = urlAt(
5212
5577
  call,
5213
5578
  methodCall.urlPosition,
5214
5579
  pattern.url.keyword,
5215
5580
  options,
5216
5581
  site
5217
5582
  );
5218
- return path8 === null ? null : { method: method.toUpperCase(), path: path8, range: rangeOf(call) };
5583
+ return target === null ? null : {
5584
+ method: method.toUpperCase(),
5585
+ ...target,
5586
+ range: rangeOf(call),
5587
+ node: call
5588
+ };
5219
5589
  }
5220
5590
  function calledAttribute(callee, pattern, module2, facts) {
5221
5591
  const origin = originOf(callee, module2);
@@ -5248,7 +5618,9 @@ function urlAt(call, position, keyword, options, site) {
5248
5618
  if (argument === null) {
5249
5619
  return null;
5250
5620
  }
5251
- return (0, import_values12.pathOf)(evaluatedValue(argument, options.facts, site)) ?? null;
5621
+ const value = evaluatedValue(argument, options.facts, site);
5622
+ const path8 = (0, import_values14.pathOf)(value);
5623
+ return path8 === void 0 ? null : { path: path8, host: (0, import_values14.hostOf)(value) };
5252
5624
  }
5253
5625
  function argumentAt(call, position, keyword) {
5254
5626
  const args = field(call, "arguments");
@@ -5266,25 +5638,33 @@ function argumentAt(call, position, keyword) {
5266
5638
  }
5267
5639
  positional.push(child);
5268
5640
  }
5269
- return positional[position] ?? null;
5641
+ return position === null ? null : positional[position] ?? null;
5270
5642
  }
5271
- function callerBranches(definition, range, facts) {
5643
+ function callerBranches(definition, reading) {
5644
+ const range = rangeOf(definition);
5272
5645
  const body = field(definition, "body");
5273
- const effects = invocationEffects(definition, facts);
5274
- const terminals = bodyTerminals(body, []);
5275
- if (body === null || terminals.length === 0) {
5646
+ const sites = invocationSites(definition, reading.facts);
5647
+ const effects = sites.map((site) => site.effect);
5648
+ if (body === null) {
5276
5649
  return [handsBack(range, effects)];
5277
5650
  }
5651
+ const raised = raisesIn(body, {
5652
+ calls: [],
5653
+ module: moduleOf(definition),
5654
+ facts: reading.facts
5655
+ });
5278
5656
  const branches = enumerateBodyBranches({
5279
5657
  body,
5280
- terminals,
5281
- raised: [],
5658
+ terminals: bodyTerminals(body, raised),
5659
+ raised,
5282
5660
  effects,
5283
- branchOf: (found) => found.type === "raise" ? { terminal: found.terminal, location: rangeOf(found.statement) } : handsBackAt(rangeOf(found.statement)),
5661
+ branchOf: (found2) => found2.type === "raise" ? { terminal: found2.terminal, location: rangeOf(found2.statement) } : handsBackAt(rangeOf(found2.statement)),
5284
5662
  fallthrough: handsBackAt(range),
5285
- facts
5663
+ facts: reading.facts,
5664
+ keepsArms: (condition) => (0, import_behavioral_ir3.testsStatus)(predicateOf(condition, reading.facts), reading.statusMembers)
5286
5665
  });
5287
- return branches.length === 0 ? [handsBack(range, effects)] : branches;
5666
+ const found = branches.length === 0 ? [handsBack(range, effects)] : branches;
5667
+ return reading.handOff === void 0 ? found : throughResponseHelpers(found, sites, reading.handOff);
5288
5668
  }
5289
5669
  function handsBackAt(range) {
5290
5670
  return {
@@ -5320,11 +5700,24 @@ function responseAccessors(pattern) {
5320
5700
  ...response.body === void 0 ? {} : { bodyAccessors: response.body },
5321
5701
  ...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
5322
5702
  ...response.success === void 0 ? {} : { successAccessors: response.success },
5323
- ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
5703
+ ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery },
5704
+ ...response.redirectDelivery === void 0 ? {} : { redirectDelivery: response.redirectDelivery }
5324
5705
  };
5325
5706
  }
5326
5707
  function clientUnit(definition, name, request, pattern, pack, options) {
5327
5708
  const range = rangeOf(definition);
5709
+ const accessors = responseAccessors(pattern);
5710
+ const statusMembers = (0, import_behavioral_ir3.statusMembersOf)(accessors);
5711
+ const reading = {
5712
+ facts: options.facts,
5713
+ statusMembers,
5714
+ handOff: {
5715
+ request: request.node,
5716
+ bodyMethods: accessors.bodyAccessors ?? [],
5717
+ facts: options.facts,
5718
+ readHelper: (helper) => callerBranches(helper, { facts: options.facts, statusMembers })
5719
+ }
5720
+ };
5328
5721
  return {
5329
5722
  identity: {
5330
5723
  name,
@@ -5336,15 +5729,17 @@ function clientUnit(definition, name, request, pattern, pack, options) {
5336
5729
  exportName: name,
5337
5730
  exportPath: [name]
5338
5731
  },
5339
- boundaryBinding: (0, import_behavioral_ir2.restBinding)({
5732
+ boundaryBinding: (0, import_behavioral_ir3.restBinding)({
5340
5733
  transport: pack.protocol,
5341
5734
  method: request.method,
5342
5735
  path: request.path,
5736
+ host: request.host,
5343
5737
  recognition: pack.name
5344
5738
  }),
5345
5739
  parameters: [],
5346
- branches: callerBranches(definition, range, options.facts),
5347
- ...responseAccessors(pattern),
5740
+ branches: callerBranches(definition, reading),
5741
+ ...accessors,
5742
+ ...request.redirectDelivery === void 0 ? {} : { redirectDelivery: request.redirectDelivery },
5348
5743
  bodyContent: "statements",
5349
5744
  dependencyCalls: [],
5350
5745
  declaredContract: null
@@ -5388,8 +5783,8 @@ function routeOnDroppedApp(facts, object, constructionKey, pattern) {
5388
5783
  }
5389
5784
 
5390
5785
  // src/envReads.ts
5391
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
5392
- var import_extractor5 = require("@suss/extractor");
5786
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
5787
+ var import_extractor7 = require("@suss/extractor");
5393
5788
  var import_resolution7 = require("@suss/resolution");
5394
5789
 
5395
5790
  // src/envSpellings.ts
@@ -5567,7 +5962,7 @@ function isTestedAt(parent, child, subject, seen) {
5567
5962
  }
5568
5963
  function conditionOf(node) {
5569
5964
  if (node.type === "conditional_expression") {
5570
- return node.namedChildren[1] ?? null;
5965
+ return node.namedChild(1) ?? null;
5571
5966
  }
5572
5967
  return CONDITIONED_TYPES.has(node.type) ? field(node, "condition") : null;
5573
5968
  }
@@ -5590,7 +5985,7 @@ function isPresentAt(node, subject) {
5590
5985
  }
5591
5986
  function isBranchWherePresent(parent, child, subject) {
5592
5987
  if (parent.type === "conditional_expression") {
5593
- const [whenTrue, test2, whenFalse] = parent.namedChildren;
5988
+ const [whenTrue, test2, whenFalse] = children(parent);
5594
5989
  return test2 !== void 0 && test2 !== null && (whenTrue?.id === child.id && isPresentWhen(test2, true, subject) || whenFalse?.id === child.id && isPresentWhen(test2, false, subject));
5595
5990
  }
5596
5991
  if (parent.type === "boolean_operator") {
@@ -5628,7 +6023,7 @@ function followsExitWhenAbsent(parent, child, subject) {
5628
6023
  if (parent.type !== "block" && parent.type !== "module") {
5629
6024
  return false;
5630
6025
  }
5631
- const statements = parent.namedChildren;
6026
+ const statements = children(parent);
5632
6027
  const at = statements.findIndex((statement) => statement?.id === child.id);
5633
6028
  return statements.slice(0, at).some(
5634
6029
  (statement) => statement !== null && leavesWhenAbsent(statement, subject)
@@ -5672,7 +6067,7 @@ function continuationRaises(from) {
5672
6067
  let parent = current.parent;
5673
6068
  while (parent !== null) {
5674
6069
  if (parent.type === "block" || parent.type === "module") {
5675
- const statements = parent.namedChildren;
6070
+ const statements = children(parent);
5676
6071
  const at = statements.findIndex((one) => one?.id === current.id);
5677
6072
  const exit = firstExit(statements.slice(at + 1));
5678
6073
  if (exit !== null) {
@@ -5716,7 +6111,7 @@ function exitOf(statement) {
5716
6111
  if (LEAVING_TYPES.has(statement.type)) {
5717
6112
  return "leave";
5718
6113
  }
5719
- return statement.type === "block" ? firstExit(statement.namedChildren) : null;
6114
+ return statement.type === "block" ? firstExit(children(statement)) : null;
5720
6115
  }
5721
6116
  function isPresentWhen(test, outcome, subject) {
5722
6117
  const expression = peelParens(test);
@@ -5756,7 +6151,7 @@ function isPresentAfterComparison(comparison, outcome, subject) {
5756
6151
  }
5757
6152
  function comparisonParts(comparison) {
5758
6153
  const operators = fields(comparison, "operators");
5759
- const operands = comparison.namedChildren.filter(
6154
+ const operands = children(comparison).filter(
5760
6155
  (child) => child !== null && !operators.some((one) => one.id === child.id)
5761
6156
  );
5762
6157
  const [left, right] = operands;
@@ -5779,7 +6174,7 @@ function comparedWithNone(comparison) {
5779
6174
  }
5780
6175
  var NONE_OPERATORS = /* @__PURE__ */ new Set(["is", "is not", "==", "!="]);
5781
6176
  function peelParens(node) {
5782
- const inner = node.type === "parenthesized_expression" ? node.namedChildren[0] : null;
6177
+ const inner = node.type === "parenthesized_expression" ? node.namedChild(0) : null;
5783
6178
  return inner === null || inner === void 0 ? node : peelParens(inner);
5784
6179
  }
5785
6180
  function climbParens(node) {
@@ -5837,7 +6232,7 @@ function envReadEffects(root, module2, facts) {
5837
6232
  const slots = [];
5838
6233
  const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
5839
6234
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
5840
- (0, import_extractor5.walkDescendants)(root, startScope, {
6235
+ (0, import_extractor7.walkDescendants)(root, startScope, children, {
5841
6236
  at: (node, scope) => {
5842
6237
  const syntax = envReadSyntaxAt(node, scope);
5843
6238
  if (syntax !== null) {
@@ -5853,7 +6248,7 @@ function envReadEffects(root, module2, facts) {
5853
6248
  },
5854
6249
  into: (node, scope) => {
5855
6250
  if (DEFERRED_BODY_TYPES.has(node.type)) {
5856
- return import_extractor5.SKIP_CHILDREN;
6251
+ return import_extractor7.SKIP_CHILDREN;
5857
6252
  }
5858
6253
  return module2.scopeFor.get(node.id) ?? scope;
5859
6254
  }
@@ -5872,7 +6267,7 @@ function envReadEffects(root, module2, facts) {
5872
6267
  function configReadEffect(read) {
5873
6268
  return {
5874
6269
  type: "interaction",
5875
- binding: (0, import_behavioral_ir3.runtimeConfigBinding)({ recognition: PYTHON_ENV_RECOGNITION }),
6270
+ binding: (0, import_behavioral_ir4.runtimeConfigBinding)({ recognition: PYTHON_ENV_RECOGNITION }),
5876
6271
  callee: `os.environ["${read.name}"]`,
5877
6272
  interaction: {
5878
6273
  class: "config-read",
@@ -5896,7 +6291,7 @@ function hasFallback(node, spelling, scope) {
5896
6291
  }
5897
6292
  function envFactsIn(filePath, root, module2) {
5898
6293
  const found = { sites: [], objects: [] };
5899
- (0, import_extractor5.walkDescendants)(root, module2.moduleScope, {
6294
+ (0, import_extractor7.walkDescendants)(root, module2.moduleScope, children, {
5900
6295
  at: (node, scope) => {
5901
6296
  if (isEnviron(node, scope) && handsOnward(node)) {
5902
6297
  found.objects.push(keyOf(filePath, node));
@@ -6077,172 +6472,41 @@ function argumentsByParameter(db, call, calleeKey2) {
6077
6472
  }
6078
6473
  for (const row of db.lookup("paramNamed", 0, callee)) {
6079
6474
  const at = byName.get(String(row[1]));
6080
- if (at !== void 0) {
6081
- argumentAt2.set(String(row[2]), at);
6082
- }
6083
- }
6084
- }
6085
- return argumentAt2;
6086
- }
6087
- function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6088
- const reads = /* @__PURE__ */ new Map();
6089
- for (const [parameter, argument] of argumentAt2) {
6090
- const sites = named.get(parameter);
6091
- (0, import_resolution7.noteLookup)(
6092
- db,
6093
- `${NAMED_PARAMETER}${parameter}`,
6094
- describeSites(sites, siteById)
6095
- );
6096
- if (sites === void 0) {
6097
- continue;
6098
- }
6099
- const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
6100
- if (name === null) {
6101
- continue;
6102
- }
6103
- for (const id of sites) {
6104
- const defaulted = siteById.get(id)?.defaulted ?? false;
6105
- reads.set(name, (reads.get(name) ?? true) && defaulted);
6106
- }
6107
- }
6108
- if (reads.size === 0) {
6109
- return [];
6110
- }
6111
- const defaultedAtCall = isDefaultedAt(call);
6112
- return [...reads].map(
6113
- ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
6114
- );
6115
- }
6116
-
6117
- // src/paths/raisedResponses.ts
6118
- var import_values17 = require("@suss/values");
6119
- var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
6120
- function raisedResponses(body, options) {
6121
- if (body === null || options.calls.length === 0) {
6122
- return [];
6123
- }
6124
- const found = [];
6125
- const walk = (node) => {
6126
- for (const child of node.namedChildren) {
6127
- if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
6128
- continue;
6129
- }
6130
- const response = raisedResponseOf(child, options);
6131
- if (response !== null) {
6132
- found.push(response);
6133
- }
6134
- walk(child);
6135
- }
6136
- };
6137
- walk(body);
6138
- return found;
6139
- }
6140
- var ENDING_EXPRESSION = {
6141
- raise_statement: (statement) => statement.namedChildren[0] ?? null,
6142
- expression_statement: (statement) => {
6143
- const only = statement.namedChildren[0];
6144
- return only?.type === "call" ? only : null;
6145
- }
6146
- };
6147
- function raisedResponseOf(statement, options) {
6148
- const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
6149
- if (expression === null) {
6150
- return null;
6151
- }
6152
- const thrownByCall = statement.type !== "raise_statement";
6153
- const call = expression.type === "call" ? expression : null;
6154
- const callee = calleeOf(expression, call);
6155
- const declared = declaredCallFor(callee, options);
6156
- if (declared === void 0 && thrownByCall) {
6157
- return null;
6158
- }
6159
- return {
6160
- statement,
6161
- thrownByCall,
6162
- terminal: terminalOf(statement, callee, call, declared, options.facts)
6163
- };
6164
- }
6165
- function returnedResponseStatus(statement, options) {
6166
- const returned = statement.namedChildren[0];
6167
- if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
6168
- return null;
6169
- }
6170
- const callee = field(returned, "function");
6171
- if (callee === null) {
6172
- return null;
6173
- }
6174
- const declared = declaredCallFor(callee, options);
6175
- return declared === void 0 ? null : statusOf(declared, returned, options.facts);
6176
- }
6177
- function calleeOf(expression, call) {
6178
- return call === null ? expression : field(call, "function") ?? expression;
6179
- }
6180
- function declaredCallFor(callee, options) {
6181
- const origin = originOf(callee, options.module);
6182
- if (origin === null) {
6183
- return void 0;
6184
- }
6185
- const qualified = `${origin.module}.${origin.name}`;
6186
- return options.calls.find((call) => call.callee === qualified);
6187
- }
6188
- function terminalOf(statement, callee, call, declared, facts) {
6189
- return {
6190
- kind: "throw",
6191
- statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
6192
- body: null,
6193
- exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
6194
- message: call === null ? null : writtenMessageOf(call),
6195
- ...declared === void 0 ? {} : { producesResponse: true },
6196
- component: null,
6197
- renderTree: null,
6198
- delegateTarget: null,
6199
- emitEvent: null,
6200
- location: rangeOf(statement)
6201
- };
6202
- }
6203
- function lastSegmentOf(dotted) {
6204
- const segment = dotted.split(".").pop();
6205
- return segment === void 0 || segment === "" ? null : segment;
6206
- }
6207
- function statusOf(declared, call, facts) {
6208
- const written = call === null ? null : statusArgumentOf(declared, call);
6209
- if (written === null) {
6210
- return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
6211
- }
6212
- const value = (0, import_values17.constantOf)(evaluatedValue(written, facts));
6213
- if (typeof value === "number") {
6214
- return { type: "literal", value };
6215
- }
6216
- return { type: "dynamic", sourceText: written.text };
6217
- }
6218
- function argumentsOf2(call) {
6219
- return (field(call, "arguments")?.namedChildren ?? []).filter(
6220
- (child) => child !== null
6221
- );
6222
- }
6223
- function statusArgumentOf(declared, call) {
6224
- const args = argumentsOf2(call);
6225
- const keyed = args.find(
6226
- (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
6227
- );
6228
- if (keyed !== void 0) {
6229
- return field(keyed, "value");
6230
- }
6231
- if (declared.statusArgument === void 0) {
6232
- return null;
6475
+ if (at !== void 0) {
6476
+ argumentAt2.set(String(row[2]), at);
6477
+ }
6478
+ }
6233
6479
  }
6234
- const positional = args.filter((arg) => arg.type !== "keyword_argument");
6235
- return positional[declared.statusArgument] ?? null;
6480
+ return argumentAt2;
6236
6481
  }
6237
- function writtenMessageOf(call) {
6238
- for (const arg of argumentsOf2(call)) {
6239
- const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
6240
- const literal = value === null ? null : stringLiteralValue(value);
6241
- if (literal !== null) {
6242
- return literal;
6482
+ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6483
+ const reads = /* @__PURE__ */ new Map();
6484
+ for (const [parameter, argument] of argumentAt2) {
6485
+ const sites = named.get(parameter);
6486
+ (0, import_resolution7.noteLookup)(
6487
+ db,
6488
+ `${NAMED_PARAMETER}${parameter}`,
6489
+ describeSites(sites, siteById)
6490
+ );
6491
+ if (sites === void 0) {
6492
+ continue;
6493
+ }
6494
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
6495
+ if (name === null) {
6496
+ continue;
6497
+ }
6498
+ for (const id of sites) {
6499
+ const defaulted = siteById.get(id)?.defaulted ?? false;
6500
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
6243
6501
  }
6244
6502
  }
6245
- return null;
6503
+ if (reads.size === 0) {
6504
+ return [];
6505
+ }
6506
+ const defaultedAtCall = isDefaultedAt(call);
6507
+ return [...reads].map(
6508
+ ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
6509
+ );
6246
6510
  }
6247
6511
 
6248
6512
  // src/paths/returnedShape.ts
@@ -6304,7 +6568,7 @@ function shapeOfReturned(node, depth = 0) {
6304
6568
  return { type: "unknown" };
6305
6569
  }
6306
6570
  function returnedBodyShape(statement) {
6307
- const returned = statement.namedChildren[0];
6571
+ const returned = statement.namedChild(0);
6308
6572
  if (returned == null) {
6309
6573
  return null;
6310
6574
  }
@@ -6317,8 +6581,8 @@ function returnedBodyShape(statement) {
6317
6581
  }
6318
6582
 
6319
6583
  // src/streamWrites.ts
6320
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
6321
- var import_extractor6 = require("@suss/extractor");
6584
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
6585
+ var import_extractor8 = require("@suss/extractor");
6322
6586
 
6323
6587
  // src/stdlibNames.ts
6324
6588
  function isStdlibModule(node, scope, module2) {
@@ -6350,7 +6614,7 @@ var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lamb
6350
6614
  function streamWriteEffects(root, module2) {
6351
6615
  const found = [];
6352
6616
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
6353
- (0, import_extractor6.walkDescendants)(root, startScope, {
6617
+ (0, import_extractor8.walkDescendants)(root, startScope, children, {
6354
6618
  at: (node, scope) => {
6355
6619
  if (node.type !== "call") {
6356
6620
  return;
@@ -6362,7 +6626,7 @@ function streamWriteEffects(root, module2) {
6362
6626
  },
6363
6627
  into: (node, scope) => {
6364
6628
  if (DEFERRED_BODY_TYPES2.has(node.type)) {
6365
- return import_extractor6.SKIP_CHILDREN;
6629
+ return import_extractor8.SKIP_CHILDREN;
6366
6630
  }
6367
6631
  return module2.scopeFor.get(node.id) ?? scope;
6368
6632
  }
@@ -6398,7 +6662,7 @@ function writeEffect(site) {
6398
6662
  const { call, callee, target } = site;
6399
6663
  return {
6400
6664
  type: "interaction",
6401
- binding: (0, import_behavioral_ir4.ioBinding)({ recognition: PYTHON_STREAM_RECOGNITION, target }),
6665
+ binding: (0, import_behavioral_ir5.ioBinding)({ recognition: PYTHON_STREAM_RECOGNITION, target }),
6402
6666
  callee,
6403
6667
  groupId: `${call.startPosition.row + 1}:${call.startPosition.column + 1}`,
6404
6668
  interaction: {
@@ -6437,7 +6701,7 @@ function jsonDumped(node, scope) {
6437
6701
  // src/discovery.ts
6438
6702
  function decoratedNodes(root) {
6439
6703
  const found = [];
6440
- (0, import_extractor7.walkDescendants)(root, null, {
6704
+ (0, import_extractor9.walkDescendants)(root, null, children, {
6441
6705
  at: (node) => {
6442
6706
  if (node.type !== "decorated_definition") {
6443
6707
  return;
@@ -6588,18 +6852,18 @@ function unitsFor(pattern, pack, decoratorModule, classification, definition, mo
6588
6852
  );
6589
6853
  }
6590
6854
  };
6591
- return (0, import_behavioral_ir5.dispatchByType)(table, pattern);
6855
+ return (0, import_behavioral_ir6.dispatchByType)(table, pattern);
6592
6856
  }
6593
6857
  function readPathArgument(classification, options) {
6594
6858
  const first = classification.args[0];
6595
6859
  if (first !== void 0) {
6596
6860
  const value = evaluatedValue(first.node, options.facts);
6597
- const path8 = (0, import_values19.literalOf)(value) ?? (0, import_values19.pathOf)(value);
6861
+ const path8 = (0, import_values20.literalOf)(value) ?? (0, import_values20.pathOf)(value);
6598
6862
  if (path8 !== void 0) {
6599
- return (0, import_extractor7.writtenReading)(path8, classification.range);
6863
+ return (0, import_extractor9.writtenReading)(path8, classification.range);
6600
6864
  }
6601
6865
  }
6602
- return (0, import_extractor7.unreadableReading)(
6866
+ return (0, import_extractor9.unreadableReading)(
6603
6867
  "The path in this route's decorator is not a string literal, so the binding names no path and nothing pairs with it",
6604
6868
  classification.range
6605
6869
  );
@@ -6624,16 +6888,16 @@ var PATH_TEMPLATE_READERS = {
6624
6888
  function readPathTemplate(pattern, path8, range) {
6625
6889
  const syntax = pattern.pathParamSyntax;
6626
6890
  if (syntax === void 0) {
6627
- return (0, import_extractor7.writtenReading)({ path: path8, paramNames: NO_PATH_PARAMS }, range);
6891
+ return (0, import_extractor9.writtenReading)({ path: path8, paramNames: NO_PATH_PARAMS }, range);
6628
6892
  }
6629
6893
  const reader = PATH_TEMPLATE_READERS[syntax];
6630
6894
  if (reader === void 0) {
6631
- return (0, import_extractor7.unreadableReading)(
6895
+ return (0, import_extractor9.unreadableReading)(
6632
6896
  `The route's pack declares path-parameter syntax "${syntax}", which this adapter has no reader for, so the binding names no path and nothing pairs with it`,
6633
6897
  range
6634
6898
  );
6635
6899
  }
6636
- return (0, import_extractor7.writtenReading)(reader(path8), range);
6900
+ return (0, import_extractor9.writtenReading)(reader(path8), range);
6637
6901
  }
6638
6902
  function classRouteUnits(pattern, pack, classification, classNode, module2, options) {
6639
6903
  const pathArgument = readPathArgument(classification, options);
@@ -6684,7 +6948,7 @@ function classRouteUnits(pattern, pack, classification, classNode, module2, opti
6684
6948
  module: module2,
6685
6949
  filePath: options.filePath,
6686
6950
  skipReceiverParam: true,
6687
- responseShape: import_extractor7.absentReading,
6951
+ responseShape: import_extractor9.absentReading,
6688
6952
  statusCode: defaultedStatus(
6689
6953
  readReturnedStatus(pattern, maybeMethod, {
6690
6954
  module: module2,
@@ -6711,11 +6975,11 @@ function classRouteUnits(pattern, pack, classification, classNode, module2, opti
6711
6975
  return units;
6712
6976
  }
6713
6977
  function readRoutePath(pattern, pathArgument, classification, module2, options) {
6714
- const composed = (0, import_extractor7.andThenReading)(
6978
+ const composed = (0, import_extractor9.andThenReading)(
6715
6979
  pathArgument,
6716
6980
  (literal, range) => composeRoutePath(pattern, classification, module2, options, literal, range)
6717
6981
  );
6718
- return (0, import_extractor7.andThenReading)(
6982
+ return (0, import_extractor9.andThenReading)(
6719
6983
  composed,
6720
6984
  (path8, range) => readPathTemplate(pattern, path8, range)
6721
6985
  );
@@ -6741,10 +7005,10 @@ function readRoutePaths(pattern, pathArgument, classification, module2, options)
6741
7005
  ];
6742
7006
  }
6743
7007
  return resolution.values.map((prefix) => ({
6744
- routePath: (0, import_extractor7.andThenReading)(
6745
- (0, import_extractor7.andThenReading)(
7008
+ routePath: (0, import_extractor9.andThenReading)(
7009
+ (0, import_extractor9.andThenReading)(
6746
7010
  pathArgument,
6747
- (literal, range) => (0, import_extractor7.writtenReading)(servedSpelling(pattern, prefix + literal), range)
7011
+ (literal, range) => (0, import_extractor9.writtenReading)(servedSpelling(pattern, prefix + literal), range)
6748
7012
  ),
6749
7013
  (path8, range) => readPathTemplate(pattern, path8, range)
6750
7014
  ),
@@ -6785,9 +7049,9 @@ function composeRoutePath(pattern, classification, module2, options, literal, ra
6785
7049
  range
6786
7050
  );
6787
7051
  if (prefix.kind === "absent") {
6788
- return (0, import_extractor7.writtenReading)(servedSpelling(pattern, literal), range);
7052
+ return (0, import_extractor9.writtenReading)(servedSpelling(pattern, literal), range);
6789
7053
  }
6790
- return (0, import_extractor7.mapReading)(
7054
+ return (0, import_extractor9.mapReading)(
6791
7055
  prefix,
6792
7056
  (value) => servedSpelling(pattern, value + literal)
6793
7057
  );
@@ -6807,32 +7071,36 @@ function readRouterPrefix(pattern, classification, module2, options, range) {
6807
7071
  options
6808
7072
  );
6809
7073
  if (resolution === null) {
6810
- return import_extractor7.absentReading;
7074
+ return import_extractor9.absentReading;
6811
7075
  }
6812
7076
  if (resolution.kind === "abstain") {
6813
- return (0, import_extractor7.unreadableReading)(
7077
+ return (0, import_extractor9.unreadableReading)(
6814
7078
  `The router this route is declared on ${resolution.reason}, so the binding names no path and nothing pairs with it`,
6815
7079
  range
6816
7080
  );
6817
7081
  }
6818
7082
  if (resolution.kind === "composed") {
6819
- return (0, import_extractor7.writtenReading)(resolution.value, range);
7083
+ return (0, import_extractor9.writtenReading)(resolution.value, range);
6820
7084
  }
6821
- return import_extractor7.absentReading;
7085
+ return import_extractor9.absentReading;
6822
7086
  }
6823
- function readStatusCode(pattern, classification) {
7087
+ function readStatusCode(pattern, classification, options) {
6824
7088
  if (pattern.statusCodeKeyword === void 0) {
6825
- return import_extractor7.absentReading;
7089
+ return import_extractor9.absentReading;
6826
7090
  }
6827
7091
  const arg = classification.keywordArgs[pattern.statusCodeKeyword];
6828
7092
  if (arg === void 0) {
6829
- return import_extractor7.absentReading;
7093
+ return import_extractor9.absentReading;
6830
7094
  }
6831
- if (arg.kind === "number") {
6832
- return (0, import_extractor7.writtenReading)(arg.value, classification.range);
7095
+ const status = statusNumberOf(arg.node, {
7096
+ ...options,
7097
+ constants: pattern.statusCodeConstants ?? {}
7098
+ });
7099
+ if (status !== null) {
7100
+ return (0, import_extractor9.writtenReading)(status, classification.range);
6833
7101
  }
6834
- return (0, import_extractor7.unreadableReading)(
6835
- "The status this route's decorator states is not a literal number, so the response claims no status",
7102
+ return (0, import_extractor9.unreadableReading)(
7103
+ "The status this route's decorator states does not come to a number here, so the response claims no status",
6836
7104
  classification.range
6837
7105
  );
6838
7106
  }
@@ -6841,11 +7109,11 @@ function statusFromReturnedValue(node, facts) {
6841
7109
  return null;
6842
7110
  }
6843
7111
  const value = evaluatedValue(node, facts);
6844
- const number = (0, import_values19.constantOf)(value);
7112
+ const number = (0, import_values20.constantOf)(value);
6845
7113
  if (typeof number === "number") {
6846
7114
  return Number.isInteger(number) ? number : null;
6847
7115
  }
6848
- const text2 = (0, import_values19.literalOf)(value);
7116
+ const text2 = (0, import_values20.literalOf)(value);
6849
7117
  if (text2 === null) {
6850
7118
  return null;
6851
7119
  }
@@ -6853,30 +7121,49 @@ function statusFromReturnedValue(node, facts) {
6853
7121
  return digits === null ? null : Number.parseInt(digits[1], 10);
6854
7122
  }
6855
7123
  function statusOfReturn(statement, facts) {
6856
- const returned = statement.namedChildren[0];
7124
+ const returned = statement.namedChild(0);
6857
7125
  if (returned?.type !== "expression_list") {
6858
7126
  return { kind: "none" };
6859
7127
  }
6860
- const status = statusFromReturnedValue(returned.namedChildren[1], facts);
7128
+ const status = statusFromReturnedValue(
7129
+ returned.namedChild(1) ?? void 0,
7130
+ facts
7131
+ );
6861
7132
  return status === null ? { kind: "unreadable" } : { kind: "status", value: status };
6862
7133
  }
6863
7134
  function returnStatusReading(statement, ctx) {
6864
7135
  const read = statusOfReturnIn(statement, ctx.pattern, ctx);
6865
7136
  const range = rangeOf(statement);
6866
7137
  if (read.kind === "status") {
6867
- return (0, import_extractor7.writtenReading)(read.value, range);
7138
+ return (0, import_extractor9.writtenReading)(read.value, range);
6868
7139
  }
6869
7140
  if (read.kind === "unreadable") {
6870
- return (0, import_extractor7.unreadableReading)(
7141
+ return (0, import_extractor9.unreadableReading)(
6871
7142
  "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
6872
7143
  range
6873
7144
  );
6874
7145
  }
6875
- return ctx.pattern.statusFromReturnedTuple === true ? import_extractor7.absentReading : ctx.declaredStatus.reading;
7146
+ return ctx.pattern.statusFromReturnedTuple === true ? import_extractor9.absentReading : ctx.declaredStatus.reading;
6876
7147
  }
6877
7148
  function returnBranch(statement, ctx) {
6878
7149
  const range = rangeOf(statement);
6879
7150
  const writtenBody = returnedBodyShape(statement);
7151
+ return responseBranchAt(
7152
+ range,
7153
+ returnStatusReading(statement, ctx),
7154
+ writtenBody === null ? ctx.responseShape.reading : (0, import_extractor9.writtenReading)(writtenBody, range),
7155
+ ctx
7156
+ );
7157
+ }
7158
+ function fallthroughBranch(definitionNode, ctx) {
7159
+ return responseBranchAt(
7160
+ rangeOf(definitionNode),
7161
+ ctx.pattern.statusFromReturnedTuple === true ? import_extractor9.absentReading : ctx.declaredStatus.reading,
7162
+ ctx.responseShape.reading,
7163
+ ctx
7164
+ );
7165
+ }
7166
+ function responseBranchAt(range, status, body, ctx) {
6880
7167
  return {
6881
7168
  terminal: {
6882
7169
  kind: "response",
@@ -6893,12 +7180,10 @@ function returnBranch(statement, ctx) {
6893
7180
  location: range
6894
7181
  },
6895
7182
  statusCodeReading: {
6896
- reading: returnStatusReading(statement, ctx),
7183
+ reading: status,
6897
7184
  ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
6898
7185
  },
6899
- bodyShapeReading: {
6900
- reading: writtenBody === null ? ctx.responseShape.reading : (0, import_extractor7.writtenReading)(writtenBody, range)
6901
- },
7186
+ bodyShapeReading: { reading: body },
6902
7187
  location: range
6903
7188
  };
6904
7189
  }
@@ -6912,18 +7197,19 @@ function terminalBranchOf(found, ctx) {
6912
7197
  // A route has no continuation, so none is ever collected for it.
6913
7198
  continuation: (variant) => returnBranch(variant.statement, ctx)
6914
7199
  };
6915
- return (0, import_behavioral_ir5.dispatchByType)(table, found);
7200
+ return (0, import_behavioral_ir6.dispatchByType)(table, found);
6916
7201
  }
6917
7202
  function branchesPerTerminal(options) {
6918
7203
  const body = field(options.definitionNode, "body");
6919
7204
  const raised = raisedResponses(body, {
6920
7205
  calls: options.responseStatusCalls,
6921
7206
  module: options.ctx.module,
6922
- facts: options.ctx.facts
7207
+ facts: options.ctx.facts,
7208
+ constants: options.ctx.pattern.statusCodeConstants ?? {}
6923
7209
  });
6924
7210
  const terminals = bodyTerminals(body, raised);
6925
7211
  const soleReturnWritesBody = terminals.length === 1 && terminals[0]?.type === "return" && returnedBodyShape(terminals[0].statement) !== null;
6926
- if (terminals.length < 2 && !soleReturnWritesBody) {
7212
+ if (terminals.length === 0) {
6927
7213
  return null;
6928
7214
  }
6929
7215
  const branches = enumerateBodyBranches({
@@ -6932,6 +7218,7 @@ function branchesPerTerminal(options) {
6932
7218
  raised,
6933
7219
  effects: options.effects,
6934
7220
  branchOf: (found) => terminalBranchOf(found, options.ctx),
7221
+ fallthrough: fallthroughBranch(options.definitionNode, options.ctx),
6935
7222
  facts: options.ctx.facts
6936
7223
  });
6937
7224
  return branches.length > 1 || soleReturnWritesBody ? branches : null;
@@ -6940,7 +7227,8 @@ function statusOfReturnIn(statement, pattern, options) {
6940
7227
  const constructed = returnedResponseStatus(statement, {
6941
7228
  calls: pattern.responseConstructors ?? [],
6942
7229
  module: options.module,
6943
- facts: options.facts
7230
+ facts: options.facts,
7231
+ constants: pattern.statusCodeConstants ?? {}
6944
7232
  });
6945
7233
  if (constructed !== null) {
6946
7234
  return constructed.type === "literal" ? { kind: "status", value: constructed.value } : { kind: "unreadable" };
@@ -6952,7 +7240,7 @@ function statusOfReturnIn(statement, pattern, options) {
6952
7240
  }
6953
7241
  function readReturnedStatus(pattern, definitionNode, options) {
6954
7242
  if (pattern.statusFromReturnedTuple !== true && (pattern.responseConstructors ?? []).length === 0) {
6955
- return import_extractor7.absentReading;
7243
+ return import_extractor9.absentReading;
6956
7244
  }
6957
7245
  const range = rangeOf(definitionNode);
6958
7246
  const statuses = /* @__PURE__ */ new Set();
@@ -6969,23 +7257,23 @@ function readReturnedStatus(pattern, definitionNode, options) {
6969
7257
  }
6970
7258
  }
6971
7259
  if (unreadable > 0) {
6972
- return (0, import_extractor7.unreadableReading)(
7260
+ return (0, import_extractor9.unreadableReading)(
6973
7261
  "This route returns a status this reading cannot resolve to a number, so no status is claimed for it",
6974
7262
  range
6975
7263
  );
6976
7264
  }
6977
7265
  if (statuses.size === 0) {
6978
- return import_extractor7.absentReading;
7266
+ return import_extractor9.absentReading;
6979
7267
  }
6980
7268
  const candidates = [...statuses];
6981
7269
  if (statuses.size > 1 || plainReturns > 0) {
6982
- return (0, import_extractor7.ambiguousReading)(
7270
+ return (0, import_extractor9.ambiguousReading)(
6983
7271
  candidates,
6984
7272
  "This route returns more than one status depending on which branch runs, and nothing here reads which, so no status is claimed for it",
6985
7273
  range
6986
7274
  );
6987
7275
  }
6988
- return (0, import_extractor7.writtenReading)(candidates[0], range);
7276
+ return (0, import_extractor9.writtenReading)(candidates[0], range);
6989
7277
  }
6990
7278
  function declaredOrReturnedStatus(declared, returned) {
6991
7279
  return declared.kind === "absent" ? returned : declared;
@@ -6998,19 +7286,19 @@ function defaultedStatus(reading, pattern) {
6998
7286
  }
6999
7287
  function readResponseModel(pattern, classification, module2, ctx) {
7000
7288
  if (pattern.responseModelKeyword === void 0) {
7001
- return import_extractor7.absentReading;
7289
+ return import_extractor9.absentReading;
7002
7290
  }
7003
7291
  const arg = classification.keywordArgs[pattern.responseModelKeyword];
7004
7292
  if (arg === void 0) {
7005
- return import_extractor7.absentReading;
7293
+ return import_extractor9.absentReading;
7006
7294
  }
7007
7295
  if (arg.kind === "identifier") {
7008
- return (0, import_extractor7.writtenReading)(
7296
+ return (0, import_extractor9.writtenReading)(
7009
7297
  shapeFromName(arg.name, module2.moduleScope, ctx),
7010
7298
  classification.range
7011
7299
  );
7012
7300
  }
7013
- return (0, import_extractor7.unreadableReading)(
7301
+ return (0, import_extractor9.unreadableReading)(
7014
7302
  "The response model this route's decorator states is not a name, so the response claims no body shape",
7015
7303
  classification.range
7016
7304
  );
@@ -7018,9 +7306,9 @@ function readResponseModel(pattern, classification, module2, ctx) {
7018
7306
  function readReturnAnnotation(definitionNode, scope, ctx) {
7019
7307
  const returnTypeNode = field(definitionNode, "return_type");
7020
7308
  if (returnTypeNode === null) {
7021
- return import_extractor7.absentReading;
7309
+ return import_extractor9.absentReading;
7022
7310
  }
7023
- return (0, import_extractor7.writtenReading)(
7311
+ return (0, import_extractor9.writtenReading)(
7024
7312
  annotationToShape(returnTypeNode, scope, ctx),
7025
7313
  rangeOf(returnTypeNode)
7026
7314
  );
@@ -7034,6 +7322,25 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7034
7322
  module2.scopeFor,
7035
7323
  options.importedDefinition ?? null
7036
7324
  );
7325
+ const wrappers = wrappersAround(
7326
+ pattern,
7327
+ pack,
7328
+ classification,
7329
+ functionNode,
7330
+ module2,
7331
+ options
7332
+ );
7333
+ const dependencyParameters = options.wrappers?.dependencyParameters(
7334
+ wrappers
7335
+ ) ?? { names: /* @__PURE__ */ new Set(), declaresSource: false };
7336
+ const declaredStatuses = declaredStatusesOf(
7337
+ pattern,
7338
+ pack,
7339
+ classification,
7340
+ functionNode,
7341
+ module2,
7342
+ options
7343
+ );
7037
7344
  return readRoutePaths(
7038
7345
  pattern,
7039
7346
  readPathArgument(classification, options),
@@ -7046,6 +7353,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7046
7353
  pack,
7047
7354
  name: functionName,
7048
7355
  exportPath: [functionName],
7356
+ dependencyParameters,
7357
+ declaredStatuses,
7049
7358
  method: verb,
7050
7359
  routePath: entry.routePath,
7051
7360
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
@@ -7062,7 +7371,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7062
7371
  responseShape: readResponseModel(pattern, classification, module2, ctx),
7063
7372
  statusCode: defaultedStatus(
7064
7373
  declaredOrReturnedStatus(
7065
- readStatusCode(pattern, classification),
7374
+ readStatusCode(pattern, classification, {
7375
+ module: module2,
7376
+ facts: options.facts
7377
+ }),
7066
7378
  readReturnedStatus(pattern, functionNode, {
7067
7379
  module: module2,
7068
7380
  facts: options.facts
@@ -7073,14 +7385,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7073
7385
  definitionsCtx: ctx,
7074
7386
  storage: options.storage,
7075
7387
  importedDefinition: options.importedDefinition,
7076
- wrappers: wrappersAround(
7077
- pattern,
7078
- pack,
7079
- classification,
7080
- functionNode,
7081
- module2,
7082
- options
7083
- )
7388
+ wrappers
7084
7389
  },
7085
7390
  options
7086
7391
  )
@@ -7099,11 +7404,43 @@ function wrappersAround(pattern, pack, classification, definitionNode, module2,
7099
7404
  definitionNode
7100
7405
  });
7101
7406
  }
7407
+ function declaredStatusesOf(pattern, pack, classification, definitionNode, module2, options) {
7408
+ const keyword = pattern.responsesKeyword;
7409
+ if (keyword === void 0) {
7410
+ return [];
7411
+ }
7412
+ const above = options.wrappers?.callsAbove({
7413
+ pack,
7414
+ pattern,
7415
+ file: options.absoluteFile ?? options.filePath,
7416
+ module: module2,
7417
+ classification,
7418
+ definitionNode
7419
+ }) ?? [];
7420
+ const dictionaries = [
7421
+ classification.keywordArgs[keyword]?.node,
7422
+ ...above.map(
7423
+ (call) => readCallArguments(field(call, "arguments")).keywordArgs[keyword]?.node
7424
+ )
7425
+ ].filter((node) => node?.type === "dictionary");
7426
+ const statuses = dictionaries.flatMap(
7427
+ (dictionary) => children(dictionary).flatMap((pair) => {
7428
+ const key = pair?.type === "pair" ? field(pair, "key") : null;
7429
+ const status = key === null ? null : statusNumberOf(key, {
7430
+ module: moduleOf(key),
7431
+ facts: options.facts,
7432
+ constants: pattern.statusCodeConstants ?? {}
7433
+ });
7434
+ return status === null ? [] : [status];
7435
+ })
7436
+ );
7437
+ return [...new Set(statuses)].sort((a, b) => a - b);
7438
+ }
7102
7439
  function readResponseShape(declared, readAnnotation) {
7103
7440
  if (declared.kind === "written") {
7104
7441
  return { reading: declared, passedOver: [] };
7105
7442
  }
7106
- return (0, import_extractor7.firstWrittenReading)([declared, readAnnotation()]);
7443
+ return (0, import_extractor9.firstWrittenReading)([declared, readAnnotation()]);
7107
7444
  }
7108
7445
  function routeUnitOrAbstention(unitOptions, options) {
7109
7446
  try {
@@ -7131,7 +7468,7 @@ function unbuiltRouteUnit(unitOptions, error) {
7131
7468
  exportName: unitOptions.exportPath[0] ?? unitOptions.name,
7132
7469
  exportPath: unitOptions.exportPath
7133
7470
  },
7134
- boundaryBinding: (0, import_behavioral_ir5.restBinding)({
7471
+ boundaryBinding: (0, import_behavioral_ir6.restBinding)({
7135
7472
  transport: unitOptions.pack.protocol,
7136
7473
  method: nullIfEmpty(unitOptions.method),
7137
7474
  path: null,
@@ -7143,7 +7480,7 @@ function unbuiltRouteUnit(unitOptions, error) {
7143
7480
  dependencyCalls: [],
7144
7481
  declaredContract: null,
7145
7482
  readings: [
7146
- (0, import_extractor7.unreadableReading)(
7483
+ (0, import_extractor9.unreadableReading)(
7147
7484
  `This route could not be read into a unit (${message}), so the binding names no path and nothing pairs with it`,
7148
7485
  range
7149
7486
  )
@@ -7169,7 +7506,7 @@ function buildRouteUnit(options) {
7169
7506
  definitionsCtx,
7170
7507
  storage: storageLookup
7171
7508
  } = options;
7172
- const template = (0, import_extractor7.valueToReadFurtherFrom)(routePath);
7509
+ const template = (0, import_extractor9.valueToReadFurtherFrom)(routePath);
7173
7510
  const ctx = definitionsCtx ?? createAnnotationContext(
7174
7511
  module2.scopeFor,
7175
7512
  options.importedDefinition ?? null
@@ -7182,6 +7519,8 @@ function buildRouteUnit(options) {
7182
7519
  pathParamNames: template?.paramNames ?? null,
7183
7520
  requestBodyFromAnnotatedClass,
7184
7521
  injectedCallees,
7522
+ injectedTypes: new Set(pattern.injectedParameterTypes ?? []),
7523
+ module: module2,
7185
7524
  sources: pattern.parameterSources ?? {},
7186
7525
  aliasKeyword: pattern.parameterAliasKeyword
7187
7526
  },
@@ -7238,6 +7577,22 @@ function buildRouteUnit(options) {
7238
7577
  };
7239
7578
  branches.push(withBodyEffects(bare, extra));
7240
7579
  }
7580
+ if (branches.length > 0) {
7581
+ branches.push(
7582
+ ...validationFailureBranches(
7583
+ pattern,
7584
+ readsTheRequest(
7585
+ parameters,
7586
+ template,
7587
+ options.dependencyParameters ?? {
7588
+ names: /* @__PURE__ */ new Set(),
7589
+ declaresSource: false
7590
+ }
7591
+ ),
7592
+ rangeOf(definitionNode)
7593
+ )
7594
+ );
7595
+ }
7241
7596
  const bodyNode = field(definitionNode, "body");
7242
7597
  return {
7243
7598
  identity: {
@@ -7250,7 +7605,7 @@ function buildRouteUnit(options) {
7250
7605
  exportName: exportPath[0] ?? name,
7251
7606
  exportPath
7252
7607
  },
7253
- boundaryBinding: (0, import_behavioral_ir5.restBinding)({
7608
+ boundaryBinding: (0, import_behavioral_ir6.restBinding)({
7254
7609
  transport: pack.protocol,
7255
7610
  method,
7256
7611
  path: template?.path ?? null,
@@ -7269,15 +7624,57 @@ function buildRouteUnit(options) {
7269
7624
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
7270
7625
  ...options.mount !== void 0 ? { mount: options.mount } : {},
7271
7626
  ...options.wrappers !== void 0 && options.wrappers.length > 0 ? { wrappers: options.wrappers } : {},
7272
- ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling }
7627
+ ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling },
7628
+ ...options.declaredStatuses !== void 0 && options.declaredStatuses.length > 0 ? { declaredStatuses: options.declaredStatuses } : {}
7273
7629
  };
7274
7630
  }
7631
+ function readsTheRequest(parameters, template, dependencies) {
7632
+ if (dependencies.declaresSource || parameters.some((parameter) => parameter.role !== null)) {
7633
+ return true;
7634
+ }
7635
+ return [...template?.paramNames ?? []].some(
7636
+ (name) => dependencies.names.has(name)
7637
+ );
7638
+ }
7639
+ function validationFailureBranches(pattern, readsRequest, range) {
7640
+ const status = pattern.validationFailureStatus;
7641
+ if (status === void 0 || !readsRequest) {
7642
+ return [];
7643
+ }
7644
+ return [
7645
+ {
7646
+ conditions: [
7647
+ {
7648
+ sourceText: "request input fails validation",
7649
+ structured: null,
7650
+ polarity: "positive",
7651
+ source: "earlyThrow"
7652
+ }
7653
+ ],
7654
+ terminal: {
7655
+ kind: "response",
7656
+ statusCode: { type: "literal", value: status },
7657
+ body: null,
7658
+ exceptionType: null,
7659
+ message: null,
7660
+ component: null,
7661
+ renderTree: null,
7662
+ delegateTarget: null,
7663
+ emitEvent: null,
7664
+ location: range
7665
+ },
7666
+ effects: [],
7667
+ location: range,
7668
+ isDefault: false
7669
+ }
7670
+ ];
7671
+ }
7275
7672
  function unreadRoleReadings(parameters, range) {
7276
7673
  if (!parameters.some((parameter) => parameter.role === null)) {
7277
7674
  return [];
7278
7675
  }
7279
7676
  return [
7280
- (0, import_extractor7.unreadableReading)(
7677
+ (0, import_extractor9.unreadableReading)(
7281
7678
  "Nothing read this route's path, so its parameters name no role and a path parameter here does not read as one",
7282
7679
  range
7283
7680
  )
@@ -7338,10 +7735,7 @@ function readParameters(definitionNode, reading, skipReceiverParam) {
7338
7735
  }
7339
7736
  const out = [];
7340
7737
  let position = 0;
7341
- for (const param of parametersNode.namedChildren) {
7342
- if (param === null) {
7343
- continue;
7344
- }
7738
+ for (const param of children(parametersNode)) {
7345
7739
  if (skipReceiverParam && position === 0 && isReceiverParam(param)) {
7346
7740
  position += 1;
7347
7741
  continue;
@@ -7365,7 +7759,7 @@ function readParameter(param, reading, position) {
7365
7759
  const { name, typeNode } = info;
7366
7760
  const shape = typeNode !== null ? annotationToShape(typeNode, reading.scope, reading.ctx) : null;
7367
7761
  const typeText = typeNode !== null ? typeNode.text : null;
7368
- if (isInjectedParameter(param, reading)) {
7762
+ if (isInjectedParameter(param, reading) || isInjectedType(typeNode, reading)) {
7369
7763
  return { name, position, role: null, typeText };
7370
7764
  }
7371
7765
  const declared = declaringCall(
@@ -7445,6 +7839,14 @@ function calleeName2(call) {
7445
7839
  function isInjectedParameter(param, reading) {
7446
7840
  return declaringCall(param, reading.injectedCallees, reading) !== null;
7447
7841
  }
7842
+ function isInjectedType(typeNode, reading) {
7843
+ if (typeNode === null || reading.injectedTypes.size === 0) {
7844
+ return false;
7845
+ }
7846
+ const written = typeNode.type === "type" ? typeNode.namedChild(0) ?? null : typeNode;
7847
+ const origin = written === null ? null : originOf(written, reading.module);
7848
+ return origin !== null && reading.injectedTypes.has(`${origin.module}.${origin.name}`);
7849
+ }
7448
7850
  function declaringCall(param, callees, reading) {
7449
7851
  if (callees.size === 0) {
7450
7852
  return null;
@@ -7668,15 +8070,15 @@ function parseWith(language, source) {
7668
8070
  // src/project.ts
7669
8071
  var import_node_fs4 = __toESM(require("fs"), 1);
7670
8072
  var import_node_path5 = __toESM(require("path"), 1);
7671
- var import_behavioral_ir9 = require("@suss/behavioral-ir");
8073
+ var import_behavioral_ir10 = require("@suss/behavioral-ir");
7672
8074
  var import_datalog2 = require("@suss/datalog");
7673
- var import_extractor16 = require("@suss/extractor");
8075
+ var import_extractor18 = require("@suss/extractor");
7674
8076
  var import_resolution18 = require("@suss/resolution");
7675
8077
 
7676
8078
  // src/diagnostics.ts
7677
- var import_extractor8 = require("@suss/extractor");
8079
+ var import_extractor10 = require("@suss/extractor");
7678
8080
  function buildPythonExtractionReport(args) {
7679
- return (0, import_extractor8.buildUngatedExtractionReport)({
8081
+ return (0, import_extractor10.buildUngatedExtractionReport)({
7680
8082
  packs: args.packs.map((pack) => ({
7681
8083
  name: pack.name,
7682
8084
  version: pack.version ?? null,
@@ -7689,14 +8091,14 @@ function buildPythonExtractionReport(args) {
7689
8091
  }
7690
8092
 
7691
8093
  // src/exits.ts
7692
- var import_extractor9 = require("@suss/extractor");
8094
+ var import_extractor11 = require("@suss/extractor");
7693
8095
  var import_resolution9 = require("@suss/resolution");
7694
- var import_values20 = require("@suss/values");
8096
+ var import_values21 = require("@suss/values");
7695
8097
  var DEFERRED_BODY_TYPES3 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
7696
8098
  function exitSites(root, module2) {
7697
8099
  const found = [];
7698
8100
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
7699
- (0, import_extractor9.walkDescendants)(root, startScope, {
8101
+ (0, import_extractor11.walkDescendants)(root, startScope, children, {
7700
8102
  at: (node, scope) => {
7701
8103
  const site = exitSiteAt(node, scope);
7702
8104
  if (site !== null) {
@@ -7705,7 +8107,7 @@ function exitSites(root, module2) {
7705
8107
  },
7706
8108
  into: (node, scope) => {
7707
8109
  if (DEFERRED_BODY_TYPES3.has(node.type)) {
7708
- return import_extractor9.SKIP_CHILDREN;
8110
+ return import_extractor11.SKIP_CHILDREN;
7709
8111
  }
7710
8112
  return module2.scopeFor.get(node.id) ?? scope;
7711
8113
  }
@@ -7713,7 +8115,7 @@ function exitSites(root, module2) {
7713
8115
  return found;
7714
8116
  }
7715
8117
  function exitSiteAt(statement, scope) {
7716
- const expression = statement.namedChildren[0] ?? null;
8118
+ const expression = statement.namedChild(0) ?? null;
7717
8119
  if (expression === null) {
7718
8120
  return null;
7719
8121
  }
@@ -7760,7 +8162,7 @@ function exitCodeOf(code, facts) {
7760
8162
  if (stringLiteralValue(code) !== null) {
7761
8163
  return { type: "literal", value: 1 };
7762
8164
  }
7763
- const value = (0, import_values20.constantOf)(evaluatedValue(code, facts));
8165
+ const value = (0, import_values21.constantOf)(evaluatedValue(code, facts));
7764
8166
  return typeof value === "number" ? { type: "literal", value } : { type: "dynamic", sourceText: code.text };
7765
8167
  }
7766
8168
  function exitingBranches(definition, module2, effects, facts) {
@@ -7794,7 +8196,7 @@ function markExitCodeFunctions(summaries, exits, facts, workspaceRoot) {
7794
8196
  (site) => site.code === null ? [] : [nodeId(file, site.code)]
7795
8197
  )
7796
8198
  );
7797
- (0, import_extractor9.markReturnsAsExitCode)(
8199
+ (0, import_extractor11.markReturnsAsExitCode)(
7798
8200
  summaries,
7799
8201
  (0, import_resolution9.exitCodeFunctions)(facts, sinks),
7800
8202
  workspaceRoot
@@ -7879,25 +8281,25 @@ function definedNodeOf(binding) {
7879
8281
  }
7880
8282
 
7881
8283
  // src/moduleSurface.ts
7882
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
7883
- var import_extractor12 = require("@suss/extractor");
8284
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
8285
+ var import_extractor14 = require("@suss/extractor");
7884
8286
  var import_resolution13 = require("@suss/resolution");
7885
8287
 
7886
8288
  // src/reach/closure.ts
7887
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
8289
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
7888
8290
  var import_datalog = require("@suss/datalog");
7889
- var import_extractor11 = require("@suss/extractor");
8291
+ var import_extractor13 = require("@suss/extractor");
7890
8292
  var import_resolution12 = require("@suss/resolution");
7891
8293
 
7892
8294
  // src/flags.ts
7893
- var import_extractor10 = require("@suss/extractor");
8295
+ var import_extractor12 = require("@suss/extractor");
7894
8296
  var PARSE_METHODS = /* @__PURE__ */ new Set(["parse_args", "parse_known_args"]);
7895
8297
  var DEFERRED_BODY_TYPES4 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
7896
8298
  function argparseFlagReads(definition, module2, parameterNames2) {
7897
8299
  const parsers = /* @__PURE__ */ new Map();
7898
8300
  const handedTo = /* @__PURE__ */ new Map();
7899
8301
  const startScope = module2.scopeFor.get(definition.id) ?? module2.moduleScope;
7900
- (0, import_extractor10.walkDescendants)(definition, startScope, {
8302
+ (0, import_extractor12.walkDescendants)(definition, startScope, children, {
7901
8303
  at: (node, scope) => {
7902
8304
  noteParser(node, scope, parsers);
7903
8305
  noteArgument(node, parsers);
@@ -7905,7 +8307,7 @@ function argparseFlagReads(definition, module2, parameterNames2) {
7905
8307
  },
7906
8308
  into: (node, scope) => {
7907
8309
  if (DEFERRED_BODY_TYPES4.has(node.type)) {
7908
- return import_extractor10.SKIP_CHILDREN;
8310
+ return import_extractor12.SKIP_CHILDREN;
7909
8311
  }
7910
8312
  return module2.scopeFor.get(node.id) ?? scope;
7911
8313
  }
@@ -7999,8 +8401,40 @@ function resolveCallee2(call, site, ctx, read) {
7999
8401
  return spelling;
8000
8402
  }
8001
8403
  const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution11.calleeOutcomeOf)(ctx.facts, spelling.key);
8404
+ if (selfCallInMixin(call, outcome, site, ctx)) {
8405
+ return { kind: "stopped", reason: "selfCallInMixin" };
8406
+ }
8002
8407
  return asCallee(outcome, site.owner, ctx);
8003
8408
  }
8409
+ function selfCallInMixin(call, outcome, site, ctx) {
8410
+ if (outcome.kind !== "callerSupplied" || !outcome.key.startsWith(`${site.owner}#`)) {
8411
+ return false;
8412
+ }
8413
+ const callee = readThrough(field(call, "function"));
8414
+ const receiver = callee?.type === "attribute" ? field(callee, "object") : null;
8415
+ const name = callee === null ? null : field(callee, "attribute")?.text;
8416
+ if (receiver?.type !== "identifier" || name === void 0 || name === null) {
8417
+ return false;
8418
+ }
8419
+ if (outcome.key !== `${site.owner}#${receiver.text}`) {
8420
+ return false;
8421
+ }
8422
+ return ctx.facts.lookup("instanceOf", 0, outcome.key).some((row) => {
8423
+ const classKey = String(row[1]);
8424
+ return ctx.facts.has("plainClass", [classKey]) && !instancesHaveAttribute(ctx.facts, classKey, name);
8425
+ });
8426
+ }
8427
+ var FALLBACK_ATTRIBUTE = "__getattr__";
8428
+ function instancesHaveAttribute(facts, classKey, name) {
8429
+ return facts.lookup("holdsProperty", 0, classKey).some((member) => {
8430
+ if (String(member[1]) === FALLBACK_ATTRIBUTE) {
8431
+ return true;
8432
+ }
8433
+ return facts.lookup("storesProperty", 0, String(member[2])).some(
8434
+ (store) => String(store[1]) === name && store[3] === import_resolution11.RECEIVER_STORE_NAME
8435
+ );
8436
+ });
8437
+ }
8004
8438
  function namedOutcomes(keys, ctx) {
8005
8439
  return (0, import_resolution11.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
8006
8440
  }
@@ -8324,17 +8758,17 @@ function reachedFunctions(seeds, options) {
8324
8758
  continue;
8325
8759
  }
8326
8760
  const build = () => {
8327
- const summary2 = (0, import_extractor11.assembleSummary)(libraryUnit(target, options), {
8761
+ const summary2 = (0, import_extractor13.assembleSummary)(libraryUnit(target, options), {
8328
8762
  gapHandling: options.gapHandling
8329
8763
  });
8330
8764
  if (options.gapHandling !== "silent") {
8331
8765
  summary2.gaps.push(
8332
- ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
8766
+ ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir7.unfollowedCallGap)
8333
8767
  );
8334
8768
  }
8335
- (0, import_behavioral_ir6.placeCalls)(summary2, targetsByKey.get(key));
8336
- (0, import_behavioral_ir6.placeArgTargets)(summary2, argTargetsByKey.get(key));
8337
- (0, import_behavioral_ir6.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
8769
+ (0, import_behavioral_ir7.placeCalls)(summary2, targetsByKey.get(key));
8770
+ (0, import_behavioral_ir7.placeArgTargets)(summary2, argTargetsByKey.get(key));
8771
+ (0, import_behavioral_ir7.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
8338
8772
  return summary2;
8339
8773
  };
8340
8774
  const summary = ledger === void 0 ? build() : ledger.charging(chargeOf(charges, key), () => {
@@ -8350,7 +8784,7 @@ function reachedFunctions(seeds, options) {
8350
8784
  summaries.push(summary);
8351
8785
  }
8352
8786
  if (options.gapHandling !== "silent") {
8353
- (0, import_behavioral_ir6.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
8787
+ (0, import_behavioral_ir7.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
8354
8788
  }
8355
8789
  return {
8356
8790
  summaries,
@@ -8550,15 +8984,15 @@ function passedNameKeys(file, call) {
8550
8984
  if (args === null) {
8551
8985
  return [];
8552
8986
  }
8553
- return args.namedChildren.flatMap((arg) => {
8987
+ return children(args).flatMap((arg) => {
8554
8988
  const key = passedNameKeyOf(file, arg);
8555
8989
  return key === null ? [] : [key];
8556
8990
  });
8557
8991
  }
8558
8992
  function scanBody(source, ctx, recognized, read, implied) {
8559
8993
  const followed = [];
8560
- const stops = [];
8561
- const placements = new import_behavioral_ir6.TargetPlacements();
8994
+ const stops = new import_behavioral_ir7.StopList();
8995
+ const placements = new import_behavioral_ir7.TargetPlacements();
8562
8996
  const parameterCalls = [];
8563
8997
  const passedPositions = /* @__PURE__ */ new Set();
8564
8998
  const seen = /* @__PURE__ */ new Set();
@@ -8574,7 +9008,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8574
9008
  if (args === null) {
8575
9009
  return;
8576
9010
  }
8577
- args.namedChildren.forEach((arg, position) => {
9011
+ children(args).forEach((arg, position) => {
8578
9012
  const nameKey2 = passedNameKeyOf(file, arg);
8579
9013
  if (nameKey2 === null) {
8580
9014
  return;
@@ -8607,10 +9041,12 @@ function scanBody(source, ctx, recognized, read, implied) {
8607
9041
  outcome.kind === "followed" ? keyOf2(outcome.target) : null
8608
9042
  );
8609
9043
  if (outcome.kind === "stopped") {
8610
- const stopKey = `${outcome.reason}:${callee}`;
8611
- if (!seen.has(stopKey) && !recognized.has(call.id) && (0, import_behavioral_ir6.worthRecording)(outcome.reason)) {
8612
- seen.add(stopKey);
8613
- stops.push({ callee, reason: outcome.reason });
9044
+ if (!recognized.has(call.id) && (0, import_behavioral_ir7.worthRecording)(outcome.reason)) {
9045
+ stops.add({
9046
+ callee,
9047
+ reason: outcome.reason,
9048
+ ...resultUsedAsValue(call) ? { usedAsValue: true } : {}
9049
+ });
8614
9050
  }
8615
9051
  if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
8616
9052
  const parameterIndex = ownParameters.indexOf(callee);
@@ -8643,7 +9079,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8643
9079
  }
8644
9080
  return {
8645
9081
  followed,
8646
- stops,
9082
+ stops: stops.stops,
8647
9083
  targets: placements.targets,
8648
9084
  argTargets: placements.argTargets,
8649
9085
  parameterCalls,
@@ -8652,13 +9088,13 @@ function scanBody(source, ctx, recognized, read, implied) {
8652
9088
  }
8653
9089
  function callsWritten(file, body, outer, owner, limit) {
8654
9090
  const found = [];
8655
- (0, import_extractor11.walkDescendants)(body, outer, {
9091
+ (0, import_extractor13.walkDescendants)(body, outer, children, {
8656
9092
  at: (child, scope) => {
8657
9093
  if (child.type === "call") {
8658
9094
  found.push({ call: child, site: { file, scope, owner } });
8659
9095
  }
8660
9096
  },
8661
- into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor11.SKIP_CHILDREN
9097
+ into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor13.SKIP_CHILDREN
8662
9098
  });
8663
9099
  return found;
8664
9100
  }
@@ -8678,14 +9114,14 @@ function identifiersUnder(node, found = []) {
8678
9114
  found.push(node.text);
8679
9115
  return found;
8680
9116
  }
8681
- for (const child of node.namedChildren) {
9117
+ for (const child of children(node)) {
8682
9118
  if (child !== null && child.type !== "attribute" && child.type !== "subscript") {
8683
9119
  identifiersUnder(child, found);
8684
9120
  }
8685
9121
  }
8686
9122
  return found;
8687
9123
  }
8688
- function libraryUnit(target, options, binding = (0, import_behavioral_ir6.functionCallBinding)({
9124
+ function libraryUnit(target, options, binding = (0, import_behavioral_ir7.functionCallBinding)({
8689
9125
  transport: "in-process",
8690
9126
  recognition: "reachable"
8691
9127
  })) {
@@ -8779,10 +9215,7 @@ function positionalParameters(node, isMethod2) {
8779
9215
  }
8780
9216
  const out = [];
8781
9217
  let position = 0;
8782
- for (const param of parameters.namedChildren) {
8783
- if (param === null) {
8784
- continue;
8785
- }
9218
+ for (const param of children(parameters)) {
8786
9219
  const name = parameterName2(param);
8787
9220
  if (name === null) {
8788
9221
  continue;
@@ -8802,10 +9235,7 @@ function callParameterNames(node, isMethod2) {
8802
9235
  return [];
8803
9236
  }
8804
9237
  const names = [];
8805
- for (const param of parameters.namedChildren) {
8806
- if (param === null) {
8807
- continue;
8808
- }
9238
+ for (const param of children(parameters)) {
8809
9239
  const name = parameterName2(param);
8810
9240
  if (name === null) {
8811
9241
  continue;
@@ -8830,10 +9260,10 @@ function parameterName2(param) {
8830
9260
 
8831
9261
  // src/moduleSurface.ts
8832
9262
  function settlePythonModules(modules) {
8833
- return (0, import_extractor12.settleModules)(modules, ["__init__.py"]);
9263
+ return (0, import_extractor14.settleModules)(modules, ["__init__.py"]);
8834
9264
  }
8835
9265
  function moduleExportUnits(file, options) {
8836
- const module2 = (0, import_extractor12.moduleOfFile)(options.modules, file.file);
9266
+ const module2 = (0, import_extractor14.moduleOfFile)(options.modules, file.file);
8837
9267
  if (module2 === void 0) {
8838
9268
  return [];
8839
9269
  }
@@ -8855,8 +9285,8 @@ function moduleExportUnits(file, options) {
8855
9285
  exportPath: [...one.exportPath]
8856
9286
  },
8857
9287
  options,
8858
- (0, import_behavioral_ir7.moduleExportBinding)({
8859
- recognition: import_extractor12.MODULE_SURFACE_RECOGNITION,
9288
+ (0, import_behavioral_ir8.moduleExportBinding)({
9289
+ recognition: import_extractor14.MODULE_SURFACE_RECOGNITION,
8860
9290
  module: module2.name,
8861
9291
  exportName
8862
9292
  })
@@ -8902,7 +9332,7 @@ function exportsOf(file, node, exportName) {
8902
9332
  if (node.type !== "class_definition" || body === null) {
8903
9333
  return [];
8904
9334
  }
8905
- return body.namedChildren.flatMap((child) => {
9335
+ return children(body).flatMap((child) => {
8906
9336
  if (child === null) {
8907
9337
  return [];
8908
9338
  }
@@ -9025,6 +9455,11 @@ function watchRouterIndex(index, db, positions, fileOfModule) {
9025
9455
  found
9026
9456
  );
9027
9457
  return found;
9458
+ },
9459
+ // The wrapper index notes what it reads through the mounts, in terms
9460
+ // it can look up again.
9461
+ mountsAbove(pattern, constructionKey) {
9462
+ return index.mountsAbove(pattern, constructionKey);
9028
9463
  }
9029
9464
  };
9030
9465
  }
@@ -9129,9 +9564,33 @@ function buildRouterIndex(files, packs, resolverOptions) {
9129
9564
  };
9130
9565
  }
9131
9566
  return NOT_ROUTER;
9567
+ },
9568
+ mountsAbove(pattern, constructionKey) {
9569
+ const index = byPattern.get(pattern);
9570
+ return index === void 0 ? null : hopsAbove(index, constructionKey);
9132
9571
  }
9133
9572
  };
9134
9573
  }
9574
+ function hopsAbove(index, key) {
9575
+ const hops = [];
9576
+ const seen = /* @__PURE__ */ new Set();
9577
+ let current = key;
9578
+ while (current !== null) {
9579
+ const construction = index.byValueKey.get(current);
9580
+ if (construction === void 0 || seen.has(current)) {
9581
+ return null;
9582
+ }
9583
+ seen.add(current);
9584
+ const states = index.mounts.get(construction) ?? [];
9585
+ const only = states.length === 1 ? states[0] : void 0;
9586
+ if (only?.kind !== "mounted" || only.written === void 0) {
9587
+ return null;
9588
+ }
9589
+ hops.push({ ...only.written, parentKey: only.parentValueKey });
9590
+ current = only.parentValueKey;
9591
+ }
9592
+ return hops;
9593
+ }
9135
9594
  function composedPrefixOf(index, construction, routerPath) {
9136
9595
  const ownPrefix = composedOwnPrefix(construction.prefix, index.composition);
9137
9596
  if (ownPrefix.kind === "abstain") {
@@ -9414,8 +9873,8 @@ function constructionStatement(stmt, module2, importModule) {
9414
9873
  if (stmt.type !== "expression_statement") {
9415
9874
  return null;
9416
9875
  }
9417
- const assignment2 = stmt.namedChildren.find(
9418
- (child) => child !== null && child.type === "assignment"
9876
+ const assignment2 = children(stmt).find(
9877
+ (child) => child.type === "assignment"
9419
9878
  );
9420
9879
  if (assignment2 === void 0) {
9421
9880
  return null;
@@ -9489,7 +9948,7 @@ function collectReturnedConstructions(bound, importModule, composition, index) {
9489
9948
  if (inner.type !== "return_statement") {
9490
9949
  continue;
9491
9950
  }
9492
- const returned = inner.namedChildren[0];
9951
+ const returned = inner.namedChild(0);
9493
9952
  if (returned?.type !== "call") {
9494
9953
  continue;
9495
9954
  }
@@ -9669,7 +10128,7 @@ function sequenceElementNames(node) {
9669
10128
  return null;
9670
10129
  }
9671
10130
  const names = [];
9672
- for (const child of node.namedChildren) {
10131
+ for (const child of children(node)) {
9673
10132
  if (child?.type !== "identifier") {
9674
10133
  return null;
9675
10134
  }
@@ -9882,8 +10341,9 @@ function recordMountStatement(stmt, position, scan) {
9882
10341
  scan.index.facts
9883
10342
  );
9884
10343
  const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
10344
+ const recorded = state.kind === "mounted" ? { ...state, written: { call: mountCall.call, file: scan.bound.file } } : state;
9885
10345
  for (const target of mountedConstructions(mounted, position, scan)) {
9886
- recordMount(scan.index, target, state);
10346
+ recordMount(scan.index, target, recorded);
9887
10347
  }
9888
10348
  }
9889
10349
  function mountStateOf(includerConstructorName, includer, keywordArgs, objectPrefix, composition, site, facts) {
@@ -10324,10 +10784,10 @@ function poetryPackages(pyproject) {
10324
10784
 
10325
10785
  // src/testCases.ts
10326
10786
  var import_node_path4 = __toESM(require("path"), 1);
10327
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
10328
- var import_extractor13 = require("@suss/extractor");
10787
+ var import_behavioral_ir9 = require("@suss/behavioral-ir");
10788
+ var import_extractor15 = require("@suss/extractor");
10329
10789
  var import_resolution16 = require("@suss/resolution");
10330
- var import_values26 = require("@suss/values");
10790
+ var import_values27 = require("@suss/values");
10331
10791
  function pythonTestIndex(packs, context) {
10332
10792
  const patterns = packs.flatMap((pack) => pack.tests ?? []);
10333
10793
  return patterns.length === 0 ? null : new PythonTestIndex(patterns, context);
@@ -10344,7 +10804,7 @@ var PythonTestIndex = class {
10344
10804
  implied = /* @__PURE__ */ new Map();
10345
10805
  /** The tests in one file, when the file is collected and listed. */
10346
10806
  unitsIn(file, options) {
10347
- return this.testsIn(file).filter((test) => (0, import_extractor13.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
10807
+ return this.testsIn(file).filter((test) => (0, import_extractor15.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
10348
10808
  }
10349
10809
  /** What pytest calls before this function runs, when it is a test or a fixture. */
10350
10810
  impliedCalls(source) {
@@ -10394,7 +10854,7 @@ var PythonTestIndex = class {
10394
10854
  )
10395
10855
  ).flatMap(
10396
10856
  (marker) => positionalArguments(marker).flatMap((arg) => {
10397
- const name = (0, import_values26.literalOf)(evaluatedValue(arg, this.context.facts));
10857
+ const name = (0, import_values27.literalOf)(evaluatedValue(arg, this.context.facts));
10398
10858
  return name === null ? [] : [name];
10399
10859
  })
10400
10860
  );
@@ -10418,7 +10878,7 @@ var PythonTestIndex = class {
10418
10878
  ...raw,
10419
10879
  identity: {
10420
10880
  ...raw.identity,
10421
- name: (0, import_behavioral_ir8.testUnitName)(test.titles),
10881
+ name: (0, import_behavioral_ir9.testUnitName)(test.titles),
10422
10882
  nameKind: "label",
10423
10883
  kind: "test",
10424
10884
  exportName: null,
@@ -10430,7 +10890,7 @@ var PythonTestIndex = class {
10430
10890
  }
10431
10891
  patternFor(file) {
10432
10892
  return this.patterns.find(
10433
- (pattern) => (0, import_extractor13.matchesTestFileName)(file.file, pattern.filePatterns)
10893
+ (pattern) => (0, import_extractor15.matchesTestFileName)(file.file, pattern.filePatterns)
10434
10894
  ) ?? null;
10435
10895
  }
10436
10896
  testsIn(file) {
@@ -10808,11 +11268,11 @@ var PythonTestIndex = class {
10808
11268
  return null;
10809
11269
  }
10810
11270
  const written = oneLine(call.text);
10811
- const target = (0, import_values26.literalOf)(evaluatedValue(first, this.context.facts));
11271
+ const target = (0, import_values27.literalOf)(evaluatedValue(first, this.context.facts));
10812
11272
  if (target !== null) {
10813
11273
  return { ...this.dottedTarget(target), written };
10814
11274
  }
10815
- const member = second === void 0 ? null : (0, import_values26.literalOf)(evaluatedValue(second, this.context.facts));
11275
+ const member = second === void 0 ? null : (0, import_values27.literalOf)(evaluatedValue(second, this.context.facts));
10816
11276
  if (member === null) {
10817
11277
  return null;
10818
11278
  }
@@ -10950,8 +11410,8 @@ function fixtureDeclaration(definition, file, pattern, facts) {
10950
11410
  const renamed = keywordArgument(decorator, pattern.fixtureNameKeyword);
10951
11411
  const autouse = keywordArgument(decorator, pattern.autouseKeyword);
10952
11412
  return {
10953
- name: (renamed === null ? null : (0, import_values26.literalOf)(evaluatedValue(renamed, facts))) ?? defName,
10954
- autouse: autouse !== null && (0, import_values26.constantOf)(evaluatedValue(autouse, facts)) === true
11413
+ name: (renamed === null ? null : (0, import_values27.literalOf)(evaluatedValue(renamed, facts))) ?? defName,
11414
+ autouse: autouse !== null && (0, import_values27.constantOf)(evaluatedValue(autouse, facts)) === true
10955
11415
  };
10956
11416
  }
10957
11417
  function keywordArgument(call, name) {
@@ -10983,18 +11443,20 @@ function oneLine(text2) {
10983
11443
  }
10984
11444
 
10985
11445
  // src/version.ts
10986
- var import_extractor14 = require("@suss/extractor");
11446
+ var import_extractor16 = require("@suss/extractor");
10987
11447
  var ADAPTER_VERSION = "0.1.0";
10988
- var adapterStamp = (0, import_extractor14.createAdapterStamp)({
11448
+ var adapterStamp = (0, import_extractor16.createAdapterStamp)({
10989
11449
  moduleUrl: importMetaUrl,
10990
11450
  version: ADAPTER_VERSION
10991
11451
  });
10992
11452
 
10993
11453
  // src/wrappers.ts
10994
- var import_extractor15 = require("@suss/extractor");
11454
+ var import_extractor17 = require("@suss/extractor");
10995
11455
  var import_resolution17 = require("@suss/resolution");
10996
11456
  var EVERY_ROUTE = "wrapEvery ";
10997
11457
  var OWN_ROUTES = "wrapOwn ";
11458
+ var MOUNTED = "wrapMounted ";
11459
+ var MOUNTED_SEPARATOR = "\0";
10998
11460
  function describeRegistered(found) {
10999
11461
  return (found ?? []).map(
11000
11462
  ({ reference }) => `${reference.file}:${reference.name}:${reference.onThrow === true}`
@@ -11032,6 +11494,10 @@ var PythonWrapperIndex = class {
11032
11494
  /** By construction key: what a router or a blueprint registered. */
11033
11495
  ownRoutes = /* @__PURE__ */ new Map();
11034
11496
  unitsByKey = /* @__PURE__ */ new Map();
11497
+ /** By pack name: the app constructions its routes run under. */
11498
+ appCalls = /* @__PURE__ */ new Map();
11499
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
11500
+ dependencyParameterTable = /* @__PURE__ */ new Map();
11035
11501
  unitsByFile = /* @__PURE__ */ new Map();
11036
11502
  /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
11037
11503
  onRegistered = null;
@@ -11050,6 +11516,12 @@ var PythonWrapperIndex = class {
11050
11516
  this.ownRoutes.get(id.slice(OWN_ROUTES.length))
11051
11517
  );
11052
11518
  }
11519
+ if (id.startsWith(MOUNTED)) {
11520
+ const [packName, position, key] = id.slice(MOUNTED.length).split(MOUNTED_SEPARATOR);
11521
+ const pack = this.options.packs.find((one) => one.name === packName);
11522
+ const pattern = pack?.discovery[Number(position)];
11523
+ return pack === void 0 || pattern === void 0 || key === void 0 ? describeRegistered([]) : describeRegistered(this.registeredAbove(pack, pattern, key));
11524
+ }
11053
11525
  return null;
11054
11526
  }
11055
11527
  noted(kind, key, found) {
@@ -11091,14 +11563,46 @@ var PythonWrapperIndex = class {
11091
11563
  inFile.push(unit);
11092
11564
  this.unitsByFile.set(target.file.file, inFile);
11093
11565
  }
11094
- return {
11095
- reference: {
11096
- file: target.file.displayPath,
11097
- name: target.name,
11098
- ...isThrowForm(declared.form) ? { onThrow: true } : {}
11099
- },
11100
- form: declared.form
11566
+ const reference = {
11567
+ file: target.file.displayPath,
11568
+ name: target.name,
11569
+ ...isThrowForm(declared.form) ? { onThrow: true } : {}
11101
11570
  };
11571
+ if (declared.form.type === "dependency") {
11572
+ this.dependencyParameterTable.set(referenceKey(reference), {
11573
+ file: target.file.file,
11574
+ names: parameterNamesOf(target.node),
11575
+ declaresSource: declaresSourcedParameter(
11576
+ target.node,
11577
+ Object.keys(declared.pattern.parameterSources ?? {})
11578
+ )
11579
+ });
11580
+ }
11581
+ return { reference, form: declared.form };
11582
+ }
11583
+ /**
11584
+ * The parameters the dependencies among these wrappers declare, and
11585
+ * whether one is declared through a parameter source such as `Query()`.
11586
+ * The library fills those from the request and validates them, the same
11587
+ * as parameters the route declares.
11588
+ */
11589
+ dependencyParameters(references) {
11590
+ const names = /* @__PURE__ */ new Set();
11591
+ let declaresSource = false;
11592
+ for (const reference of references) {
11593
+ const found = this.dependencyParameterTable.get(referenceKey(reference));
11594
+ if (found === void 0) {
11595
+ continue;
11596
+ }
11597
+ if (this.options.facts !== void 0) {
11598
+ (0, import_resolution17.noteKeyRead)(this.options.facts, found.file);
11599
+ }
11600
+ for (const name of found.names) {
11601
+ names.add(name);
11602
+ }
11603
+ declaresSource ||= found.declaresSource;
11604
+ }
11605
+ return { names, declaresSource };
11102
11606
  }
11103
11607
  wrappersFor(query) {
11104
11608
  const file = this.filesByPath.get(query.file);
@@ -11110,13 +11614,15 @@ var PythonWrapperIndex = class {
11110
11614
  if (file === void 0 || forms.length === 0) {
11111
11615
  return [];
11112
11616
  }
11617
+ const own = this.ownRoutesOf(query);
11113
11618
  const found = [
11114
11619
  ...this.noted(
11115
11620
  EVERY_ROUTE,
11116
11621
  query.pack.name,
11117
11622
  this.everyRoute.get(query.pack.name)
11118
11623
  ),
11119
- ...this.ownRoutesOf(query)
11624
+ ...own === null ? [] : this.mountedAbove(query, own.key),
11625
+ ...own?.registered ?? []
11120
11626
  ];
11121
11627
  for (const declared of forms) {
11122
11628
  if (declared.form.type !== "dependency") {
@@ -11147,11 +11653,65 @@ var PythonWrapperIndex = class {
11147
11653
  }
11148
11654
  return ordered;
11149
11655
  }
11656
+ /**
11657
+ * What the mounts above the route's router register on the way down:
11658
+ * the dependencies an `include_router` call lists, and those of each
11659
+ * router it is mounted onto, outermost first, the order they run in.
11660
+ */
11661
+ mountedAbove(query, key) {
11662
+ const position = query.pack.discovery.indexOf(query.pattern);
11663
+ return [
11664
+ ...this.noted(
11665
+ MOUNTED,
11666
+ [query.pack.name, position, key].join(MOUNTED_SEPARATOR),
11667
+ this.registeredAbove(query.pack, query.pattern, key)
11668
+ )
11669
+ ];
11670
+ }
11671
+ registeredAbove(pack, pattern, key) {
11672
+ const hops = this.options.routers?.mountsAbove(pattern, key) ?? null;
11673
+ if (hops === null) {
11674
+ return [];
11675
+ }
11676
+ const forms = (pattern.wrappers ?? []).flatMap(
11677
+ (form) => form.type === "dependency" ? [{ pack, pattern, form }] : []
11678
+ );
11679
+ const found = [];
11680
+ for (const hop of [...hops].reverse()) {
11681
+ found.push(
11682
+ ...hop.parentKey === null ? [] : this.ownRoutes.get(hop.parentKey) ?? []
11683
+ );
11684
+ const { keywordArgs } = readCallArguments(field(hop.call, "arguments"));
11685
+ for (const declared of forms) {
11686
+ const listed = keywordArgs[declared.form.keyword];
11687
+ const names = listed === void 0 ? [] : dependencyNamesIn(listed.node, declared.form);
11688
+ for (const name of names) {
11689
+ const target = this.functionCalled(hop.file, name);
11690
+ if (target !== null) {
11691
+ found.push(this.registered(target, declared));
11692
+ }
11693
+ }
11694
+ }
11695
+ }
11696
+ return found;
11697
+ }
11150
11698
  ownRoutesOf(query) {
11699
+ const match = this.ownRouterOf(query);
11700
+ return match === null ? null : {
11701
+ key: match.key,
11702
+ registered: this.noted(
11703
+ OWN_ROUTES,
11704
+ match.key,
11705
+ this.ownRoutes.get(match.key)
11706
+ )
11707
+ };
11708
+ }
11709
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
11710
+ ownRouterOf(query) {
11151
11711
  const site = this.objectSiteOf(query);
11152
11712
  const bound = site === null ? void 0 : this.filesByPath.get(site.file);
11153
11713
  if (site === null || bound === void 0) {
11154
- return [];
11714
+ return null;
11155
11715
  }
11156
11716
  if (this.options.facts !== void 0) {
11157
11717
  (0, import_resolution17.noteKeyRead)(this.options.facts, site.file);
@@ -11165,12 +11725,46 @@ var PythonWrapperIndex = class {
11165
11725
  { pack: query.pack, pattern: query.pattern, form }
11166
11726
  );
11167
11727
  if (match !== null && match.registrar.covers === "ownRoutes") {
11168
- return [
11169
- ...this.noted(OWN_ROUTES, match.key, this.ownRoutes.get(match.key))
11170
- ];
11728
+ return match;
11171
11729
  }
11172
11730
  }
11173
- return [];
11731
+ return null;
11732
+ }
11733
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
11734
+ recordApp(pack, call, file) {
11735
+ const calls = this.appCalls.get(pack) ?? [];
11736
+ if (!calls.some((one) => one.call === call)) {
11737
+ calls.push({ call, file });
11738
+ }
11739
+ this.appCalls.set(pack, calls);
11740
+ }
11741
+ /**
11742
+ * Every call that registers something for a route on the way to it:
11743
+ * the app's construction, the mount calls and the routers they mount
11744
+ * onto, and the route's own router. A caller reads a keyword off each.
11745
+ */
11746
+ callsAbove(query) {
11747
+ const own = this.ownRouterOf(query);
11748
+ const hops = own === null ? [] : this.options.routers?.mountsAbove(query.pattern, own.key) ?? [];
11749
+ const parents = hops.flatMap((hop) => {
11750
+ const node = hop.parentKey === null ? null : nodeOfResolutionKey(hop.parentKey, this.options.facts);
11751
+ return node === null ? [] : [node];
11752
+ });
11753
+ const files = [
11754
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.file),
11755
+ ...hops.map((hop) => hop.file)
11756
+ ];
11757
+ if (this.options.facts !== void 0) {
11758
+ for (const file of files) {
11759
+ (0, import_resolution17.noteKeyRead)(this.options.facts, file);
11760
+ }
11761
+ }
11762
+ return [
11763
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.call),
11764
+ ...hops.map((hop) => hop.call),
11765
+ ...parents,
11766
+ ...own === null ? [] : [own.call]
11767
+ ];
11174
11768
  }
11175
11769
  /**
11176
11770
  * Where the decorator's object was built: the route's own file, the file
@@ -11215,6 +11809,23 @@ var PythonWrapperIndex = class {
11215
11809
  return this.unitsByFile.get(file) ?? [];
11216
11810
  }
11217
11811
  };
11812
+ function referenceKey(reference) {
11813
+ return `${reference.file}::${reference.name}`;
11814
+ }
11815
+ function parameterNamesOf(definition) {
11816
+ return children(field(definition, "parameters")).flatMap((parameter) => {
11817
+ const named = parameterNameAndType(parameter);
11818
+ return named === null ? [] : [named.name];
11819
+ });
11820
+ }
11821
+ function declaresSourcedParameter(definition, sources) {
11822
+ return children(field(definition, "parameters")).some((parameter) => {
11823
+ const value = field(parameter, "value");
11824
+ const callee = value?.type === "call" ? field(value, "function") : null;
11825
+ const name = callee?.type === "attribute" ? field(callee, "attribute")?.text : callee?.text;
11826
+ return name !== void 0 && sources.includes(name);
11827
+ });
11828
+ }
11218
11829
  function isThrowForm(form) {
11219
11830
  return form.type === "decoratedWrapper" && form.throwParam !== void 0;
11220
11831
  }
@@ -11261,8 +11872,8 @@ function registerFrom(file, declared, index) {
11261
11872
  }
11262
11873
  function registerConstructorDependencies(file, declared, form, index) {
11263
11874
  const scope = file.module.moduleScope;
11264
- for (const stmt of file.root.namedChildren) {
11265
- const assignment2 = stmt?.type === "expression_statement" ? stmt.namedChildren.find((child) => child?.type === "assignment") : void 0;
11875
+ for (const stmt of children(file.root)) {
11876
+ const assignment2 = stmt?.type === "expression_statement" ? children(stmt).find((child) => child?.type === "assignment") : void 0;
11266
11877
  const left = assignment2 === void 0 || assignment2 === null ? null : field(assignment2, "left");
11267
11878
  if (left?.type !== "identifier") {
11268
11879
  continue;
@@ -11277,6 +11888,9 @@ function registerConstructorDependencies(file, declared, form, index) {
11277
11888
  if (match === null) {
11278
11889
  continue;
11279
11890
  }
11891
+ if (match.registrar.covers === "everyRoute") {
11892
+ index.recordApp(declared.pack.name, match.call, file.file);
11893
+ }
11280
11894
  const { keywordArgs } = readCallArguments(field(match.call, "arguments"));
11281
11895
  const listed = keywordArgs[form.keyword];
11282
11896
  if (listed === void 0) {
@@ -11294,7 +11908,7 @@ function coverageOf(match, pack) {
11294
11908
  return match.registrar.covers === "everyRoute" ? { kind: "everyRoute", pack: pack.name } : { kind: "ownRoutes", key: match.key };
11295
11909
  }
11296
11910
  function registerDecorated(file, declared, form, index) {
11297
- (0, import_extractor15.walkDescendants)(file.root, file.module.moduleScope, {
11911
+ (0, import_extractor17.walkDescendants)(file.root, file.module.moduleScope, children, {
11298
11912
  at: (node, scope) => {
11299
11913
  if (node.type !== "decorated_definition") {
11300
11914
  return;
@@ -11330,7 +11944,7 @@ function registerDecorated(file, declared, form, index) {
11330
11944
  });
11331
11945
  }
11332
11946
  function dependencyNamesIn(node, form) {
11333
- const calls = node.type === "list" ? node.namedChildren : [node];
11947
+ const calls = node.type === "list" ? children(node) : [node];
11334
11948
  const names = [];
11335
11949
  for (const call of calls) {
11336
11950
  const name = call === null ? null : dependencyNameOf(call, form);
@@ -11349,7 +11963,7 @@ function dependencyNameOf(node, form) {
11349
11963
  if (calleeName3 === null || !form.callees.includes(calleeName3)) {
11350
11964
  return null;
11351
11965
  }
11352
- const first = field(node, "arguments")?.namedChildren[0];
11966
+ const first = field(node, "arguments")?.namedChild(0);
11353
11967
  return first?.type === "identifier" ? first.text : null;
11354
11968
  }
11355
11969
  function decoratorDependencies(classification, form) {
@@ -11367,7 +11981,7 @@ function parameterDependencies(definitionNode, form) {
11367
11981
  if (direct !== null) {
11368
11982
  return direct;
11369
11983
  }
11370
- for (const child of node.namedChildren) {
11984
+ for (const child of children(node)) {
11371
11985
  const found = findIn(child, depth + 1);
11372
11986
  if (found !== null) {
11373
11987
  return found;
@@ -11375,10 +11989,7 @@ function parameterDependencies(definitionNode, form) {
11375
11989
  }
11376
11990
  return null;
11377
11991
  };
11378
- for (const param of field(definitionNode, "parameters")?.namedChildren ?? []) {
11379
- if (param === null) {
11380
- continue;
11381
- }
11992
+ for (const param of children(field(definitionNode, "parameters"))) {
11382
11993
  const inDefault = field(param, "value");
11383
11994
  const found = inDefault === null ? findIn(field(param, "type"), 0) : dependencyNameOf(inDefault, form);
11384
11995
  if (found !== null) {
@@ -11394,7 +12005,8 @@ function wrapperUnit(target, declared, options) {
11394
12005
  const raised = raisedResponses(body, {
11395
12006
  calls: declared.pattern.responseStatusCalls ?? [],
11396
12007
  module: file.module,
11397
- facts: options.facts
12008
+ facts: options.facts,
12009
+ constants: declared.pattern.statusCodeConstants ?? {}
11398
12010
  });
11399
12011
  const continuations = continuationStatements(node, declared.form);
11400
12012
  const terminals = bodyTerminals(body, raised, continuations);
@@ -11422,9 +12034,9 @@ function wrapperUnit(target, declared, options) {
11422
12034
  });
11423
12035
  const respond = (statement) => returnBranch(statement, {
11424
12036
  pattern: declared.pattern,
11425
- responseShape: { reading: import_extractor15.absentReading },
12037
+ responseShape: { reading: import_extractor17.absentReading },
11426
12038
  declaredStatus: {
11427
- reading: import_extractor15.absentReading,
12039
+ reading: import_extractor17.absentReading,
11428
12040
  ...declared.pattern.defaultStatusCode !== void 0 ? { libraryDefault: declared.pattern.defaultStatusCode } : {}
11429
12041
  },
11430
12042
  module: file.module,
@@ -11478,7 +12090,7 @@ function returnHandsOn(statement, form) {
11478
12090
  return false;
11479
12091
  }
11480
12092
  if (form.returnedValueResponds === true) {
11481
- const returned = statement.namedChildren[0];
12093
+ const returned = statement.namedChild(0);
11482
12094
  return returned === void 0 || returned === null || returned.type === "none";
11483
12095
  }
11484
12096
  return true;
@@ -11487,9 +12099,7 @@ function continuationStatements(node, form) {
11487
12099
  if (form.type !== "decoratedWrapper" || form.continuationParam === void 0) {
11488
12100
  return [];
11489
12101
  }
11490
- const params = (field(node, "parameters")?.namedChildren ?? []).filter(
11491
- (param2) => param2 !== null
11492
- );
12102
+ const params = children(field(node, "parameters"));
11493
12103
  const param = params[form.continuationParam];
11494
12104
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
11495
12105
  if (continuation === null) {
@@ -11512,7 +12122,7 @@ function continuationStatements(node, form) {
11512
12122
 
11513
12123
  // src/project.ts
11514
12124
  function keptPythonParses() {
11515
- return new import_extractor16.KeptParses(({ root }) => root.tree.delete());
12125
+ return new import_extractor18.KeptParses(({ root }) => root.tree.delete());
11516
12126
  }
11517
12127
  async function parsePythonAhead(files, kept) {
11518
12128
  for (const file of files) {
@@ -11601,16 +12211,16 @@ function factsForFile(options) {
11601
12211
  }
11602
12212
  async function extractPythonProject(options) {
11603
12213
  const { roots, unreadManifests } = rootsOfRun(options);
11604
- const timer = options.onTiming !== void 0 ? (0, import_extractor16.createTimer)() : (0, import_extractor16.noopTimer)();
12214
+ const timer = options.onTiming !== void 0 ? (0, import_extractor18.createTimer)() : (0, import_extractor18.noopTimer)();
11605
12215
  const cacheDir = adapterStamp.declineWhenRunFromSource(
11606
12216
  options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? import_node_path5.default.join(options.projectRoot, ".suss", "cache") : null)
11607
12217
  );
11608
- const layer = (0, import_extractor16.createCacheLayer)(cacheDir);
12218
+ const layer = (0, import_extractor18.createCacheLayer)(cacheDir);
11609
12219
  const packsDigest = `${adapterStamp.packsDigest(
11610
12220
  options.packs.map(
11611
12221
  (pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
11612
12222
  )
11613
- )}|${(0, import_extractor16.extractionConfigStamp)({
12223
+ )}|${(0, import_extractor18.extractionConfigStamp)({
11614
12224
  gapHandling: options.gapHandling,
11615
12225
  workspaceRoot: options.workspaceRoot,
11616
12226
  projectRoot: options.projectRoot,
@@ -11620,7 +12230,7 @@ async function extractPythonProject(options) {
11620
12230
  })}`;
11621
12231
  const input = {
11622
12232
  files: cacheDir === null ? [] : options.files,
11623
- adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor16.runDigest)(packsDigest, options.packs, options.files)
12233
+ adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor18.runDigest)(packsDigest, options.packs, options.files)
11624
12234
  };
11625
12235
  const lookup = await timer.timeAsync(
11626
12236
  "cache.lookup",
@@ -11682,7 +12292,7 @@ async function runPython(options, roots, cache, previous) {
11682
12292
  const db = new import_datalog2.Database();
11683
12293
  const summaries = [];
11684
12294
  const gapHandling = options.gapHandling ?? "permissive";
11685
- const tallies = (0, import_extractor8.createPackTallies)(options.packs);
12295
+ const tallies = (0, import_extractor10.createPackTallies)(options.packs);
11686
12296
  const ledger = cacheDir === null ? null : new import_resolution18.DependencyLedger(options.files);
11687
12297
  const log = ledger === null ? null : new import_resolution18.FactLog(db, ledger);
11688
12298
  if (ledger !== null) {
@@ -11790,7 +12400,8 @@ async function runPython(options, roots, cache, previous) {
11790
12400
  roots,
11791
12401
  facts: needsValues ? db : void 0,
11792
12402
  definitions,
11793
- storageFor
12403
+ storageFor,
12404
+ routers: plainRouterIndex
11794
12405
  })
11795
12406
  );
11796
12407
  const importedDefinition2 = importedDefinitionLookup(db, bound);
@@ -11937,11 +12548,11 @@ async function runPython(options, roots, cache, previous) {
11937
12548
  const assemble = (raw, boundFile) => {
11938
12549
  const summary = timer.time(
11939
12550
  "summarize",
11940
- () => (0, import_extractor16.assembleSummary)(raw, { gapHandling })
12551
+ () => (0, import_extractor18.assembleSummary)(raw, { gapHandling })
11941
12552
  );
11942
12553
  summaries.push(summary);
11943
12554
  assembledHere.add(summary);
11944
- (0, import_extractor8.tallyUnit)(tallies, raw.boundaryBinding?.recognition);
12555
+ (0, import_extractor10.tallyUnit)(tallies, raw.boundaryBinding?.recognition);
11945
12556
  const span = raw.identity.span;
11946
12557
  const key = span === void 0 ? null : `${boundFile.file}:${span.start}-${span.end}`;
11947
12558
  if (key !== null) {
@@ -11958,7 +12569,7 @@ async function runPython(options, roots, cache, previous) {
11958
12569
  const summary = structuredClone(unit.summary);
11959
12570
  summariesReused += 1;
11960
12571
  summaries.push(summary);
11961
- (0, import_extractor8.tallyUnit)(tallies, unit.recognition);
12572
+ (0, import_extractor10.tallyUnit)(tallies, unit.recognition);
11962
12573
  if (unit.seedKey !== void 0) {
11963
12574
  seedFrom(unit.seedKey, boundFile, summary);
11964
12575
  }
@@ -12016,12 +12627,12 @@ async function runPython(options, roots, cache, previous) {
12016
12627
  const placeWhatItReached = (summary, key) => {
12017
12628
  if (gapHandling !== "silent") {
12018
12629
  summary.gaps.push(
12019
- ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir9.unfollowedCallGap)
12630
+ ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir10.unfollowedCallGap)
12020
12631
  );
12021
12632
  }
12022
- (0, import_behavioral_ir9.placeCalls)(summary, reached.targetsByKey.get(key));
12023
- (0, import_behavioral_ir9.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
12024
- (0, import_behavioral_ir9.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
12633
+ (0, import_behavioral_ir10.placeCalls)(summary, reached.targetsByKey.get(key));
12634
+ (0, import_behavioral_ir10.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
12635
+ (0, import_behavioral_ir10.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
12025
12636
  };
12026
12637
  for (const [key, owners] of summariesBySeed) {
12027
12638
  for (const summary of owners) {
@@ -12038,7 +12649,7 @@ async function runPython(options, roots, cache, previous) {
12038
12649
  }
12039
12650
  }
12040
12651
  if (gapHandling !== "silent") {
12041
- (0, import_behavioral_ir9.recordParameterGaps)(
12652
+ (0, import_behavioral_ir10.recordParameterGaps)(
12042
12653
  reached.parameterCallsByKey,
12043
12654
  summariesBySeed,
12044
12655
  reached.passedPositions
@@ -12062,8 +12673,8 @@ async function runPython(options, roots, cache, previous) {
12062
12673
  }
12063
12674
  const summary = timer.time(
12064
12675
  "summarize",
12065
- () => (0, import_extractor16.assembleSummary)(
12066
- (0, import_extractor16.moduleInitStructure)({
12676
+ () => (0, import_extractor18.assembleSummary)(
12677
+ (0, import_extractor18.moduleInitStructure)({
12067
12678
  name: import_node_path5.default.basename(displayPath),
12068
12679
  file: displayPath,
12069
12680
  range: rangeOf(boundFile.root),
@@ -12072,7 +12683,7 @@ async function runPython(options, roots, cache, previous) {
12072
12683
  ...charged(
12073
12684
  boundFile.file,
12074
12685
  () => moduleLoadInvocationEffects(boundFile.root, db)
12075
- ).map(import_extractor16.effectToIR)
12686
+ ).map(import_extractor18.effectToIR)
12076
12687
  ]
12077
12688
  }),
12078
12689
  { gapHandling }
@@ -12092,23 +12703,23 @@ async function runPython(options, roots, cache, previous) {
12092
12703
  throw new ReplayFailed("");
12093
12704
  }
12094
12705
  const resolvedImports = (0, import_resolution18.importedFilesByFile)(db, displayPathOf);
12095
- (0, import_extractor16.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
12096
- (0, import_extractor16.stampModules)(summaries, declaredModules, options.workspaceRoot);
12706
+ (0, import_extractor18.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
12707
+ (0, import_extractor18.stampModules)(summaries, declaredModules, options.workspaceRoot);
12097
12708
  const idRoot = options.projectRoot ?? options.workspaceRoot;
12098
12709
  for (const summary of summaries) {
12099
12710
  const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : import_node_path5.default.resolve(options.workspaceRoot, summary.location.file);
12100
- summary.identity.id = (0, import_behavioral_ir9.summaryIdFromParts)({
12711
+ summary.identity.id = (0, import_behavioral_ir10.summaryIdFromParts)({
12101
12712
  workspace: void 0,
12102
12713
  file: idRoot === void 0 ? absoluteFile : import_node_path5.default.relative(idRoot, absoluteFile),
12103
12714
  name: summary.identity.name,
12104
12715
  exportPath: summary.identity.exportPath
12105
12716
  });
12106
12717
  }
12107
- (0, import_behavioral_ir9.disambiguateSummaryIds)(summaries);
12108
- (0, import_behavioral_ir9.linkCallsToSummaries)(summaries);
12718
+ (0, import_behavioral_ir10.disambiguateSummaryIds)(summaries);
12719
+ (0, import_behavioral_ir10.linkCallsToSummaries)(summaries);
12109
12720
  const composed = timer.time("summarize", () => {
12110
12721
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
12111
- return (0, import_extractor16.composeWrappers)(summaries, { gapHandling });
12722
+ return (0, import_extractor18.composeWrappers)(summaries, { gapHandling });
12112
12723
  });
12113
12724
  await timer.timeAsync("cache.write", async () => {
12114
12725
  if (cacheDir === null || composed.length === 0) {
@@ -12233,7 +12844,7 @@ function indexDefinitions(into, file, node) {
12233
12844
  if (isFunction(node)) {
12234
12845
  into.set(nodeId(file, node), node);
12235
12846
  }
12236
- for (const child of node.namedChildren) {
12847
+ for (const child of children(node)) {
12237
12848
  if (child !== null) {
12238
12849
  indexDefinitions(into, file, child);
12239
12850
  }
@@ -12499,7 +13110,7 @@ function importSitesIn(root) {
12499
13110
  found.push({ module: entry.module, name: entry.name, line });
12500
13111
  }
12501
13112
  }
12502
- for (const child of node.namedChildren) {
13113
+ for (const child of children(node)) {
12503
13114
  if (child !== null) {
12504
13115
  visit(child);
12505
13116
  }