@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.js CHANGED
@@ -82,10 +82,7 @@ function stringLiteralValue(node) {
82
82
  return null;
83
83
  }
84
84
  let content = "";
85
- for (const child of node.namedChildren) {
86
- if (child === null) {
87
- continue;
88
- }
85
+ for (const child of children(node)) {
89
86
  if (child.type === "string_content") {
90
87
  content += stringContentValue(child);
91
88
  continue;
@@ -97,7 +94,29 @@ function stringLiteralValue(node) {
97
94
  return content;
98
95
  }
99
96
  function children(node) {
100
- return node.namedChildren.filter((child) => child !== null);
97
+ if (node === null || node === void 0) {
98
+ return [];
99
+ }
100
+ const found = [];
101
+ const count = node.namedChildCount;
102
+ for (let index = 0; index < count; index++) {
103
+ const child = node.namedChild(index);
104
+ if (child !== null) {
105
+ found.push(child);
106
+ }
107
+ }
108
+ return found;
109
+ }
110
+ function allChildren(node) {
111
+ const found = [];
112
+ const count = node.childCount;
113
+ for (let index = 0; index < count; index++) {
114
+ const child = node.child(index);
115
+ if (child !== null) {
116
+ found.push(child);
117
+ }
118
+ }
119
+ return found;
101
120
  }
102
121
  function bodyStatements(body) {
103
122
  return children(body);
@@ -129,11 +148,59 @@ function stripDecorators(node) {
129
148
  if (definition === null) {
130
149
  throw new Error("decorated_definition with no definition field");
131
150
  }
132
- const decorators = node.namedChildren.filter(
133
- (child) => child !== null && child.type === "decorator"
151
+ const decorators = children(node).filter(
152
+ (child) => child.type === "decorator"
134
153
  );
135
154
  return { definition, decorators };
136
155
  }
156
+ var isChild = (fieldName) => (parent, child) => field(parent, fieldName)?.id === child.id;
157
+ var always = () => true;
158
+ var asCondition = isChild("condition");
159
+ var isConditionalTest = (parent, child) => parent.namedChild(1)?.id === child.id;
160
+ var VALUE_USED_UNDER = {
161
+ assignment: isChild("right"),
162
+ augmented_assignment: isChild("right"),
163
+ named_expression: isChild("value"),
164
+ attribute: isChild("object"),
165
+ subscript: always,
166
+ argument_list: always,
167
+ keyword_argument: isChild("value"),
168
+ pair: always,
169
+ list: always,
170
+ tuple: always,
171
+ set: always,
172
+ dictionary: always,
173
+ interpolation: always,
174
+ comparison_operator: always,
175
+ binary_operator: always,
176
+ unary_operator: always,
177
+ not_operator: always,
178
+ if_statement: asCondition,
179
+ elif_clause: asCondition,
180
+ while_statement: asCondition,
181
+ conditional_expression: isConditionalTest,
182
+ assert_statement: always,
183
+ for_statement: isChild("right"),
184
+ for_in_clause: isChild("right"),
185
+ with_item: always,
186
+ as_pattern: always
187
+ };
188
+ var PASSES_VALUE_ON = {
189
+ parenthesized_expression: always,
190
+ await: always,
191
+ boolean_operator: always,
192
+ conditional_expression: (parent, child) => !isConditionalTest(parent, child)
193
+ };
194
+ function resultUsedAsValue(node) {
195
+ let child = node;
196
+ let parent = node.parent;
197
+ while (parent !== null && PASSES_VALUE_ON[parent.type]?.(parent, child) === true) {
198
+ child = parent;
199
+ parent = parent.parent;
200
+ }
201
+ const used = parent === null ? void 0 : VALUE_USED_UNDER[parent.type];
202
+ return parent !== null && used !== void 0 && used(parent, child);
203
+ }
137
204
  var NodeSet = class extends IdSet {
138
205
  };
139
206
  var NodeMap = class extends IdMap {
@@ -195,7 +262,7 @@ function bindWithStatement(stmt, scope, ctx) {
195
262
  }
196
263
  }
197
264
  function asPatternsIn(stmt, found = []) {
198
- for (const child of stmt.namedChildren) {
265
+ for (const child of children(stmt)) {
199
266
  if (child === null || child.type === "block") {
200
267
  continue;
201
268
  }
@@ -224,12 +291,12 @@ function bindDeleteStatement(stmt, scope) {
224
291
  return;
225
292
  }
226
293
  if (node.type === "expression_list") {
227
- for (const child of node.namedChildren) {
294
+ for (const child of children(node)) {
228
295
  unbind(child);
229
296
  }
230
297
  }
231
298
  };
232
- for (const child of stmt.namedChildren) {
299
+ for (const child of children(stmt)) {
233
300
  unbind(child);
234
301
  }
235
302
  }
@@ -270,10 +337,7 @@ function bindImportStatement(stmt, scope) {
270
337
  function relativeImportParts(node) {
271
338
  let relativeLevel = 0;
272
339
  let module = "";
273
- for (const child of node.namedChildren) {
274
- if (child === null) {
275
- continue;
276
- }
340
+ for (const child of children(node)) {
277
341
  if (child.type === "import_prefix") {
278
342
  relativeLevel += child.text.length;
279
343
  continue;
@@ -290,8 +354,8 @@ function bindImportFromStatement(stmt, scope, ctx) {
290
354
  return;
291
355
  }
292
356
  const { module, relativeLevel } = moduleNode.type === "relative_import" ? relativeImportParts(moduleNode) : { module: moduleNode.text, relativeLevel: 0 };
293
- const hasWildcard = stmt.namedChildren.some(
294
- (child) => child !== null && child.type === "wildcard_import"
357
+ const hasWildcard = children(stmt).some(
358
+ (child) => child.type === "wildcard_import"
295
359
  );
296
360
  if (hasWildcard) {
297
361
  ctx.openImports.push(
@@ -349,10 +413,7 @@ function bindFunctionDefinition(stmt, scope, ctx) {
349
413
  }
350
414
  }
351
415
  function bindParameters(parametersNode, functionScope) {
352
- for (const param of parametersNode.namedChildren) {
353
- if (param === null) {
354
- continue;
355
- }
416
+ for (const param of children(parametersNode)) {
356
417
  const name = parameterName(param);
357
418
  if (name !== null) {
358
419
  bindName(functionScope, name, { kind: "parameter" });
@@ -369,9 +430,7 @@ function parameterName(param) {
369
430
  "list_splat_pattern",
370
431
  "dictionary_splat_pattern"
371
432
  )) {
372
- const inner = param.namedChildren.find(
373
- (child) => child !== null && child.type === "identifier"
374
- );
433
+ const inner = children(param).find((child) => child.type === "identifier");
375
434
  return inner?.text ?? null;
376
435
  }
377
436
  if (isType(param, "default_parameter", "typed_default_parameter")) {
@@ -398,8 +457,8 @@ function bindClassDefinition(stmt, scope, ctx) {
398
457
  }
399
458
  }
400
459
  function bindExpressionStatement(stmt, scope) {
401
- const assignment2 = stmt.namedChildren.find(
402
- (child) => child !== null && child.type === "assignment"
460
+ const assignment2 = children(stmt).find(
461
+ (child) => child.type === "assignment"
403
462
  );
404
463
  if (assignment2 === void 0 || assignment2 === null) {
405
464
  return;
@@ -416,15 +475,15 @@ function bindExpressionStatement(stmt, scope) {
416
475
  }
417
476
  }
418
477
  function bindGlobalStatement(stmt, scope) {
419
- for (const child of stmt.namedChildren) {
420
- if (child !== null && child.type === "identifier") {
478
+ for (const child of children(stmt)) {
479
+ if (child.type === "identifier") {
421
480
  scope.bindings.set(child.text, { kind: "global" });
422
481
  }
423
482
  }
424
483
  }
425
484
  function bindNonlocalStatement(stmt, scope) {
426
- for (const child of stmt.namedChildren) {
427
- if (child !== null && child.type === "identifier") {
485
+ for (const child of children(stmt)) {
486
+ if (child.type === "identifier") {
428
487
  scope.bindings.set(child.text, { kind: "nonlocal" });
429
488
  }
430
489
  }
@@ -573,18 +632,9 @@ function shapeFromName(name, scope, ctx) {
573
632
  if (DICT_NAMES.has(name)) {
574
633
  return { type: "dictionary", values: { type: "unknown" } };
575
634
  }
576
- const binding = resolveName(scope, name);
577
- if (binding?.kind === "classDef") {
578
- return recordShapeRef(name, binding.node, ctx);
579
- }
580
- if (binding?.kind === "import" || binding?.kind === "importFrom") {
581
- const imported = ctx.importedDefinition?.(scope, name) ?? null;
582
- if (imported?.node.type === "class_definition") {
583
- if (!ctx.scopeMaps.includes(imported.scopeFor)) {
584
- ctx.scopeMaps.push(imported.scopeFor);
585
- }
586
- return recordShapeRef(name, imported.node, ctx);
587
- }
635
+ const classNode = projectClassNamed(name, scope, ctx);
636
+ if (classNode !== null) {
637
+ return recordShapeRef(name, classNode, ctx);
588
638
  }
589
639
  const alias = aliasValueOf(name, scope, ctx);
590
640
  if (alias !== null) {
@@ -592,6 +642,23 @@ function shapeFromName(name, scope, ctx) {
592
642
  }
593
643
  return { type: "ref", name };
594
644
  }
645
+ function projectClassNamed(name, scope, ctx) {
646
+ const binding = resolveName(scope, name);
647
+ if (binding?.kind === "classDef") {
648
+ return binding.node;
649
+ }
650
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
651
+ return null;
652
+ }
653
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
654
+ if (imported?.node.type !== "class_definition") {
655
+ return null;
656
+ }
657
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
658
+ ctx.scopeMaps.push(imported.scopeFor);
659
+ }
660
+ return imported.node;
661
+ }
595
662
  function shapeFromAliasValue(name, alias, ctx) {
596
663
  if (ctx.expanding.has(alias.node.id)) {
597
664
  return { type: "ref", name };
@@ -618,8 +685,9 @@ function shapeFromSubscript(node, scope, ctx) {
618
685
  );
619
686
  }
620
687
  function annotationTarget(annotation) {
621
- if (annotation.type === "type" && annotation.namedChildren[0]) {
622
- return annotationTarget(annotation.namedChildren[0]);
688
+ const inner = annotation.type === "type" ? annotation.namedChild(0) : null;
689
+ if (inner !== null) {
690
+ return annotationTarget(inner);
623
691
  }
624
692
  if (annotation.type === "binary_operator") {
625
693
  const named = [field(annotation, "left"), field(annotation, "right")].find(
@@ -628,7 +696,7 @@ function annotationTarget(annotation) {
628
696
  return named === void 0 || named === null ? null : annotationTarget(named);
629
697
  }
630
698
  if (annotation.type === "generic_type") {
631
- const outer = annotation.namedChildren[0];
699
+ const outer = annotation.namedChild(0);
632
700
  const first = genericTypeArgs(annotation)[0];
633
701
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
634
702
  return annotationTarget(first);
@@ -645,12 +713,10 @@ function typeNameOf(annotation) {
645
713
  return target.type === "identifier" ? target.text : stringLiteralValue(target);
646
714
  }
647
715
  function genericTypeArgs(node) {
648
- const typeParameter = node.namedChildren.find(
649
- (child) => child !== null && child.type === "type_parameter"
716
+ const typeParameter = children(node).find(
717
+ (child) => child.type === "type_parameter"
650
718
  );
651
- return typeParameter?.namedChildren.filter(
652
- (child) => child !== null && child.type === "type"
653
- ) ?? [];
719
+ return children(typeParameter).filter((child) => child.type === "type");
654
720
  }
655
721
  function shapeFromGenericType(node, scope, ctx) {
656
722
  const base = node.namedChild(0);
@@ -702,22 +768,44 @@ function recordShapeRef(name, classNode, ctx) {
702
768
  return ref;
703
769
  }
704
770
  ctx.definitions.set(key, null);
771
+ ctx.definitions.set(key, {
772
+ type: "record",
773
+ properties: classFields(classNode, ctx, /* @__PURE__ */ new Set())
774
+ });
775
+ return ref;
776
+ }
777
+ function classFields(classNode, ctx, visiting) {
705
778
  const bodyNode = field(classNode, "body");
706
779
  const classScope = scopeOfNode(ctx, classNode);
707
- ctx.definitions.set(
708
- key,
709
- bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
780
+ if (bodyNode === null || classScope === void 0 || visiting.has(classNode.id)) {
781
+ return {};
782
+ }
783
+ visiting.add(classNode.id);
784
+ const inherited = baseClassNames(classNode).flatMap((name) => {
785
+ const base = projectClassNamed(name, classScope, ctx);
786
+ return base === null ? [] : [classFields(base, ctx, visiting)];
787
+ });
788
+ const own = recordShapeOf(bodyNode, classScope, ctx);
789
+ return Object.assign(
790
+ {},
791
+ ...inherited.reverse(),
792
+ own.type === "record" ? own.properties : {}
793
+ );
794
+ }
795
+ function baseClassNames(classNode) {
796
+ const superclasses = field(classNode, "superclasses");
797
+ return children(superclasses).flatMap(
798
+ (child) => child.type === "identifier" ? [child.text] : []
710
799
  );
711
- return ref;
712
800
  }
713
801
  function recordShapeOf(bodyNode, classScope, ctx) {
714
802
  const properties = {};
715
- for (const stmt of bodyNode.namedChildren) {
803
+ for (const stmt of children(bodyNode)) {
716
804
  if (stmt === null || stmt.type !== "expression_statement") {
717
805
  continue;
718
806
  }
719
- const assignment2 = stmt.namedChildren.find(
720
- (child) => child !== null && child.type === "assignment"
807
+ const assignment2 = children(stmt).find(
808
+ (child) => child.type === "assignment"
721
809
  );
722
810
  if (assignment2 === void 0 || assignment2 === null) {
723
811
  continue;
@@ -727,6 +815,9 @@ function recordShapeOf(bodyNode, classScope, ctx) {
727
815
  if (left === null || left.type !== "identifier" || typeNode === null) {
728
816
  continue;
729
817
  }
818
+ if (typeNameOf(typeNode) === "ClassVar") {
819
+ continue;
820
+ }
730
821
  properties[left.text] = annotationToShape(typeNode, classScope, ctx);
731
822
  }
732
823
  return { type: "record", properties };
@@ -743,8 +834,10 @@ import { nodeOfKey, noteKeyRead } from "@suss/resolution";
743
834
  import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
744
835
 
745
836
  // src/facts/resolve.ts
837
+ import { isTestFile } from "@suss/behavioral-ir";
746
838
  import {
747
839
  allocationSitesOf,
840
+ answersFromTestsLeftOut,
748
841
  askResolution,
749
842
  askResolutionUnder,
750
843
  askSources,
@@ -802,7 +895,13 @@ function constructionSites(db, classKey) {
802
895
  }
803
896
  function writtenValueOf(db, key) {
804
897
  resolveWritten(db, [key]);
805
- return sharedWrittenValueOf(db, key, (keys) => resolveWritten(db, keys));
898
+ return sharedWrittenValueOf(
899
+ db,
900
+ key,
901
+ (keys) => resolveWritten(db, keys),
902
+ void 0,
903
+ answersFromTestsLeftOut(key, isTestFile)
904
+ );
806
905
  }
807
906
  function writtenValuesOf(db, key) {
808
907
  resolveWritten(db, [key]);
@@ -1430,10 +1529,10 @@ function emitExpressionFacts(emitter, node) {
1430
1529
  });
1431
1530
  }
1432
1531
  function yieldedValue(node) {
1433
- if (node.children.some((child) => child?.type === "from")) {
1532
+ if (allChildren(node).some((child) => child.type === "from")) {
1434
1533
  return null;
1435
1534
  }
1436
- return node.namedChildren.find((child) => child?.type !== "comment") ?? null;
1535
+ return children(node).find((child) => child?.type !== "comment") ?? null;
1437
1536
  }
1438
1537
  function emitFunctionFacts(emitter, fn, classKey) {
1439
1538
  const funcKey = nodeId(emitter.filePath, fn);
@@ -1505,7 +1604,7 @@ function emitFunctionFacts(emitter, fn, classKey) {
1505
1604
  const stores = /* @__PURE__ */ new Map();
1506
1605
  const visit = (child, type) => {
1507
1606
  if (type === "return_statement") {
1508
- const returned = child.namedChildren[0];
1607
+ const returned = child.namedChild(0);
1509
1608
  if (returned != null) {
1510
1609
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1511
1610
  statesReturn = true;
@@ -2543,9 +2642,7 @@ function stringExpression(node) {
2543
2642
  }
2544
2643
  function comparisonExpression(node) {
2545
2644
  const operands = children(node);
2546
- const operators = node.children.filter(
2547
- (child) => child !== null && !child.isNamed
2548
- );
2645
+ const operators = allChildren(node).filter((child) => !child.isNamed);
2549
2646
  const operator = operators[0];
2550
2647
  if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2551
2648
  return { kind: "opaque" };
@@ -2639,6 +2736,7 @@ var STATEMENT_TYPES = {
2639
2736
  kind: "return",
2640
2737
  value: children(node)[0] ?? null
2641
2738
  }),
2739
+ raise_statement: (node) => caughtInSameFunction(node) ? OPAQUE : { kind: "throw" },
2642
2740
  try_statement: (node) => ({
2643
2741
  kind: "block",
2644
2742
  body: [
@@ -2727,6 +2825,19 @@ function blockStatements(block) {
2727
2825
  function isRoot(node) {
2728
2826
  return ROOT_TYPES.has(node.type);
2729
2827
  }
2828
+ var EXCEPT_CLAUSE_TYPES = /* @__PURE__ */ new Set(["except_clause", "except_group_clause"]);
2829
+ function caughtInSameFunction(node) {
2830
+ let child = node;
2831
+ let parent = node.parent;
2832
+ while (parent !== null && !isRoot(parent)) {
2833
+ if (parent.type === "try_statement" && field(parent, "body")?.id === child.id && children(parent).some((clause) => EXCEPT_CLAUSE_TYPES.has(clause.type))) {
2834
+ return true;
2835
+ }
2836
+ child = parent;
2837
+ parent = parent.parent;
2838
+ }
2839
+ return false;
2840
+ }
2730
2841
  function isPathStatement(node, parent) {
2731
2842
  return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2732
2843
  }
@@ -3139,6 +3250,10 @@ function writtenNodeOf(node, db) {
3139
3250
  const bound = db === void 0 ? void 0 : projects.get(db);
3140
3251
  return bound?.context.writtenTo(node) ?? null;
3141
3252
  }
3253
+ function calledDefinition(call, db) {
3254
+ const bound = db === void 0 ? void 0 : projects.get(db);
3255
+ return bound?.context.callable(call) ?? null;
3256
+ }
3142
3257
  function resolutionKeyOf(node, db) {
3143
3258
  const bound = db === void 0 ? void 0 : projects.get(db);
3144
3259
  return bound?.keyOf(node) ?? null;
@@ -3361,7 +3476,7 @@ function argShapeOf(node) {
3361
3476
  if (node.type === "list") {
3362
3477
  return {
3363
3478
  kind: "list",
3364
- items: node.namedChildren.filter((child) => child !== null).map(readArg)
3479
+ items: children(node).map(readArg)
3365
3480
  };
3366
3481
  }
3367
3482
  return { kind: "other" };
@@ -3372,10 +3487,7 @@ function readCallArguments(argumentList) {
3372
3487
  if (argumentList === null) {
3373
3488
  return { args, keywordArgs };
3374
3489
  }
3375
- for (const child of argumentList.namedChildren) {
3376
- if (child === null) {
3377
- continue;
3378
- }
3490
+ for (const child of children(argumentList)) {
3379
3491
  if (child.type === "keyword_argument") {
3380
3492
  const nameNode = field(child, "name");
3381
3493
  const valueNode = field(child, "value");
@@ -3501,10 +3613,10 @@ function unwrapDecorator(decoratorNode, facts) {
3501
3613
  }
3502
3614
  function passesParametersThrough(def, inner) {
3503
3615
  const params = field(def, "parameters");
3504
- const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
3616
+ const names = children(params).filter((child) => child.type === "identifier").map((child) => child.text);
3505
3617
  const argumentList = field(inner, "arguments");
3506
- const positional = (argumentList?.namedChildren ?? []).filter(
3507
- (child) => child !== null && child.type !== "keyword_argument"
3618
+ const positional = children(argumentList).filter(
3619
+ (child) => child.type !== "keyword_argument"
3508
3620
  );
3509
3621
  return positional.length === names.length && positional.every(
3510
3622
  (argument, at) => argument.type === "identifier" && argument.text === names[at]
@@ -3527,8 +3639,14 @@ import {
3527
3639
  import { constantOf as constantOf5, literalOf as literalOf6, pathOf as pathOf2 } from "@suss/values";
3528
3640
 
3529
3641
  // src/clientCalls.ts
3530
- import { hasNameHole, restBinding } from "@suss/behavioral-ir";
3531
- import { pathOf } from "@suss/values";
3642
+ import {
3643
+ hasNameHole,
3644
+ restBinding,
3645
+ statusMembersOf,
3646
+ testsStatus
3647
+ } from "@suss/behavioral-ir";
3648
+ import { redirectDeliveryWhenSet } from "@suss/extractor";
3649
+ import { hostOf, pathOf, scalarOf } from "@suss/values";
3532
3650
 
3533
3651
  // src/paths/bodyBranches.ts
3534
3652
  import { enumerateOrDegrade as enumerateOrDegrade2, guardsHoldOn } from "@suss/extractor";
@@ -3549,7 +3667,7 @@ var COMPARISONS = {
3549
3667
  "<=": "lte"
3550
3668
  };
3551
3669
  function operatorText(node) {
3552
- return node.children.filter((child) => child !== null && !child.isNamed).map((child) => child.text).join(" ");
3670
+ return allChildren(node).filter((child) => !child.isNamed).map((child) => child.text).join(" ");
3553
3671
  }
3554
3672
  function valueRefOf(node, facts, inputs) {
3555
3673
  const value = evaluatedValue(node, facts);
@@ -3601,7 +3719,7 @@ function nullCheckOf(subject, negated2, facts, inputs) {
3601
3719
  };
3602
3720
  }
3603
3721
  function comparisonOf(node, facts, inputs) {
3604
- const [left, right] = node.namedChildren.filter(
3722
+ const [left, right] = children(node).filter(
3605
3723
  (child) => child !== null
3606
3724
  );
3607
3725
  if (left === void 0 || right === void 0) {
@@ -3630,14 +3748,14 @@ function comparisonOf(node, facts, inputs) {
3630
3748
  }
3631
3749
  function predicateOf(node, facts, inputs) {
3632
3750
  if (node.type === "parenthesized_expression") {
3633
- const inner = node.namedChildren[0];
3751
+ const inner = node.namedChild(0);
3634
3752
  return inner == null ? opaqueOf(node) : predicateOf(inner, facts, inputs);
3635
3753
  }
3636
3754
  if (node.type === "comparison_operator") {
3637
3755
  return comparisonOf(node, facts, inputs);
3638
3756
  }
3639
3757
  if (node.type === "not_operator") {
3640
- const operand2 = field(node, "argument") ?? node.namedChildren[0];
3758
+ const operand2 = field(node, "argument") ?? node.namedChild(0);
3641
3759
  return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2, facts, inputs) };
3642
3760
  }
3643
3761
  if (node.type === "identifier" || node.type === "attribute" || inputs?.has(node.id) === true) {
@@ -3652,11 +3770,11 @@ function predicateOf(node, facts, inputs) {
3652
3770
  var SUBJECT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "subscript"]);
3653
3771
  function operandsOf(node) {
3654
3772
  if (node.type === "parenthesized_expression" || node.type === "not_operator") {
3655
- const inner = field(node, "argument") ?? node.namedChildren[0];
3773
+ const inner = field(node, "argument") ?? node.namedChild(0);
3656
3774
  return inner == null ? [] : operandsOf(inner);
3657
3775
  }
3658
3776
  if (node.type === "comparison_operator") {
3659
- return node.namedChildren.filter(
3777
+ return children(node).filter(
3660
3778
  (child) => child !== null && SUBJECT_TYPES.has(child.type)
3661
3779
  );
3662
3780
  }
@@ -3682,7 +3800,7 @@ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lam
3682
3800
  function exitKindOf(node, thrown) {
3683
3801
  let sawReturn = false;
3684
3802
  const raises = (current) => {
3685
- for (const child of current.namedChildren) {
3803
+ for (const child of children(current)) {
3686
3804
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
3687
3805
  continue;
3688
3806
  }
@@ -3710,12 +3828,10 @@ function blockStatements2(block) {
3710
3828
  if (block === null) {
3711
3829
  return [];
3712
3830
  }
3713
- return block.namedChildren.filter((child) => child !== null);
3831
+ return children(block);
3714
3832
  }
3715
3833
  function childBlock(node) {
3716
- return node.namedChildren.find(
3717
- (child) => child !== null && child.type === "block"
3718
- ) ?? null;
3834
+ return children(node).find((child) => child.type === "block") ?? null;
3719
3835
  }
3720
3836
  var Lowerer = class {
3721
3837
  // tree-sitter hands back a fresh wrapper object each time a child is
@@ -3764,7 +3880,7 @@ var Lowerer = class {
3764
3880
  return [nested];
3765
3881
  }
3766
3882
  lowerIf(node) {
3767
- const clauses = node.namedChildren.filter(
3883
+ const clauses = children(node).filter(
3768
3884
  (child) => child !== null && (child.type === "elif_clause" || child.type === "else_clause")
3769
3885
  );
3770
3886
  return {
@@ -3776,10 +3892,10 @@ var Lowerer = class {
3776
3892
  };
3777
3893
  }
3778
3894
  lowerTry(node) {
3779
- const excepts = node.namedChildren.filter(
3895
+ const excepts = children(node).filter(
3780
3896
  (child) => child !== null && (child.type === "except_clause" || child.type === "except_group_clause")
3781
3897
  );
3782
- const finallyClause = node.namedChildren.find(
3898
+ const finallyClause = children(node).find(
3783
3899
  (child) => child !== null && child.type === "finally_clause"
3784
3900
  ) ?? null;
3785
3901
  return {
@@ -3795,7 +3911,7 @@ var Lowerer = class {
3795
3911
  (child) => child.type === "case_clause"
3796
3912
  );
3797
3913
  const groups = cases.map((clause) => {
3798
- const pattern = clause.namedChildren.find(
3914
+ const pattern = children(clause).find(
3799
3915
  (child) => child !== null && child.type === "case_pattern"
3800
3916
  ) ?? null;
3801
3917
  return {
@@ -3876,7 +3992,7 @@ function literalArgOf(node, facts) {
3876
3992
  return null;
3877
3993
  }
3878
3994
  function bodyCalls(node, found = []) {
3879
- for (const child of node.namedChildren) {
3995
+ for (const child of children(node)) {
3880
3996
  if (child === null || NESTED_DEFINITION_TYPES.has(child.type)) {
3881
3997
  continue;
3882
3998
  }
@@ -3888,7 +4004,7 @@ function bodyCalls(node, found = []) {
3888
4004
  return found;
3889
4005
  }
3890
4006
  function moduleLoadCalls(node, found = []) {
3891
- for (const child of node.namedChildren) {
4007
+ for (const child of children(node)) {
3892
4008
  if (child === null || !runsAtModuleLoad(child)) {
3893
4009
  continue;
3894
4010
  }
@@ -3928,7 +4044,7 @@ function argumentNodes(call) {
3928
4044
  if (args === null) {
3929
4045
  return [];
3930
4046
  }
3931
- return args.namedChildren.filter((child) => child !== null).map(
4047
+ return children(args).map(
3932
4048
  (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3933
4049
  );
3934
4050
  }
@@ -3939,6 +4055,9 @@ function calleeText(call) {
3939
4055
  return field(call, "function")?.text ?? call.text;
3940
4056
  }
3941
4057
  function invocationEffects(definitionNode, facts) {
4058
+ return invocationSites(definitionNode, facts).map((site) => site.effect);
4059
+ }
4060
+ function invocationSites(definitionNode, facts) {
3942
4061
  const body = field(definitionNode, "body");
3943
4062
  if (body === null) {
3944
4063
  return [];
@@ -3946,14 +4065,18 @@ function invocationEffects(definitionNode, facts) {
3946
4065
  return invocationEffectsIn(body, bodyCalls(body), facts);
3947
4066
  }
3948
4067
  function moduleLoadInvocationEffects(moduleNode, facts) {
3949
- return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
4068
+ return invocationEffectsIn(
4069
+ moduleNode,
4070
+ moduleLoadCalls(moduleNode),
4071
+ facts
4072
+ ).map((site) => site.effect);
3950
4073
  }
3951
4074
  var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3952
4075
  function askedNodesUnder(node, found = []) {
3953
4076
  if (ASKED_ABOUT_TYPES.has(node.type)) {
3954
4077
  found.push(node);
3955
4078
  }
3956
- for (const child of node.namedChildren) {
4079
+ for (const child of children(node)) {
3957
4080
  if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3958
4081
  askedNodesUnder(child, found);
3959
4082
  }
@@ -4027,11 +4150,14 @@ function invocationEffectsIn(body, written, facts) {
4027
4150
  (condition) => predicateOf(condition, facts, inputs)
4028
4151
  );
4029
4152
  return {
4030
- type: "invocation",
4031
- callee: calleeText(call),
4032
- args: argsOf(call, facts),
4033
- async: false,
4034
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4153
+ call,
4154
+ effect: {
4155
+ type: "invocation",
4156
+ callee: calleeText(call),
4157
+ args: argsOf(call, facts),
4158
+ async: false,
4159
+ ...conditions.length > 0 ? { preconditions: conditions } : {}
4160
+ }
4035
4161
  };
4036
4162
  });
4037
4163
  }
@@ -4066,7 +4192,7 @@ function statedBy(statement, name) {
4066
4192
  }
4067
4193
  if (statement.type === "as_pattern") {
4068
4194
  const alias = field(statement, "alias");
4069
- const value = statement.namedChildren[0] ?? null;
4195
+ const value = statement.namedChild(0) ?? null;
4070
4196
  const callee = value?.type === "call" ? field(value, "function") : null;
4071
4197
  return alias?.text === name ? callee : null;
4072
4198
  }
@@ -4262,7 +4388,7 @@ function importedFunctionMatch(call, options) {
4262
4388
  return null;
4263
4389
  }
4264
4390
  const args = field(call, "arguments");
4265
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
4391
+ const first = children(args)[0] ?? null;
4266
4392
  const statement = first === null ? null : writtenStatement(first, void 0, options.facts);
4267
4393
  if (statement === null) {
4268
4394
  return null;
@@ -4626,7 +4752,7 @@ function columnComparisons(node) {
4626
4752
  const args = field(node, "arguments");
4627
4753
  return (args === null ? [] : children(args)).flatMap(columnComparisons);
4628
4754
  }
4629
- const operator = node.children.find((child) => !child?.isNamed);
4755
+ const operator = allChildren(node).find((child) => !child.isNamed);
4630
4756
  const [left, right, ...rest] = children(node);
4631
4757
  if (node.type !== "comparison_operator" || operator?.type !== "==" || left === void 0 || right === void 0 || rest.length > 0) {
4632
4758
  return [];
@@ -4999,10 +5125,7 @@ function literalAt(key, facts) {
4999
5125
 
5000
5126
  // src/paths/bodyBranches.ts
5001
5127
  function returnStatements(node, found = []) {
5002
- for (const child of node?.namedChildren ?? []) {
5003
- if (child === null) {
5004
- continue;
5005
- }
5128
+ for (const child of children(node)) {
5006
5129
  if (child.type === "function_definition" || child.type === "lambda") {
5007
5130
  continue;
5008
5131
  }
@@ -5059,7 +5182,8 @@ function enumerateBodyBranches(options) {
5059
5182
  const enumerated = enumerateOrDegrade2(
5060
5183
  {
5061
5184
  statements: lowered.statements,
5062
- terminalsByStmt: lowered.terminalsByStmt
5185
+ terminalsByStmt: lowered.terminalsByStmt,
5186
+ ...options.keepsArms === void 0 ? {} : { keepsArms: options.keepsArms }
5063
5187
  },
5064
5188
  statements
5065
5189
  );
@@ -5096,6 +5220,236 @@ function enumerateBodyBranches(options) {
5096
5220
  return branches;
5097
5221
  }
5098
5222
 
5223
+ // src/paths/raisedResponses.ts
5224
+ import { constantOf as constantOf4 } from "@suss/values";
5225
+ function statusNumberOf(node, options) {
5226
+ const value = constantOf4(evaluatedValue(node, options.facts));
5227
+ if (typeof value === "number") {
5228
+ return value;
5229
+ }
5230
+ const origin = originOf(node, options.module);
5231
+ return origin === null ? null : options.constants?.[`${origin.module}.${origin.name}`] ?? null;
5232
+ }
5233
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
5234
+ function raisedResponses(body, options) {
5235
+ if (options.calls.length === 0) {
5236
+ return [];
5237
+ }
5238
+ return raisesIn(body, options);
5239
+ }
5240
+ function raisesIn(body, options) {
5241
+ if (body === null) {
5242
+ return [];
5243
+ }
5244
+ const found = [];
5245
+ const walk = (node) => {
5246
+ for (const child of children(node)) {
5247
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
5248
+ continue;
5249
+ }
5250
+ const response = raisedResponseOf(child, options);
5251
+ if (response !== null) {
5252
+ found.push(response);
5253
+ }
5254
+ walk(child);
5255
+ }
5256
+ };
5257
+ walk(body);
5258
+ return found;
5259
+ }
5260
+ var ENDING_EXPRESSION = {
5261
+ raise_statement: (statement) => statement.namedChild(0) ?? null,
5262
+ expression_statement: (statement) => {
5263
+ const only = statement.namedChild(0);
5264
+ return only?.type === "call" ? only : null;
5265
+ }
5266
+ };
5267
+ function raisedResponseOf(statement, options) {
5268
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
5269
+ if (expression === null) {
5270
+ return null;
5271
+ }
5272
+ const thrownByCall = statement.type !== "raise_statement";
5273
+ const prebuilt = thrownByCall ? null : prebuiltCallOf(expression, options);
5274
+ if (prebuilt !== null) {
5275
+ return {
5276
+ statement,
5277
+ thrownByCall,
5278
+ terminal: terminalOf(
5279
+ statement,
5280
+ calleeOf(prebuilt.call, prebuilt.call),
5281
+ prebuilt.call,
5282
+ prebuilt.declared,
5283
+ prebuilt.options
5284
+ )
5285
+ };
5286
+ }
5287
+ const call = expression.type === "call" ? expression : null;
5288
+ const callee = calleeOf(expression, call);
5289
+ const declared = declaredCallFor(callee, options);
5290
+ if (declared === void 0 && thrownByCall) {
5291
+ return null;
5292
+ }
5293
+ return {
5294
+ statement,
5295
+ thrownByCall,
5296
+ terminal: terminalOf(statement, callee, call, declared, options)
5297
+ };
5298
+ }
5299
+ function prebuiltCallOf(expression, options) {
5300
+ if (expression.type !== "identifier" && expression.type !== "attribute") {
5301
+ return null;
5302
+ }
5303
+ const written = writtenNodeOf(expression, options.facts);
5304
+ if (written === null || written.type !== "call") {
5305
+ return null;
5306
+ }
5307
+ const inItsFile = { ...options, module: moduleOf(written) };
5308
+ const declared = declaredCallFor(calleeOf(written, written), inItsFile);
5309
+ return declared === void 0 ? null : { call: written, declared, options: inItsFile };
5310
+ }
5311
+ function returnedResponseStatus(statement, options) {
5312
+ const returned = statement.namedChild(0);
5313
+ if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
5314
+ return null;
5315
+ }
5316
+ const callee = field(returned, "function");
5317
+ if (callee === null) {
5318
+ return null;
5319
+ }
5320
+ const declared = declaredCallFor(callee, options);
5321
+ return declared === void 0 ? null : statusOf(declared, returned, options);
5322
+ }
5323
+ function calleeOf(expression, call) {
5324
+ return call === null ? expression : field(call, "function") ?? expression;
5325
+ }
5326
+ function declaredCallFor(callee, options) {
5327
+ const origin = originOf(callee, options.module);
5328
+ if (origin === null) {
5329
+ return void 0;
5330
+ }
5331
+ const qualified = `${origin.module}.${origin.name}`;
5332
+ return options.calls.find((call) => call.callee === qualified);
5333
+ }
5334
+ function terminalOf(statement, callee, call, declared, options) {
5335
+ return {
5336
+ kind: "throw",
5337
+ statusCode: declared === void 0 ? null : statusOf(declared, call, options),
5338
+ body: null,
5339
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
5340
+ message: call === null ? null : writtenMessageOf(call),
5341
+ ...declared === void 0 ? {} : { producesResponse: true },
5342
+ component: null,
5343
+ renderTree: null,
5344
+ delegateTarget: null,
5345
+ emitEvent: null,
5346
+ location: rangeOf(statement)
5347
+ };
5348
+ }
5349
+ function lastSegmentOf(dotted) {
5350
+ const segment = dotted.split(".").pop();
5351
+ return segment === void 0 || segment === "" ? null : segment;
5352
+ }
5353
+ function statusOf(declared, call, options) {
5354
+ const written = call === null ? null : statusArgumentOf(declared, call);
5355
+ if (written === null) {
5356
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
5357
+ }
5358
+ const value = statusNumberOf(written, options);
5359
+ return value === null ? { type: "dynamic", sourceText: written.text } : { type: "literal", value };
5360
+ }
5361
+ function argumentsOf2(call) {
5362
+ return children(field(call, "arguments"));
5363
+ }
5364
+ function statusArgumentOf(declared, call) {
5365
+ const args = argumentsOf2(call);
5366
+ const keyed = args.find(
5367
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
5368
+ );
5369
+ if (keyed !== void 0) {
5370
+ return field(keyed, "value");
5371
+ }
5372
+ if (declared.statusArgument === void 0) {
5373
+ return null;
5374
+ }
5375
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
5376
+ return positional[declared.statusArgument] ?? null;
5377
+ }
5378
+ function writtenMessageOf(call) {
5379
+ for (const arg of argumentsOf2(call)) {
5380
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
5381
+ const literal = value === null ? null : stringLiteralValue(value);
5382
+ if (literal !== null) {
5383
+ return literal;
5384
+ }
5385
+ }
5386
+ return null;
5387
+ }
5388
+
5389
+ // src/responseHandOff.ts
5390
+ import { helperPathsOnVariables, splitAtHandOffs } from "@suss/extractor";
5391
+ function throughResponseHelpers(branches, sites, options) {
5392
+ const handOffs = [];
5393
+ for (const { call, effect } of sites) {
5394
+ const helper = helperBranchesAt(call, options);
5395
+ if (helper !== null) {
5396
+ handOffs.push({
5397
+ helper,
5398
+ preconditions: effect.preconditions,
5399
+ line: rangeOf(call).start,
5400
+ at: rangeOf(call)
5401
+ });
5402
+ }
5403
+ }
5404
+ return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => splitAtHandOffs(branch, handOffs));
5405
+ }
5406
+ function helperBranchesAt(call, options) {
5407
+ const handed = callArguments(call).filter(
5408
+ (argument) => argument.node.type === "identifier" && refersToResponse(argument.node, options)
5409
+ );
5410
+ if (handed.length === 0) {
5411
+ return null;
5412
+ }
5413
+ const helper = calledDefinition(call, options.facts);
5414
+ if (helper === null || helper.type !== "function_definition") {
5415
+ return null;
5416
+ }
5417
+ const passed = /* @__PURE__ */ new Map();
5418
+ const parameters = parameterShapes(helper, isMethod(helper) ? 1 : 0);
5419
+ for (const argument of handed) {
5420
+ const parameter = parameters.find(
5421
+ (one) => argument.kind === "positional" ? one.position === argument.position : one.name === argument.name
5422
+ );
5423
+ if (parameter !== void 0) {
5424
+ passed.set(parameter.name, argument.node.text);
5425
+ }
5426
+ }
5427
+ return passed.size === 0 ? null : helperPathsOnVariables(options.readHelper(helper), passed);
5428
+ }
5429
+ function refersToResponse(name, options) {
5430
+ const written = awaited(writtenNodeOf(name, options.facts));
5431
+ if (written === null) {
5432
+ return false;
5433
+ }
5434
+ if (sameNode(written, options.request)) {
5435
+ return true;
5436
+ }
5437
+ const callee = written.type === "call" ? field(written, "function") : null;
5438
+ const method = callee === null ? null : field(callee, "attribute");
5439
+ const receiver = callee === null ? null : field(callee, "object");
5440
+ if (method === null || receiver?.type !== "identifier" || !options.bodyMethods.includes(method.text)) {
5441
+ return false;
5442
+ }
5443
+ const parsedFrom = awaited(writtenNodeOf(receiver, options.facts));
5444
+ return parsedFrom !== null && sameNode(parsedFrom, options.request);
5445
+ }
5446
+ function awaited(node) {
5447
+ return node?.type === "await" ? node.namedChild(0) ?? null : node;
5448
+ }
5449
+ function sameNode(a, b) {
5450
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.tree === b.tree;
5451
+ }
5452
+
5099
5453
  // src/clientCalls.ts
5100
5454
  function clientCallUnits(root, module, pack, pattern, options) {
5101
5455
  const units = [];
@@ -5155,7 +5509,7 @@ function requestCalls(call, pattern, module, options) {
5155
5509
  if (stated === null) {
5156
5510
  continue;
5157
5511
  }
5158
- const key = `${stated.method} ${stated.path}`;
5512
+ const key = `${stated.method} ${stated.host ?? ""} ${stated.path}`;
5159
5513
  if (!byBoundary.has(key)) {
5160
5514
  byBoundary.set(key, stated);
5161
5515
  }
@@ -5166,16 +5520,33 @@ function requestCalls(call, pattern, module, options) {
5166
5520
  return plain === null ? [] : [plain];
5167
5521
  }
5168
5522
  function requestCall(call, attribute, pattern, options, site) {
5523
+ const stated = methodAndPath(call, attribute, pattern, options, site);
5524
+ if (stated === null) {
5525
+ return null;
5526
+ }
5527
+ const redirectDelivery = redirectDeliveryAt(call, pattern, options, site);
5528
+ return redirectDelivery === void 0 ? stated : { ...stated, redirectDelivery };
5529
+ }
5530
+ function redirectDeliveryAt(call, pattern, options, site) {
5531
+ const option = pattern.response?.redirectOption;
5532
+ const argument = option === void 0 ? null : argumentAt(call, null, option.name);
5533
+ if (option === void 0 || argument === null) {
5534
+ return void 0;
5535
+ }
5536
+ const value = scalarOf(evaluatedValue(argument, options.facts, site));
5537
+ return value === null ? void 0 : redirectDeliveryWhenSet(option, value);
5538
+ }
5539
+ function methodAndPath(call, attribute, pattern, options, site) {
5169
5540
  const verb = pattern.verbAttributeNames[attribute];
5170
5541
  if (verb !== void 0) {
5171
- const path9 = urlAt(
5542
+ const target2 = urlAt(
5172
5543
  call,
5173
5544
  pattern.url.position,
5174
5545
  pattern.url.keyword,
5175
5546
  options,
5176
5547
  site
5177
5548
  );
5178
- return path9 === null ? null : { method: verb, path: path9, range: rangeOf(call) };
5549
+ return target2 === null ? null : { method: verb, ...target2, range: rangeOf(call), node: call };
5179
5550
  }
5180
5551
  const methodCall = pattern.methodCall;
5181
5552
  if (methodCall === void 0 || methodCall.attribute !== attribute) {
@@ -5190,14 +5561,19 @@ function requestCall(call, attribute, pattern, options, site) {
5190
5561
  if (method === null) {
5191
5562
  return null;
5192
5563
  }
5193
- const path8 = urlAt(
5564
+ const target = urlAt(
5194
5565
  call,
5195
5566
  methodCall.urlPosition,
5196
5567
  pattern.url.keyword,
5197
5568
  options,
5198
5569
  site
5199
5570
  );
5200
- return path8 === null ? null : { method: method.toUpperCase(), path: path8, range: rangeOf(call) };
5571
+ return target === null ? null : {
5572
+ method: method.toUpperCase(),
5573
+ ...target,
5574
+ range: rangeOf(call),
5575
+ node: call
5576
+ };
5201
5577
  }
5202
5578
  function calledAttribute(callee, pattern, module, facts) {
5203
5579
  const origin = originOf(callee, module);
@@ -5230,7 +5606,9 @@ function urlAt(call, position, keyword, options, site) {
5230
5606
  if (argument === null) {
5231
5607
  return null;
5232
5608
  }
5233
- return pathOf(evaluatedValue(argument, options.facts, site)) ?? null;
5609
+ const value = evaluatedValue(argument, options.facts, site);
5610
+ const path8 = pathOf(value);
5611
+ return path8 === void 0 ? null : { path: path8, host: hostOf(value) };
5234
5612
  }
5235
5613
  function argumentAt(call, position, keyword) {
5236
5614
  const args = field(call, "arguments");
@@ -5248,25 +5626,33 @@ function argumentAt(call, position, keyword) {
5248
5626
  }
5249
5627
  positional.push(child);
5250
5628
  }
5251
- return positional[position] ?? null;
5629
+ return position === null ? null : positional[position] ?? null;
5252
5630
  }
5253
- function callerBranches(definition, range, facts) {
5631
+ function callerBranches(definition, reading) {
5632
+ const range = rangeOf(definition);
5254
5633
  const body = field(definition, "body");
5255
- const effects = invocationEffects(definition, facts);
5256
- const terminals = bodyTerminals(body, []);
5257
- if (body === null || terminals.length === 0) {
5634
+ const sites = invocationSites(definition, reading.facts);
5635
+ const effects = sites.map((site) => site.effect);
5636
+ if (body === null) {
5258
5637
  return [handsBack(range, effects)];
5259
5638
  }
5639
+ const raised = raisesIn(body, {
5640
+ calls: [],
5641
+ module: moduleOf(definition),
5642
+ facts: reading.facts
5643
+ });
5260
5644
  const branches = enumerateBodyBranches({
5261
5645
  body,
5262
- terminals,
5263
- raised: [],
5646
+ terminals: bodyTerminals(body, raised),
5647
+ raised,
5264
5648
  effects,
5265
- branchOf: (found) => found.type === "raise" ? { terminal: found.terminal, location: rangeOf(found.statement) } : handsBackAt(rangeOf(found.statement)),
5649
+ branchOf: (found2) => found2.type === "raise" ? { terminal: found2.terminal, location: rangeOf(found2.statement) } : handsBackAt(rangeOf(found2.statement)),
5266
5650
  fallthrough: handsBackAt(range),
5267
- facts
5651
+ facts: reading.facts,
5652
+ keepsArms: (condition) => testsStatus(predicateOf(condition, reading.facts), reading.statusMembers)
5268
5653
  });
5269
- return branches.length === 0 ? [handsBack(range, effects)] : branches;
5654
+ const found = branches.length === 0 ? [handsBack(range, effects)] : branches;
5655
+ return reading.handOff === void 0 ? found : throughResponseHelpers(found, sites, reading.handOff);
5270
5656
  }
5271
5657
  function handsBackAt(range) {
5272
5658
  return {
@@ -5302,11 +5688,24 @@ function responseAccessors(pattern) {
5302
5688
  ...response.body === void 0 ? {} : { bodyAccessors: response.body },
5303
5689
  ...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
5304
5690
  ...response.success === void 0 ? {} : { successAccessors: response.success },
5305
- ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
5691
+ ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery },
5692
+ ...response.redirectDelivery === void 0 ? {} : { redirectDelivery: response.redirectDelivery }
5306
5693
  };
5307
5694
  }
5308
5695
  function clientUnit(definition, name, request, pattern, pack, options) {
5309
5696
  const range = rangeOf(definition);
5697
+ const accessors = responseAccessors(pattern);
5698
+ const statusMembers = statusMembersOf(accessors);
5699
+ const reading = {
5700
+ facts: options.facts,
5701
+ statusMembers,
5702
+ handOff: {
5703
+ request: request.node,
5704
+ bodyMethods: accessors.bodyAccessors ?? [],
5705
+ facts: options.facts,
5706
+ readHelper: (helper) => callerBranches(helper, { facts: options.facts, statusMembers })
5707
+ }
5708
+ };
5310
5709
  return {
5311
5710
  identity: {
5312
5711
  name,
@@ -5322,11 +5721,13 @@ function clientUnit(definition, name, request, pattern, pack, options) {
5322
5721
  transport: pack.protocol,
5323
5722
  method: request.method,
5324
5723
  path: request.path,
5724
+ host: request.host,
5325
5725
  recognition: pack.name
5326
5726
  }),
5327
5727
  parameters: [],
5328
- branches: callerBranches(definition, range, options.facts),
5329
- ...responseAccessors(pattern),
5728
+ branches: callerBranches(definition, reading),
5729
+ ...accessors,
5730
+ ...request.redirectDelivery === void 0 ? {} : { redirectDelivery: request.redirectDelivery },
5330
5731
  bodyContent: "statements",
5331
5732
  dependencyCalls: [],
5332
5733
  declaredContract: null
@@ -5549,7 +5950,7 @@ function isTestedAt(parent, child, subject, seen) {
5549
5950
  }
5550
5951
  function conditionOf(node) {
5551
5952
  if (node.type === "conditional_expression") {
5552
- return node.namedChildren[1] ?? null;
5953
+ return node.namedChild(1) ?? null;
5553
5954
  }
5554
5955
  return CONDITIONED_TYPES.has(node.type) ? field(node, "condition") : null;
5555
5956
  }
@@ -5572,7 +5973,7 @@ function isPresentAt(node, subject) {
5572
5973
  }
5573
5974
  function isBranchWherePresent(parent, child, subject) {
5574
5975
  if (parent.type === "conditional_expression") {
5575
- const [whenTrue, test2, whenFalse] = parent.namedChildren;
5976
+ const [whenTrue, test2, whenFalse] = children(parent);
5576
5977
  return test2 !== void 0 && test2 !== null && (whenTrue?.id === child.id && isPresentWhen(test2, true, subject) || whenFalse?.id === child.id && isPresentWhen(test2, false, subject));
5577
5978
  }
5578
5979
  if (parent.type === "boolean_operator") {
@@ -5610,7 +6011,7 @@ function followsExitWhenAbsent(parent, child, subject) {
5610
6011
  if (parent.type !== "block" && parent.type !== "module") {
5611
6012
  return false;
5612
6013
  }
5613
- const statements = parent.namedChildren;
6014
+ const statements = children(parent);
5614
6015
  const at = statements.findIndex((statement) => statement?.id === child.id);
5615
6016
  return statements.slice(0, at).some(
5616
6017
  (statement) => statement !== null && leavesWhenAbsent(statement, subject)
@@ -5654,7 +6055,7 @@ function continuationRaises(from) {
5654
6055
  let parent = current.parent;
5655
6056
  while (parent !== null) {
5656
6057
  if (parent.type === "block" || parent.type === "module") {
5657
- const statements = parent.namedChildren;
6058
+ const statements = children(parent);
5658
6059
  const at = statements.findIndex((one) => one?.id === current.id);
5659
6060
  const exit = firstExit(statements.slice(at + 1));
5660
6061
  if (exit !== null) {
@@ -5698,7 +6099,7 @@ function exitOf(statement) {
5698
6099
  if (LEAVING_TYPES.has(statement.type)) {
5699
6100
  return "leave";
5700
6101
  }
5701
- return statement.type === "block" ? firstExit(statement.namedChildren) : null;
6102
+ return statement.type === "block" ? firstExit(children(statement)) : null;
5702
6103
  }
5703
6104
  function isPresentWhen(test, outcome, subject) {
5704
6105
  const expression = peelParens(test);
@@ -5738,7 +6139,7 @@ function isPresentAfterComparison(comparison, outcome, subject) {
5738
6139
  }
5739
6140
  function comparisonParts(comparison) {
5740
6141
  const operators = fields(comparison, "operators");
5741
- const operands = comparison.namedChildren.filter(
6142
+ const operands = children(comparison).filter(
5742
6143
  (child) => child !== null && !operators.some((one) => one.id === child.id)
5743
6144
  );
5744
6145
  const [left, right] = operands;
@@ -5761,7 +6162,7 @@ function comparedWithNone(comparison) {
5761
6162
  }
5762
6163
  var NONE_OPERATORS = /* @__PURE__ */ new Set(["is", "is not", "==", "!="]);
5763
6164
  function peelParens(node) {
5764
- const inner = node.type === "parenthesized_expression" ? node.namedChildren[0] : null;
6165
+ const inner = node.type === "parenthesized_expression" ? node.namedChild(0) : null;
5765
6166
  return inner === null || inner === void 0 ? node : peelParens(inner);
5766
6167
  }
5767
6168
  function climbParens(node) {
@@ -5819,7 +6220,7 @@ function envReadEffects(root, module, facts) {
5819
6220
  const slots = [];
5820
6221
  const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
5821
6222
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
5822
- walkDescendants(root, startScope, {
6223
+ walkDescendants(root, startScope, children, {
5823
6224
  at: (node, scope) => {
5824
6225
  const syntax = envReadSyntaxAt(node, scope);
5825
6226
  if (syntax !== null) {
@@ -5878,7 +6279,7 @@ function hasFallback(node, spelling, scope) {
5878
6279
  }
5879
6280
  function envFactsIn(filePath, root, module) {
5880
6281
  const found = { sites: [], objects: [] };
5881
- walkDescendants(root, module.moduleScope, {
6282
+ walkDescendants(root, module.moduleScope, children, {
5882
6283
  at: (node, scope) => {
5883
6284
  if (isEnviron(node, scope) && handsOnward(node)) {
5884
6285
  found.objects.push(keyOf(filePath, node));
@@ -6033,198 +6434,67 @@ function helperReadsByCall(calls, db) {
6033
6434
  found.set(call.id, reads);
6034
6435
  }
6035
6436
  }
6036
- return found;
6037
- }
6038
- function argumentsByParameter(db, call, calleeKey2) {
6039
- const written = callArguments(call);
6040
- if (written.length === 0) {
6041
- return /* @__PURE__ */ new Map();
6042
- }
6043
- const positional = /* @__PURE__ */ new Map();
6044
- const byName = /* @__PURE__ */ new Map();
6045
- for (const argument of written) {
6046
- if (argument.kind === "keyword") {
6047
- byName.set(argument.name, argument.node);
6048
- continue;
6049
- }
6050
- positional.set(String(argument.position), argument.node);
6051
- }
6052
- const argumentAt2 = /* @__PURE__ */ new Map();
6053
- for (const callee of resolvedFunctions(db, calleeKey2)) {
6054
- for (const row of db.lookup("paramOf", 0, callee)) {
6055
- const at = positional.get(String(row[1]));
6056
- if (at !== void 0) {
6057
- argumentAt2.set(String(row[2]), at);
6058
- }
6059
- }
6060
- for (const row of db.lookup("paramNamed", 0, callee)) {
6061
- const at = byName.get(String(row[1]));
6062
- if (at !== void 0) {
6063
- argumentAt2.set(String(row[2]), at);
6064
- }
6065
- }
6066
- }
6067
- return argumentAt2;
6068
- }
6069
- function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6070
- const reads = /* @__PURE__ */ new Map();
6071
- for (const [parameter, argument] of argumentAt2) {
6072
- const sites = named.get(parameter);
6073
- noteLookup(
6074
- db,
6075
- `${NAMED_PARAMETER}${parameter}`,
6076
- describeSites(sites, siteById)
6077
- );
6078
- if (sites === void 0) {
6079
- continue;
6080
- }
6081
- const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
6082
- if (name === null) {
6083
- continue;
6084
- }
6085
- for (const id of sites) {
6086
- const defaulted = siteById.get(id)?.defaulted ?? false;
6087
- reads.set(name, (reads.get(name) ?? true) && defaulted);
6088
- }
6089
- }
6090
- if (reads.size === 0) {
6091
- return [];
6092
- }
6093
- const defaultedAtCall = isDefaultedAt(call);
6094
- return [...reads].map(
6095
- ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
6096
- );
6097
- }
6098
-
6099
- // src/paths/raisedResponses.ts
6100
- import { constantOf as constantOf4 } from "@suss/values";
6101
- var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
6102
- function raisedResponses(body, options) {
6103
- if (body === null || options.calls.length === 0) {
6104
- return [];
6105
- }
6106
- const found = [];
6107
- const walk = (node) => {
6108
- for (const child of node.namedChildren) {
6109
- if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
6110
- continue;
6111
- }
6112
- const response = raisedResponseOf(child, options);
6113
- if (response !== null) {
6114
- found.push(response);
6115
- }
6116
- walk(child);
6117
- }
6118
- };
6119
- walk(body);
6120
- return found;
6121
- }
6122
- var ENDING_EXPRESSION = {
6123
- raise_statement: (statement) => statement.namedChildren[0] ?? null,
6124
- expression_statement: (statement) => {
6125
- const only = statement.namedChildren[0];
6126
- return only?.type === "call" ? only : null;
6127
- }
6128
- };
6129
- function raisedResponseOf(statement, options) {
6130
- const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
6131
- if (expression === null) {
6132
- return null;
6133
- }
6134
- const thrownByCall = statement.type !== "raise_statement";
6135
- const call = expression.type === "call" ? expression : null;
6136
- const callee = calleeOf(expression, call);
6137
- const declared = declaredCallFor(callee, options);
6138
- if (declared === void 0 && thrownByCall) {
6139
- return null;
6140
- }
6141
- return {
6142
- statement,
6143
- thrownByCall,
6144
- terminal: terminalOf(statement, callee, call, declared, options.facts)
6145
- };
6146
- }
6147
- function returnedResponseStatus(statement, options) {
6148
- const returned = statement.namedChildren[0];
6149
- if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
6150
- return null;
6151
- }
6152
- const callee = field(returned, "function");
6153
- if (callee === null) {
6154
- return null;
6155
- }
6156
- const declared = declaredCallFor(callee, options);
6157
- return declared === void 0 ? null : statusOf(declared, returned, options.facts);
6158
- }
6159
- function calleeOf(expression, call) {
6160
- return call === null ? expression : field(call, "function") ?? expression;
6161
- }
6162
- function declaredCallFor(callee, options) {
6163
- const origin = originOf(callee, options.module);
6164
- if (origin === null) {
6165
- return void 0;
6166
- }
6167
- const qualified = `${origin.module}.${origin.name}`;
6168
- return options.calls.find((call) => call.callee === qualified);
6169
- }
6170
- function terminalOf(statement, callee, call, declared, facts) {
6171
- return {
6172
- kind: "throw",
6173
- statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
6174
- body: null,
6175
- exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
6176
- message: call === null ? null : writtenMessageOf(call),
6177
- ...declared === void 0 ? {} : { producesResponse: true },
6178
- component: null,
6179
- renderTree: null,
6180
- delegateTarget: null,
6181
- emitEvent: null,
6182
- location: rangeOf(statement)
6183
- };
6184
- }
6185
- function lastSegmentOf(dotted) {
6186
- const segment = dotted.split(".").pop();
6187
- return segment === void 0 || segment === "" ? null : segment;
6188
- }
6189
- function statusOf(declared, call, facts) {
6190
- const written = call === null ? null : statusArgumentOf(declared, call);
6191
- if (written === null) {
6192
- return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
6193
- }
6194
- const value = constantOf4(evaluatedValue(written, facts));
6195
- if (typeof value === "number") {
6196
- return { type: "literal", value };
6197
- }
6198
- return { type: "dynamic", sourceText: written.text };
6199
- }
6200
- function argumentsOf2(call) {
6201
- return (field(call, "arguments")?.namedChildren ?? []).filter(
6202
- (child) => child !== null
6203
- );
6204
- }
6205
- function statusArgumentOf(declared, call) {
6206
- const args = argumentsOf2(call);
6207
- const keyed = args.find(
6208
- (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
6209
- );
6210
- if (keyed !== void 0) {
6211
- return field(keyed, "value");
6212
- }
6213
- if (declared.statusArgument === void 0) {
6214
- return null;
6437
+ return found;
6438
+ }
6439
+ function argumentsByParameter(db, call, calleeKey2) {
6440
+ const written = callArguments(call);
6441
+ if (written.length === 0) {
6442
+ return /* @__PURE__ */ new Map();
6215
6443
  }
6216
- const positional = args.filter((arg) => arg.type !== "keyword_argument");
6217
- return positional[declared.statusArgument] ?? null;
6444
+ const positional = /* @__PURE__ */ new Map();
6445
+ const byName = /* @__PURE__ */ new Map();
6446
+ for (const argument of written) {
6447
+ if (argument.kind === "keyword") {
6448
+ byName.set(argument.name, argument.node);
6449
+ continue;
6450
+ }
6451
+ positional.set(String(argument.position), argument.node);
6452
+ }
6453
+ const argumentAt2 = /* @__PURE__ */ new Map();
6454
+ for (const callee of resolvedFunctions(db, calleeKey2)) {
6455
+ for (const row of db.lookup("paramOf", 0, callee)) {
6456
+ const at = positional.get(String(row[1]));
6457
+ if (at !== void 0) {
6458
+ argumentAt2.set(String(row[2]), at);
6459
+ }
6460
+ }
6461
+ for (const row of db.lookup("paramNamed", 0, callee)) {
6462
+ const at = byName.get(String(row[1]));
6463
+ if (at !== void 0) {
6464
+ argumentAt2.set(String(row[2]), at);
6465
+ }
6466
+ }
6467
+ }
6468
+ return argumentAt2;
6218
6469
  }
6219
- function writtenMessageOf(call) {
6220
- for (const arg of argumentsOf2(call)) {
6221
- const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
6222
- const literal = value === null ? null : stringLiteralValue(value);
6223
- if (literal !== null) {
6224
- return literal;
6470
+ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6471
+ const reads = /* @__PURE__ */ new Map();
6472
+ for (const [parameter, argument] of argumentAt2) {
6473
+ const sites = named.get(parameter);
6474
+ noteLookup(
6475
+ db,
6476
+ `${NAMED_PARAMETER}${parameter}`,
6477
+ describeSites(sites, siteById)
6478
+ );
6479
+ if (sites === void 0) {
6480
+ continue;
6481
+ }
6482
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
6483
+ if (name === null) {
6484
+ continue;
6485
+ }
6486
+ for (const id of sites) {
6487
+ const defaulted = siteById.get(id)?.defaulted ?? false;
6488
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
6225
6489
  }
6226
6490
  }
6227
- return null;
6491
+ if (reads.size === 0) {
6492
+ return [];
6493
+ }
6494
+ const defaultedAtCall = isDefaultedAt(call);
6495
+ return [...reads].map(
6496
+ ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
6497
+ );
6228
6498
  }
6229
6499
 
6230
6500
  // src/paths/returnedShape.ts
@@ -6286,7 +6556,7 @@ function shapeOfReturned(node, depth = 0) {
6286
6556
  return { type: "unknown" };
6287
6557
  }
6288
6558
  function returnedBodyShape(statement) {
6289
- const returned = statement.namedChildren[0];
6559
+ const returned = statement.namedChild(0);
6290
6560
  if (returned == null) {
6291
6561
  return null;
6292
6562
  }
@@ -6332,7 +6602,7 @@ var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lamb
6332
6602
  function streamWriteEffects(root, module) {
6333
6603
  const found = [];
6334
6604
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
6335
- walkDescendants2(root, startScope, {
6605
+ walkDescendants2(root, startScope, children, {
6336
6606
  at: (node, scope) => {
6337
6607
  if (node.type !== "call") {
6338
6608
  return;
@@ -6419,7 +6689,7 @@ function jsonDumped(node, scope) {
6419
6689
  // src/discovery.ts
6420
6690
  function decoratedNodes(root) {
6421
6691
  const found = [];
6422
- walkDescendants3(root, null, {
6692
+ walkDescendants3(root, null, children, {
6423
6693
  at: (node) => {
6424
6694
  if (node.type !== "decorated_definition") {
6425
6695
  return;
@@ -6802,7 +7072,7 @@ function readRouterPrefix(pattern, classification, module, options, range) {
6802
7072
  }
6803
7073
  return absentReading;
6804
7074
  }
6805
- function readStatusCode(pattern, classification) {
7075
+ function readStatusCode(pattern, classification, options) {
6806
7076
  if (pattern.statusCodeKeyword === void 0) {
6807
7077
  return absentReading;
6808
7078
  }
@@ -6810,11 +7080,15 @@ function readStatusCode(pattern, classification) {
6810
7080
  if (arg === void 0) {
6811
7081
  return absentReading;
6812
7082
  }
6813
- if (arg.kind === "number") {
6814
- return writtenReading(arg.value, classification.range);
7083
+ const status = statusNumberOf(arg.node, {
7084
+ ...options,
7085
+ constants: pattern.statusCodeConstants ?? {}
7086
+ });
7087
+ if (status !== null) {
7088
+ return writtenReading(status, classification.range);
6815
7089
  }
6816
7090
  return unreadableReading(
6817
- "The status this route's decorator states is not a literal number, so the response claims no status",
7091
+ "The status this route's decorator states does not come to a number here, so the response claims no status",
6818
7092
  classification.range
6819
7093
  );
6820
7094
  }
@@ -6835,11 +7109,14 @@ function statusFromReturnedValue(node, facts) {
6835
7109
  return digits === null ? null : Number.parseInt(digits[1], 10);
6836
7110
  }
6837
7111
  function statusOfReturn(statement, facts) {
6838
- const returned = statement.namedChildren[0];
7112
+ const returned = statement.namedChild(0);
6839
7113
  if (returned?.type !== "expression_list") {
6840
7114
  return { kind: "none" };
6841
7115
  }
6842
- const status = statusFromReturnedValue(returned.namedChildren[1], facts);
7116
+ const status = statusFromReturnedValue(
7117
+ returned.namedChild(1) ?? void 0,
7118
+ facts
7119
+ );
6843
7120
  return status === null ? { kind: "unreadable" } : { kind: "status", value: status };
6844
7121
  }
6845
7122
  function returnStatusReading(statement, ctx) {
@@ -6859,6 +7136,22 @@ function returnStatusReading(statement, ctx) {
6859
7136
  function returnBranch(statement, ctx) {
6860
7137
  const range = rangeOf(statement);
6861
7138
  const writtenBody = returnedBodyShape(statement);
7139
+ return responseBranchAt(
7140
+ range,
7141
+ returnStatusReading(statement, ctx),
7142
+ writtenBody === null ? ctx.responseShape.reading : writtenReading(writtenBody, range),
7143
+ ctx
7144
+ );
7145
+ }
7146
+ function fallthroughBranch(definitionNode, ctx) {
7147
+ return responseBranchAt(
7148
+ rangeOf(definitionNode),
7149
+ ctx.pattern.statusFromReturnedTuple === true ? absentReading : ctx.declaredStatus.reading,
7150
+ ctx.responseShape.reading,
7151
+ ctx
7152
+ );
7153
+ }
7154
+ function responseBranchAt(range, status, body, ctx) {
6862
7155
  return {
6863
7156
  terminal: {
6864
7157
  kind: "response",
@@ -6875,12 +7168,10 @@ function returnBranch(statement, ctx) {
6875
7168
  location: range
6876
7169
  },
6877
7170
  statusCodeReading: {
6878
- reading: returnStatusReading(statement, ctx),
7171
+ reading: status,
6879
7172
  ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
6880
7173
  },
6881
- bodyShapeReading: {
6882
- reading: writtenBody === null ? ctx.responseShape.reading : writtenReading(writtenBody, range)
6883
- },
7174
+ bodyShapeReading: { reading: body },
6884
7175
  location: range
6885
7176
  };
6886
7177
  }
@@ -6901,11 +7192,12 @@ function branchesPerTerminal(options) {
6901
7192
  const raised = raisedResponses(body, {
6902
7193
  calls: options.responseStatusCalls,
6903
7194
  module: options.ctx.module,
6904
- facts: options.ctx.facts
7195
+ facts: options.ctx.facts,
7196
+ constants: options.ctx.pattern.statusCodeConstants ?? {}
6905
7197
  });
6906
7198
  const terminals = bodyTerminals(body, raised);
6907
7199
  const soleReturnWritesBody = terminals.length === 1 && terminals[0]?.type === "return" && returnedBodyShape(terminals[0].statement) !== null;
6908
- if (terminals.length < 2 && !soleReturnWritesBody) {
7200
+ if (terminals.length === 0) {
6909
7201
  return null;
6910
7202
  }
6911
7203
  const branches = enumerateBodyBranches({
@@ -6914,6 +7206,7 @@ function branchesPerTerminal(options) {
6914
7206
  raised,
6915
7207
  effects: options.effects,
6916
7208
  branchOf: (found) => terminalBranchOf(found, options.ctx),
7209
+ fallthrough: fallthroughBranch(options.definitionNode, options.ctx),
6917
7210
  facts: options.ctx.facts
6918
7211
  });
6919
7212
  return branches.length > 1 || soleReturnWritesBody ? branches : null;
@@ -6922,7 +7215,8 @@ function statusOfReturnIn(statement, pattern, options) {
6922
7215
  const constructed = returnedResponseStatus(statement, {
6923
7216
  calls: pattern.responseConstructors ?? [],
6924
7217
  module: options.module,
6925
- facts: options.facts
7218
+ facts: options.facts,
7219
+ constants: pattern.statusCodeConstants ?? {}
6926
7220
  });
6927
7221
  if (constructed !== null) {
6928
7222
  return constructed.type === "literal" ? { kind: "status", value: constructed.value } : { kind: "unreadable" };
@@ -7016,6 +7310,25 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7016
7310
  module.scopeFor,
7017
7311
  options.importedDefinition ?? null
7018
7312
  );
7313
+ const wrappers = wrappersAround(
7314
+ pattern,
7315
+ pack,
7316
+ classification,
7317
+ functionNode,
7318
+ module,
7319
+ options
7320
+ );
7321
+ const dependencyParameters = options.wrappers?.dependencyParameters(
7322
+ wrappers
7323
+ ) ?? { names: /* @__PURE__ */ new Set(), declaresSource: false };
7324
+ const declaredStatuses = declaredStatusesOf(
7325
+ pattern,
7326
+ pack,
7327
+ classification,
7328
+ functionNode,
7329
+ module,
7330
+ options
7331
+ );
7019
7332
  return readRoutePaths(
7020
7333
  pattern,
7021
7334
  readPathArgument(classification, options),
@@ -7028,6 +7341,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7028
7341
  pack,
7029
7342
  name: functionName,
7030
7343
  exportPath: [functionName],
7344
+ dependencyParameters,
7345
+ declaredStatuses,
7031
7346
  method: verb,
7032
7347
  routePath: entry.routePath,
7033
7348
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
@@ -7044,7 +7359,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7044
7359
  responseShape: readResponseModel(pattern, classification, module, ctx),
7045
7360
  statusCode: defaultedStatus(
7046
7361
  declaredOrReturnedStatus(
7047
- readStatusCode(pattern, classification),
7362
+ readStatusCode(pattern, classification, {
7363
+ module,
7364
+ facts: options.facts
7365
+ }),
7048
7366
  readReturnedStatus(pattern, functionNode, {
7049
7367
  module,
7050
7368
  facts: options.facts
@@ -7055,14 +7373,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7055
7373
  definitionsCtx: ctx,
7056
7374
  storage: options.storage,
7057
7375
  importedDefinition: options.importedDefinition,
7058
- wrappers: wrappersAround(
7059
- pattern,
7060
- pack,
7061
- classification,
7062
- functionNode,
7063
- module,
7064
- options
7065
- )
7376
+ wrappers
7066
7377
  },
7067
7378
  options
7068
7379
  )
@@ -7081,6 +7392,38 @@ function wrappersAround(pattern, pack, classification, definitionNode, module, o
7081
7392
  definitionNode
7082
7393
  });
7083
7394
  }
7395
+ function declaredStatusesOf(pattern, pack, classification, definitionNode, module, options) {
7396
+ const keyword = pattern.responsesKeyword;
7397
+ if (keyword === void 0) {
7398
+ return [];
7399
+ }
7400
+ const above = options.wrappers?.callsAbove({
7401
+ pack,
7402
+ pattern,
7403
+ file: options.absoluteFile ?? options.filePath,
7404
+ module,
7405
+ classification,
7406
+ definitionNode
7407
+ }) ?? [];
7408
+ const dictionaries = [
7409
+ classification.keywordArgs[keyword]?.node,
7410
+ ...above.map(
7411
+ (call) => readCallArguments(field(call, "arguments")).keywordArgs[keyword]?.node
7412
+ )
7413
+ ].filter((node) => node?.type === "dictionary");
7414
+ const statuses = dictionaries.flatMap(
7415
+ (dictionary) => children(dictionary).flatMap((pair) => {
7416
+ const key = pair?.type === "pair" ? field(pair, "key") : null;
7417
+ const status = key === null ? null : statusNumberOf(key, {
7418
+ module: moduleOf(key),
7419
+ facts: options.facts,
7420
+ constants: pattern.statusCodeConstants ?? {}
7421
+ });
7422
+ return status === null ? [] : [status];
7423
+ })
7424
+ );
7425
+ return [...new Set(statuses)].sort((a, b) => a - b);
7426
+ }
7084
7427
  function readResponseShape(declared, readAnnotation) {
7085
7428
  if (declared.kind === "written") {
7086
7429
  return { reading: declared, passedOver: [] };
@@ -7164,6 +7507,8 @@ function buildRouteUnit(options) {
7164
7507
  pathParamNames: template?.paramNames ?? null,
7165
7508
  requestBodyFromAnnotatedClass,
7166
7509
  injectedCallees,
7510
+ injectedTypes: new Set(pattern.injectedParameterTypes ?? []),
7511
+ module,
7167
7512
  sources: pattern.parameterSources ?? {},
7168
7513
  aliasKeyword: pattern.parameterAliasKeyword
7169
7514
  },
@@ -7220,6 +7565,22 @@ function buildRouteUnit(options) {
7220
7565
  };
7221
7566
  branches.push(withBodyEffects(bare, extra));
7222
7567
  }
7568
+ if (branches.length > 0) {
7569
+ branches.push(
7570
+ ...validationFailureBranches(
7571
+ pattern,
7572
+ readsTheRequest(
7573
+ parameters,
7574
+ template,
7575
+ options.dependencyParameters ?? {
7576
+ names: /* @__PURE__ */ new Set(),
7577
+ declaresSource: false
7578
+ }
7579
+ ),
7580
+ rangeOf(definitionNode)
7581
+ )
7582
+ );
7583
+ }
7223
7584
  const bodyNode = field(definitionNode, "body");
7224
7585
  return {
7225
7586
  identity: {
@@ -7251,9 +7612,51 @@ function buildRouteUnit(options) {
7251
7612
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
7252
7613
  ...options.mount !== void 0 ? { mount: options.mount } : {},
7253
7614
  ...options.wrappers !== void 0 && options.wrappers.length > 0 ? { wrappers: options.wrappers } : {},
7254
- ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling }
7615
+ ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling },
7616
+ ...options.declaredStatuses !== void 0 && options.declaredStatuses.length > 0 ? { declaredStatuses: options.declaredStatuses } : {}
7255
7617
  };
7256
7618
  }
7619
+ function readsTheRequest(parameters, template, dependencies) {
7620
+ if (dependencies.declaresSource || parameters.some((parameter) => parameter.role !== null)) {
7621
+ return true;
7622
+ }
7623
+ return [...template?.paramNames ?? []].some(
7624
+ (name) => dependencies.names.has(name)
7625
+ );
7626
+ }
7627
+ function validationFailureBranches(pattern, readsRequest, range) {
7628
+ const status = pattern.validationFailureStatus;
7629
+ if (status === void 0 || !readsRequest) {
7630
+ return [];
7631
+ }
7632
+ return [
7633
+ {
7634
+ conditions: [
7635
+ {
7636
+ sourceText: "request input fails validation",
7637
+ structured: null,
7638
+ polarity: "positive",
7639
+ source: "earlyThrow"
7640
+ }
7641
+ ],
7642
+ terminal: {
7643
+ kind: "response",
7644
+ statusCode: { type: "literal", value: status },
7645
+ body: null,
7646
+ exceptionType: null,
7647
+ message: null,
7648
+ component: null,
7649
+ renderTree: null,
7650
+ delegateTarget: null,
7651
+ emitEvent: null,
7652
+ location: range
7653
+ },
7654
+ effects: [],
7655
+ location: range,
7656
+ isDefault: false
7657
+ }
7658
+ ];
7659
+ }
7257
7660
  function unreadRoleReadings(parameters, range) {
7258
7661
  if (!parameters.some((parameter) => parameter.role === null)) {
7259
7662
  return [];
@@ -7320,10 +7723,7 @@ function readParameters(definitionNode, reading, skipReceiverParam) {
7320
7723
  }
7321
7724
  const out = [];
7322
7725
  let position = 0;
7323
- for (const param of parametersNode.namedChildren) {
7324
- if (param === null) {
7325
- continue;
7326
- }
7726
+ for (const param of children(parametersNode)) {
7327
7727
  if (skipReceiverParam && position === 0 && isReceiverParam(param)) {
7328
7728
  position += 1;
7329
7729
  continue;
@@ -7347,7 +7747,7 @@ function readParameter(param, reading, position) {
7347
7747
  const { name, typeNode } = info;
7348
7748
  const shape = typeNode !== null ? annotationToShape(typeNode, reading.scope, reading.ctx) : null;
7349
7749
  const typeText = typeNode !== null ? typeNode.text : null;
7350
- if (isInjectedParameter(param, reading)) {
7750
+ if (isInjectedParameter(param, reading) || isInjectedType(typeNode, reading)) {
7351
7751
  return { name, position, role: null, typeText };
7352
7752
  }
7353
7753
  const declared = declaringCall(
@@ -7427,6 +7827,14 @@ function calleeName2(call) {
7427
7827
  function isInjectedParameter(param, reading) {
7428
7828
  return declaringCall(param, reading.injectedCallees, reading) !== null;
7429
7829
  }
7830
+ function isInjectedType(typeNode, reading) {
7831
+ if (typeNode === null || reading.injectedTypes.size === 0) {
7832
+ return false;
7833
+ }
7834
+ const written = typeNode.type === "type" ? typeNode.namedChild(0) ?? null : typeNode;
7835
+ const origin = written === null ? null : originOf(written, reading.module);
7836
+ return origin !== null && reading.injectedTypes.has(`${origin.module}.${origin.name}`);
7837
+ }
7430
7838
  function declaringCall(param, callees, reading) {
7431
7839
  if (callees.size === 0) {
7432
7840
  return null;
@@ -7724,7 +8132,7 @@ var DEFERRED_BODY_TYPES3 = /* @__PURE__ */ new Set(["function_definition", "lamb
7724
8132
  function exitSites(root, module) {
7725
8133
  const found = [];
7726
8134
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
7727
- walkDescendants4(root, startScope, {
8135
+ walkDescendants4(root, startScope, children, {
7728
8136
  at: (node, scope) => {
7729
8137
  const site = exitSiteAt(node, scope);
7730
8138
  if (site !== null) {
@@ -7741,7 +8149,7 @@ function exitSites(root, module) {
7741
8149
  return found;
7742
8150
  }
7743
8151
  function exitSiteAt(statement, scope) {
7744
- const expression = statement.namedChildren[0] ?? null;
8152
+ const expression = statement.namedChild(0) ?? null;
7745
8153
  if (expression === null) {
7746
8154
  return null;
7747
8155
  }
@@ -7922,6 +8330,7 @@ import {
7922
8330
  placeCalleeParameters,
7923
8331
  placeCalls,
7924
8332
  recordParameterGaps,
8333
+ StopList,
7925
8334
  TargetPlacements,
7926
8335
  unfollowedCallGap,
7927
8336
  worthRecording
@@ -7944,7 +8353,7 @@ function argparseFlagReads(definition, module, parameterNames2) {
7944
8353
  const parsers = /* @__PURE__ */ new Map();
7945
8354
  const handedTo = /* @__PURE__ */ new Map();
7946
8355
  const startScope = module.scopeFor.get(definition.id) ?? module.moduleScope;
7947
- walkDescendants5(definition, startScope, {
8356
+ walkDescendants5(definition, startScope, children, {
7948
8357
  at: (node, scope) => {
7949
8358
  noteParser(node, scope, parsers);
7950
8359
  noteArgument(node, parsers);
@@ -8021,6 +8430,7 @@ import {
8021
8430
  calleeOutcomeOf,
8022
8431
  calleeOutcomes,
8023
8432
  noteKeyRead as noteKeyRead3,
8433
+ RECEIVER_STORE_NAME as RECEIVER_STORE_NAME2,
8024
8434
  writtenSourcesOf
8025
8435
  } from "@suss/resolution";
8026
8436
  var STOP_FOR = {
@@ -8051,8 +8461,40 @@ function resolveCallee2(call, site, ctx, read) {
8051
8461
  return spelling;
8052
8462
  }
8053
8463
  const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
8464
+ if (selfCallInMixin(call, outcome, site, ctx)) {
8465
+ return { kind: "stopped", reason: "selfCallInMixin" };
8466
+ }
8054
8467
  return asCallee(outcome, site.owner, ctx);
8055
8468
  }
8469
+ function selfCallInMixin(call, outcome, site, ctx) {
8470
+ if (outcome.kind !== "callerSupplied" || !outcome.key.startsWith(`${site.owner}#`)) {
8471
+ return false;
8472
+ }
8473
+ const callee = readThrough(field(call, "function"));
8474
+ const receiver = callee?.type === "attribute" ? field(callee, "object") : null;
8475
+ const name = callee === null ? null : field(callee, "attribute")?.text;
8476
+ if (receiver?.type !== "identifier" || name === void 0 || name === null) {
8477
+ return false;
8478
+ }
8479
+ if (outcome.key !== `${site.owner}#${receiver.text}`) {
8480
+ return false;
8481
+ }
8482
+ return ctx.facts.lookup("instanceOf", 0, outcome.key).some((row) => {
8483
+ const classKey = String(row[1]);
8484
+ return ctx.facts.has("plainClass", [classKey]) && !instancesHaveAttribute(ctx.facts, classKey, name);
8485
+ });
8486
+ }
8487
+ var FALLBACK_ATTRIBUTE = "__getattr__";
8488
+ function instancesHaveAttribute(facts, classKey, name) {
8489
+ return facts.lookup("holdsProperty", 0, classKey).some((member) => {
8490
+ if (String(member[1]) === FALLBACK_ATTRIBUTE) {
8491
+ return true;
8492
+ }
8493
+ return facts.lookup("storesProperty", 0, String(member[2])).some(
8494
+ (store) => String(store[1]) === name && store[3] === RECEIVER_STORE_NAME2
8495
+ );
8496
+ });
8497
+ }
8056
8498
  function namedOutcomes(keys, ctx) {
8057
8499
  return calleeOutcomes(ctx.facts, [...new Set(keys)]);
8058
8500
  }
@@ -8602,14 +9044,14 @@ function passedNameKeys(file, call) {
8602
9044
  if (args === null) {
8603
9045
  return [];
8604
9046
  }
8605
- return args.namedChildren.flatMap((arg) => {
9047
+ return children(args).flatMap((arg) => {
8606
9048
  const key = passedNameKeyOf(file, arg);
8607
9049
  return key === null ? [] : [key];
8608
9050
  });
8609
9051
  }
8610
9052
  function scanBody(source, ctx, recognized, read, implied) {
8611
9053
  const followed = [];
8612
- const stops = [];
9054
+ const stops = new StopList();
8613
9055
  const placements = new TargetPlacements();
8614
9056
  const parameterCalls = [];
8615
9057
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8626,7 +9068,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8626
9068
  if (args === null) {
8627
9069
  return;
8628
9070
  }
8629
- args.namedChildren.forEach((arg, position) => {
9071
+ children(args).forEach((arg, position) => {
8630
9072
  const nameKey2 = passedNameKeyOf(file, arg);
8631
9073
  if (nameKey2 === null) {
8632
9074
  return;
@@ -8659,10 +9101,12 @@ function scanBody(source, ctx, recognized, read, implied) {
8659
9101
  outcome.kind === "followed" ? keyOf2(outcome.target) : null
8660
9102
  );
8661
9103
  if (outcome.kind === "stopped") {
8662
- const stopKey = `${outcome.reason}:${callee}`;
8663
- if (!seen.has(stopKey) && !recognized.has(call.id) && worthRecording(outcome.reason)) {
8664
- seen.add(stopKey);
8665
- stops.push({ callee, reason: outcome.reason });
9104
+ if (!recognized.has(call.id) && worthRecording(outcome.reason)) {
9105
+ stops.add({
9106
+ callee,
9107
+ reason: outcome.reason,
9108
+ ...resultUsedAsValue(call) ? { usedAsValue: true } : {}
9109
+ });
8666
9110
  }
8667
9111
  if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
8668
9112
  const parameterIndex = ownParameters.indexOf(callee);
@@ -8695,7 +9139,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8695
9139
  }
8696
9140
  return {
8697
9141
  followed,
8698
- stops,
9142
+ stops: stops.stops,
8699
9143
  targets: placements.targets,
8700
9144
  argTargets: placements.argTargets,
8701
9145
  parameterCalls,
@@ -8704,7 +9148,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8704
9148
  }
8705
9149
  function callsWritten(file, body, outer, owner, limit) {
8706
9150
  const found = [];
8707
- walkDescendants6(body, outer, {
9151
+ walkDescendants6(body, outer, children, {
8708
9152
  at: (child, scope) => {
8709
9153
  if (child.type === "call") {
8710
9154
  found.push({ call: child, site: { file, scope, owner } });
@@ -8730,7 +9174,7 @@ function identifiersUnder(node, found = []) {
8730
9174
  found.push(node.text);
8731
9175
  return found;
8732
9176
  }
8733
- for (const child of node.namedChildren) {
9177
+ for (const child of children(node)) {
8734
9178
  if (child !== null && child.type !== "attribute" && child.type !== "subscript") {
8735
9179
  identifiersUnder(child, found);
8736
9180
  }
@@ -8831,10 +9275,7 @@ function positionalParameters(node, isMethod2) {
8831
9275
  }
8832
9276
  const out = [];
8833
9277
  let position = 0;
8834
- for (const param of parameters.namedChildren) {
8835
- if (param === null) {
8836
- continue;
8837
- }
9278
+ for (const param of children(parameters)) {
8838
9279
  const name = parameterName2(param);
8839
9280
  if (name === null) {
8840
9281
  continue;
@@ -8854,10 +9295,7 @@ function callParameterNames(node, isMethod2) {
8854
9295
  return [];
8855
9296
  }
8856
9297
  const names = [];
8857
- for (const param of parameters.namedChildren) {
8858
- if (param === null) {
8859
- continue;
8860
- }
9298
+ for (const param of children(parameters)) {
8861
9299
  const name = parameterName2(param);
8862
9300
  if (name === null) {
8863
9301
  continue;
@@ -8954,7 +9392,7 @@ function exportsOf(file, node, exportName) {
8954
9392
  if (node.type !== "class_definition" || body === null) {
8955
9393
  return [];
8956
9394
  }
8957
- return body.namedChildren.flatMap((child) => {
9395
+ return children(body).flatMap((child) => {
8958
9396
  if (child === null) {
8959
9397
  return [];
8960
9398
  }
@@ -9077,6 +9515,11 @@ function watchRouterIndex(index, db, positions, fileOfModule) {
9077
9515
  found
9078
9516
  );
9079
9517
  return found;
9518
+ },
9519
+ // The wrapper index notes what it reads through the mounts, in terms
9520
+ // it can look up again.
9521
+ mountsAbove(pattern, constructionKey) {
9522
+ return index.mountsAbove(pattern, constructionKey);
9080
9523
  }
9081
9524
  };
9082
9525
  }
@@ -9181,9 +9624,33 @@ function buildRouterIndex(files, packs, resolverOptions) {
9181
9624
  };
9182
9625
  }
9183
9626
  return NOT_ROUTER;
9627
+ },
9628
+ mountsAbove(pattern, constructionKey) {
9629
+ const index = byPattern.get(pattern);
9630
+ return index === void 0 ? null : hopsAbove(index, constructionKey);
9184
9631
  }
9185
9632
  };
9186
9633
  }
9634
+ function hopsAbove(index, key) {
9635
+ const hops = [];
9636
+ const seen = /* @__PURE__ */ new Set();
9637
+ let current = key;
9638
+ while (current !== null) {
9639
+ const construction = index.byValueKey.get(current);
9640
+ if (construction === void 0 || seen.has(current)) {
9641
+ return null;
9642
+ }
9643
+ seen.add(current);
9644
+ const states = index.mounts.get(construction) ?? [];
9645
+ const only = states.length === 1 ? states[0] : void 0;
9646
+ if (only?.kind !== "mounted" || only.written === void 0) {
9647
+ return null;
9648
+ }
9649
+ hops.push({ ...only.written, parentKey: only.parentValueKey });
9650
+ current = only.parentValueKey;
9651
+ }
9652
+ return hops;
9653
+ }
9187
9654
  function composedPrefixOf(index, construction, routerPath) {
9188
9655
  const ownPrefix = composedOwnPrefix(construction.prefix, index.composition);
9189
9656
  if (ownPrefix.kind === "abstain") {
@@ -9466,8 +9933,8 @@ function constructionStatement(stmt, module, importModule) {
9466
9933
  if (stmt.type !== "expression_statement") {
9467
9934
  return null;
9468
9935
  }
9469
- const assignment2 = stmt.namedChildren.find(
9470
- (child) => child !== null && child.type === "assignment"
9936
+ const assignment2 = children(stmt).find(
9937
+ (child) => child.type === "assignment"
9471
9938
  );
9472
9939
  if (assignment2 === void 0) {
9473
9940
  return null;
@@ -9541,7 +10008,7 @@ function collectReturnedConstructions(bound, importModule, composition, index) {
9541
10008
  if (inner.type !== "return_statement") {
9542
10009
  continue;
9543
10010
  }
9544
- const returned = inner.namedChildren[0];
10011
+ const returned = inner.namedChild(0);
9545
10012
  if (returned?.type !== "call") {
9546
10013
  continue;
9547
10014
  }
@@ -9721,7 +10188,7 @@ function sequenceElementNames(node) {
9721
10188
  return null;
9722
10189
  }
9723
10190
  const names = [];
9724
- for (const child of node.namedChildren) {
10191
+ for (const child of children(node)) {
9725
10192
  if (child?.type !== "identifier") {
9726
10193
  return null;
9727
10194
  }
@@ -9934,8 +10401,9 @@ function recordMountStatement(stmt, position, scan) {
9934
10401
  scan.index.facts
9935
10402
  );
9936
10403
  const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
10404
+ const recorded = state.kind === "mounted" ? { ...state, written: { call: mountCall.call, file: scan.bound.file } } : state;
9937
10405
  for (const target of mountedConstructions(mounted, position, scan)) {
9938
- recordMount(scan.index, target, state);
10406
+ recordMount(scan.index, target, recorded);
9939
10407
  }
9940
10408
  }
9941
10409
  function mountStateOf(includerConstructorName, includer, keywordArgs, objectPrefix, composition, site, facts) {
@@ -11047,6 +11515,8 @@ import { absentReading as absentReading2, walkDescendants as walkDescendants7 }
11047
11515
  import { noteKeyRead as noteKeyRead6, noteLookup as noteLookup3 } from "@suss/resolution";
11048
11516
  var EVERY_ROUTE = "wrapEvery ";
11049
11517
  var OWN_ROUTES = "wrapOwn ";
11518
+ var MOUNTED = "wrapMounted ";
11519
+ var MOUNTED_SEPARATOR = "\0";
11050
11520
  function describeRegistered(found) {
11051
11521
  return (found ?? []).map(
11052
11522
  ({ reference }) => `${reference.file}:${reference.name}:${reference.onThrow === true}`
@@ -11084,6 +11554,10 @@ var PythonWrapperIndex = class {
11084
11554
  /** By construction key: what a router or a blueprint registered. */
11085
11555
  ownRoutes = /* @__PURE__ */ new Map();
11086
11556
  unitsByKey = /* @__PURE__ */ new Map();
11557
+ /** By pack name: the app constructions its routes run under. */
11558
+ appCalls = /* @__PURE__ */ new Map();
11559
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
11560
+ dependencyParameterTable = /* @__PURE__ */ new Map();
11087
11561
  unitsByFile = /* @__PURE__ */ new Map();
11088
11562
  /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
11089
11563
  onRegistered = null;
@@ -11102,6 +11576,12 @@ var PythonWrapperIndex = class {
11102
11576
  this.ownRoutes.get(id.slice(OWN_ROUTES.length))
11103
11577
  );
11104
11578
  }
11579
+ if (id.startsWith(MOUNTED)) {
11580
+ const [packName, position, key] = id.slice(MOUNTED.length).split(MOUNTED_SEPARATOR);
11581
+ const pack = this.options.packs.find((one) => one.name === packName);
11582
+ const pattern = pack?.discovery[Number(position)];
11583
+ return pack === void 0 || pattern === void 0 || key === void 0 ? describeRegistered([]) : describeRegistered(this.registeredAbove(pack, pattern, key));
11584
+ }
11105
11585
  return null;
11106
11586
  }
11107
11587
  noted(kind, key, found) {
@@ -11143,14 +11623,46 @@ var PythonWrapperIndex = class {
11143
11623
  inFile.push(unit);
11144
11624
  this.unitsByFile.set(target.file.file, inFile);
11145
11625
  }
11146
- return {
11147
- reference: {
11148
- file: target.file.displayPath,
11149
- name: target.name,
11150
- ...isThrowForm(declared.form) ? { onThrow: true } : {}
11151
- },
11152
- form: declared.form
11626
+ const reference = {
11627
+ file: target.file.displayPath,
11628
+ name: target.name,
11629
+ ...isThrowForm(declared.form) ? { onThrow: true } : {}
11153
11630
  };
11631
+ if (declared.form.type === "dependency") {
11632
+ this.dependencyParameterTable.set(referenceKey(reference), {
11633
+ file: target.file.file,
11634
+ names: parameterNamesOf(target.node),
11635
+ declaresSource: declaresSourcedParameter(
11636
+ target.node,
11637
+ Object.keys(declared.pattern.parameterSources ?? {})
11638
+ )
11639
+ });
11640
+ }
11641
+ return { reference, form: declared.form };
11642
+ }
11643
+ /**
11644
+ * The parameters the dependencies among these wrappers declare, and
11645
+ * whether one is declared through a parameter source such as `Query()`.
11646
+ * The library fills those from the request and validates them, the same
11647
+ * as parameters the route declares.
11648
+ */
11649
+ dependencyParameters(references) {
11650
+ const names = /* @__PURE__ */ new Set();
11651
+ let declaresSource = false;
11652
+ for (const reference of references) {
11653
+ const found = this.dependencyParameterTable.get(referenceKey(reference));
11654
+ if (found === void 0) {
11655
+ continue;
11656
+ }
11657
+ if (this.options.facts !== void 0) {
11658
+ noteKeyRead6(this.options.facts, found.file);
11659
+ }
11660
+ for (const name of found.names) {
11661
+ names.add(name);
11662
+ }
11663
+ declaresSource ||= found.declaresSource;
11664
+ }
11665
+ return { names, declaresSource };
11154
11666
  }
11155
11667
  wrappersFor(query) {
11156
11668
  const file = this.filesByPath.get(query.file);
@@ -11162,13 +11674,15 @@ var PythonWrapperIndex = class {
11162
11674
  if (file === void 0 || forms.length === 0) {
11163
11675
  return [];
11164
11676
  }
11677
+ const own = this.ownRoutesOf(query);
11165
11678
  const found = [
11166
11679
  ...this.noted(
11167
11680
  EVERY_ROUTE,
11168
11681
  query.pack.name,
11169
11682
  this.everyRoute.get(query.pack.name)
11170
11683
  ),
11171
- ...this.ownRoutesOf(query)
11684
+ ...own === null ? [] : this.mountedAbove(query, own.key),
11685
+ ...own?.registered ?? []
11172
11686
  ];
11173
11687
  for (const declared of forms) {
11174
11688
  if (declared.form.type !== "dependency") {
@@ -11199,11 +11713,65 @@ var PythonWrapperIndex = class {
11199
11713
  }
11200
11714
  return ordered;
11201
11715
  }
11716
+ /**
11717
+ * What the mounts above the route's router register on the way down:
11718
+ * the dependencies an `include_router` call lists, and those of each
11719
+ * router it is mounted onto, outermost first, the order they run in.
11720
+ */
11721
+ mountedAbove(query, key) {
11722
+ const position = query.pack.discovery.indexOf(query.pattern);
11723
+ return [
11724
+ ...this.noted(
11725
+ MOUNTED,
11726
+ [query.pack.name, position, key].join(MOUNTED_SEPARATOR),
11727
+ this.registeredAbove(query.pack, query.pattern, key)
11728
+ )
11729
+ ];
11730
+ }
11731
+ registeredAbove(pack, pattern, key) {
11732
+ const hops = this.options.routers?.mountsAbove(pattern, key) ?? null;
11733
+ if (hops === null) {
11734
+ return [];
11735
+ }
11736
+ const forms = (pattern.wrappers ?? []).flatMap(
11737
+ (form) => form.type === "dependency" ? [{ pack, pattern, form }] : []
11738
+ );
11739
+ const found = [];
11740
+ for (const hop of [...hops].reverse()) {
11741
+ found.push(
11742
+ ...hop.parentKey === null ? [] : this.ownRoutes.get(hop.parentKey) ?? []
11743
+ );
11744
+ const { keywordArgs } = readCallArguments(field(hop.call, "arguments"));
11745
+ for (const declared of forms) {
11746
+ const listed = keywordArgs[declared.form.keyword];
11747
+ const names = listed === void 0 ? [] : dependencyNamesIn(listed.node, declared.form);
11748
+ for (const name of names) {
11749
+ const target = this.functionCalled(hop.file, name);
11750
+ if (target !== null) {
11751
+ found.push(this.registered(target, declared));
11752
+ }
11753
+ }
11754
+ }
11755
+ }
11756
+ return found;
11757
+ }
11202
11758
  ownRoutesOf(query) {
11759
+ const match = this.ownRouterOf(query);
11760
+ return match === null ? null : {
11761
+ key: match.key,
11762
+ registered: this.noted(
11763
+ OWN_ROUTES,
11764
+ match.key,
11765
+ this.ownRoutes.get(match.key)
11766
+ )
11767
+ };
11768
+ }
11769
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
11770
+ ownRouterOf(query) {
11203
11771
  const site = this.objectSiteOf(query);
11204
11772
  const bound = site === null ? void 0 : this.filesByPath.get(site.file);
11205
11773
  if (site === null || bound === void 0) {
11206
- return [];
11774
+ return null;
11207
11775
  }
11208
11776
  if (this.options.facts !== void 0) {
11209
11777
  noteKeyRead6(this.options.facts, site.file);
@@ -11217,12 +11785,46 @@ var PythonWrapperIndex = class {
11217
11785
  { pack: query.pack, pattern: query.pattern, form }
11218
11786
  );
11219
11787
  if (match !== null && match.registrar.covers === "ownRoutes") {
11220
- return [
11221
- ...this.noted(OWN_ROUTES, match.key, this.ownRoutes.get(match.key))
11222
- ];
11788
+ return match;
11223
11789
  }
11224
11790
  }
11225
- return [];
11791
+ return null;
11792
+ }
11793
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
11794
+ recordApp(pack, call, file) {
11795
+ const calls = this.appCalls.get(pack) ?? [];
11796
+ if (!calls.some((one) => one.call === call)) {
11797
+ calls.push({ call, file });
11798
+ }
11799
+ this.appCalls.set(pack, calls);
11800
+ }
11801
+ /**
11802
+ * Every call that registers something for a route on the way to it:
11803
+ * the app's construction, the mount calls and the routers they mount
11804
+ * onto, and the route's own router. A caller reads a keyword off each.
11805
+ */
11806
+ callsAbove(query) {
11807
+ const own = this.ownRouterOf(query);
11808
+ const hops = own === null ? [] : this.options.routers?.mountsAbove(query.pattern, own.key) ?? [];
11809
+ const parents = hops.flatMap((hop) => {
11810
+ const node = hop.parentKey === null ? null : nodeOfResolutionKey(hop.parentKey, this.options.facts);
11811
+ return node === null ? [] : [node];
11812
+ });
11813
+ const files = [
11814
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.file),
11815
+ ...hops.map((hop) => hop.file)
11816
+ ];
11817
+ if (this.options.facts !== void 0) {
11818
+ for (const file of files) {
11819
+ noteKeyRead6(this.options.facts, file);
11820
+ }
11821
+ }
11822
+ return [
11823
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.call),
11824
+ ...hops.map((hop) => hop.call),
11825
+ ...parents,
11826
+ ...own === null ? [] : [own.call]
11827
+ ];
11226
11828
  }
11227
11829
  /**
11228
11830
  * Where the decorator's object was built: the route's own file, the file
@@ -11267,6 +11869,23 @@ var PythonWrapperIndex = class {
11267
11869
  return this.unitsByFile.get(file) ?? [];
11268
11870
  }
11269
11871
  };
11872
+ function referenceKey(reference) {
11873
+ return `${reference.file}::${reference.name}`;
11874
+ }
11875
+ function parameterNamesOf(definition) {
11876
+ return children(field(definition, "parameters")).flatMap((parameter) => {
11877
+ const named = parameterNameAndType(parameter);
11878
+ return named === null ? [] : [named.name];
11879
+ });
11880
+ }
11881
+ function declaresSourcedParameter(definition, sources) {
11882
+ return children(field(definition, "parameters")).some((parameter) => {
11883
+ const value = field(parameter, "value");
11884
+ const callee = value?.type === "call" ? field(value, "function") : null;
11885
+ const name = callee?.type === "attribute" ? field(callee, "attribute")?.text : callee?.text;
11886
+ return name !== void 0 && sources.includes(name);
11887
+ });
11888
+ }
11270
11889
  function isThrowForm(form) {
11271
11890
  return form.type === "decoratedWrapper" && form.throwParam !== void 0;
11272
11891
  }
@@ -11313,8 +11932,8 @@ function registerFrom(file, declared, index) {
11313
11932
  }
11314
11933
  function registerConstructorDependencies(file, declared, form, index) {
11315
11934
  const scope = file.module.moduleScope;
11316
- for (const stmt of file.root.namedChildren) {
11317
- const assignment2 = stmt?.type === "expression_statement" ? stmt.namedChildren.find((child) => child?.type === "assignment") : void 0;
11935
+ for (const stmt of children(file.root)) {
11936
+ const assignment2 = stmt?.type === "expression_statement" ? children(stmt).find((child) => child?.type === "assignment") : void 0;
11318
11937
  const left = assignment2 === void 0 || assignment2 === null ? null : field(assignment2, "left");
11319
11938
  if (left?.type !== "identifier") {
11320
11939
  continue;
@@ -11329,6 +11948,9 @@ function registerConstructorDependencies(file, declared, form, index) {
11329
11948
  if (match === null) {
11330
11949
  continue;
11331
11950
  }
11951
+ if (match.registrar.covers === "everyRoute") {
11952
+ index.recordApp(declared.pack.name, match.call, file.file);
11953
+ }
11332
11954
  const { keywordArgs } = readCallArguments(field(match.call, "arguments"));
11333
11955
  const listed = keywordArgs[form.keyword];
11334
11956
  if (listed === void 0) {
@@ -11346,7 +11968,7 @@ function coverageOf(match, pack) {
11346
11968
  return match.registrar.covers === "everyRoute" ? { kind: "everyRoute", pack: pack.name } : { kind: "ownRoutes", key: match.key };
11347
11969
  }
11348
11970
  function registerDecorated(file, declared, form, index) {
11349
- walkDescendants7(file.root, file.module.moduleScope, {
11971
+ walkDescendants7(file.root, file.module.moduleScope, children, {
11350
11972
  at: (node, scope) => {
11351
11973
  if (node.type !== "decorated_definition") {
11352
11974
  return;
@@ -11382,7 +12004,7 @@ function registerDecorated(file, declared, form, index) {
11382
12004
  });
11383
12005
  }
11384
12006
  function dependencyNamesIn(node, form) {
11385
- const calls = node.type === "list" ? node.namedChildren : [node];
12007
+ const calls = node.type === "list" ? children(node) : [node];
11386
12008
  const names = [];
11387
12009
  for (const call of calls) {
11388
12010
  const name = call === null ? null : dependencyNameOf(call, form);
@@ -11401,7 +12023,7 @@ function dependencyNameOf(node, form) {
11401
12023
  if (calleeName3 === null || !form.callees.includes(calleeName3)) {
11402
12024
  return null;
11403
12025
  }
11404
- const first = field(node, "arguments")?.namedChildren[0];
12026
+ const first = field(node, "arguments")?.namedChild(0);
11405
12027
  return first?.type === "identifier" ? first.text : null;
11406
12028
  }
11407
12029
  function decoratorDependencies(classification, form) {
@@ -11419,7 +12041,7 @@ function parameterDependencies(definitionNode, form) {
11419
12041
  if (direct !== null) {
11420
12042
  return direct;
11421
12043
  }
11422
- for (const child of node.namedChildren) {
12044
+ for (const child of children(node)) {
11423
12045
  const found = findIn(child, depth + 1);
11424
12046
  if (found !== null) {
11425
12047
  return found;
@@ -11427,10 +12049,7 @@ function parameterDependencies(definitionNode, form) {
11427
12049
  }
11428
12050
  return null;
11429
12051
  };
11430
- for (const param of field(definitionNode, "parameters")?.namedChildren ?? []) {
11431
- if (param === null) {
11432
- continue;
11433
- }
12052
+ for (const param of children(field(definitionNode, "parameters"))) {
11434
12053
  const inDefault = field(param, "value");
11435
12054
  const found = inDefault === null ? findIn(field(param, "type"), 0) : dependencyNameOf(inDefault, form);
11436
12055
  if (found !== null) {
@@ -11446,7 +12065,8 @@ function wrapperUnit(target, declared, options) {
11446
12065
  const raised = raisedResponses(body, {
11447
12066
  calls: declared.pattern.responseStatusCalls ?? [],
11448
12067
  module: file.module,
11449
- facts: options.facts
12068
+ facts: options.facts,
12069
+ constants: declared.pattern.statusCodeConstants ?? {}
11450
12070
  });
11451
12071
  const continuations = continuationStatements(node, declared.form);
11452
12072
  const terminals = bodyTerminals(body, raised, continuations);
@@ -11530,7 +12150,7 @@ function returnHandsOn(statement, form) {
11530
12150
  return false;
11531
12151
  }
11532
12152
  if (form.returnedValueResponds === true) {
11533
- const returned = statement.namedChildren[0];
12153
+ const returned = statement.namedChild(0);
11534
12154
  return returned === void 0 || returned === null || returned.type === "none";
11535
12155
  }
11536
12156
  return true;
@@ -11539,9 +12159,7 @@ function continuationStatements(node, form) {
11539
12159
  if (form.type !== "decoratedWrapper" || form.continuationParam === void 0) {
11540
12160
  return [];
11541
12161
  }
11542
- const params = (field(node, "parameters")?.namedChildren ?? []).filter(
11543
- (param2) => param2 !== null
11544
- );
12162
+ const params = children(field(node, "parameters"));
11545
12163
  const param = params[form.continuationParam];
11546
12164
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
11547
12165
  if (continuation === null) {
@@ -11842,7 +12460,8 @@ async function runPython(options, roots, cache, previous) {
11842
12460
  roots,
11843
12461
  facts: needsValues ? db : void 0,
11844
12462
  definitions,
11845
- storageFor
12463
+ storageFor,
12464
+ routers: plainRouterIndex
11846
12465
  })
11847
12466
  );
11848
12467
  const importedDefinition2 = importedDefinitionLookup(db, bound);
@@ -12285,7 +12904,7 @@ function indexDefinitions(into, file, node) {
12285
12904
  if (isFunction(node)) {
12286
12905
  into.set(nodeId(file, node), node);
12287
12906
  }
12288
- for (const child of node.namedChildren) {
12907
+ for (const child of children(node)) {
12289
12908
  if (child !== null) {
12290
12909
  indexDefinitions(into, file, child);
12291
12910
  }
@@ -12551,7 +13170,7 @@ function importSitesIn(root) {
12551
13170
  found.push({ module: entry.module, name: entry.name, line });
12552
13171
  }
12553
13172
  }
12554
- for (const child of node.namedChildren) {
13173
+ for (const child of children(node)) {
12555
13174
  if (child !== null) {
12556
13175
  visit(child);
12557
13176
  }