@suss/adapter-ruby 0.34.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  bindEvaluator: () => bindEvaluator,
37
37
  bodyStatements: () => bodyStatements,
38
38
  booleanLiteralValue: () => booleanLiteralValue,
39
+ children: () => children,
39
40
  collectFileConstants: () => collectFileConstants,
40
41
  createConstantFileCache: () => createConstantFileCache,
41
42
  createFileCache: () => createFileCache,
@@ -97,7 +98,29 @@ function field(node, name) {
97
98
  return node.childForFieldName(name);
98
99
  }
99
100
  function children(node) {
100
- return node.namedChildren.filter((child) => child !== null);
101
+ if (node === null || node === void 0) {
102
+ return [];
103
+ }
104
+ const found = [];
105
+ const count = node.namedChildCount;
106
+ for (let index = 0; index < count; index++) {
107
+ const child = node.namedChild(index);
108
+ if (child !== null) {
109
+ found.push(child);
110
+ }
111
+ }
112
+ return found;
113
+ }
114
+ function allChildren(node) {
115
+ const found = [];
116
+ const count = node.childCount;
117
+ for (let index = 0; index < count; index++) {
118
+ const child = node.child(index);
119
+ if (child !== null) {
120
+ found.push(child);
121
+ }
122
+ }
123
+ return found;
101
124
  }
102
125
  function bodyStatements(body) {
103
126
  return children(body);
@@ -159,6 +182,13 @@ var MODULE_SCOPE_STOPS = /* @__PURE__ */ new Set([
159
182
  var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
160
183
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
161
184
  var LAMBDA_TYPE = "lambda";
185
+ var RAISING_METHODS = /* @__PURE__ */ new Set(["raise", "fail"]);
186
+ function isRaiseCall(node) {
187
+ if (node.type === "identifier") {
188
+ return RAISING_METHODS.has(node.text);
189
+ }
190
+ return node.type === "call" && field(node, "receiver") === null && RAISING_METHODS.has(field(node, "method")?.text ?? "");
191
+ }
162
192
  function enclosingDefinition(node) {
163
193
  return definitionsAround.of(node);
164
194
  }
@@ -234,7 +264,7 @@ function runStatements(body, blockConfigures = () => false) {
234
264
  }
235
265
  return !(node.type === "call" && blockConfigures(node));
236
266
  };
237
- (0, import_extractor.walkDescendants)(body, null, {
267
+ (0, import_extractor.walkDescendants)(body, null, children, {
238
268
  at: (node) => {
239
269
  found.push(node);
240
270
  },
@@ -521,6 +551,51 @@ function readCallArgs(argumentList) {
521
551
  }
522
552
  return { positional, keyword };
523
553
  }
554
+ var CONTROL_OPERATORS = /* @__PURE__ */ new Set([
555
+ "and",
556
+ "or",
557
+ "&&",
558
+ "||"
559
+ ]);
560
+ var HANDS_ON = /* @__PURE__ */ new Set(["return", "next", "break"]);
561
+ var isChild = (fieldName) => (parent, child) => field(parent, fieldName)?.id === child.id;
562
+ var always = () => true;
563
+ var asCondition = isChild("condition");
564
+ var VALUE_USED_UNDER = {
565
+ assignment: isChild("right"),
566
+ operator_assignment: isChild("right"),
567
+ call: isChild("receiver"),
568
+ element_reference: always,
569
+ argument_list: (parent) => !HANDS_ON.has(parent.parent?.type ?? ""),
570
+ pair: always,
571
+ array: always,
572
+ hash: always,
573
+ interpolation: always,
574
+ unary: always,
575
+ binary: (parent) => !CONTROL_OPERATORS.has(field(parent, "operator")?.text ?? ""),
576
+ if: asCondition,
577
+ unless: asCondition,
578
+ while: asCondition,
579
+ until: asCondition,
580
+ if_modifier: asCondition,
581
+ unless_modifier: asCondition,
582
+ while_modifier: asCondition,
583
+ until_modifier: asCondition,
584
+ conditional: asCondition,
585
+ case: isChild("value")
586
+ };
587
+ function resultUsedAsValue(node) {
588
+ let child = node;
589
+ let parent = node.parent;
590
+ while (parent !== null && parent.type === "parenthesized_statements" && parent.namedChildCount === 1) {
591
+ child = parent;
592
+ parent = parent.parent;
593
+ }
594
+ if (parent === null) {
595
+ return false;
596
+ }
597
+ return VALUE_USED_UNDER[parent.type]?.(parent, child) ?? false;
598
+ }
524
599
  var NodeSet = class extends import_extractor.IdSet {
525
600
  };
526
601
  var NodeMap = class extends import_extractor.IdMap {
@@ -555,19 +630,23 @@ var PATH_CONVENTIONS = {
555
630
  function createConstantFileCache() {
556
631
  return { autoloadDirectories: /* @__PURE__ */ new Map(), resolved: /* @__PURE__ */ new Map() };
557
632
  }
558
- function resolveConstantFile(root, qualifiedName, convention, acronyms = [], cache = createConstantFileCache()) {
633
+ function resolveConstantFile(root, qualifiedName, convention, acronyms = [], cache = createConstantFileCache(), autoloadRoots = []) {
559
634
  const relative = `${PATH_CONVENTIONS[convention](qualifiedName, acronyms)}.rb`;
560
- const key = `${root}\0${relative}`;
635
+ const key = [root, ...autoloadRoots, relative].join("\0");
561
636
  const known = cache.resolved.get(key);
562
637
  if (known !== void 0) {
563
638
  return known;
564
639
  }
565
- const found = firstExisting(root, relative, cache);
640
+ const found = firstExisting(root, relative, cache, autoloadRoots);
566
641
  cache.resolved.set(key, found);
567
642
  return found;
568
643
  }
569
- function firstExisting(root, relative, cache) {
570
- for (const candidate of [root, ...autoloadDirectoriesOf(root, cache)]) {
644
+ function firstExisting(root, relative, cache, autoloadRoots) {
645
+ for (const candidate of [
646
+ root,
647
+ ...autoloadDirectoriesOf(root, cache),
648
+ ...autoloadRoots
649
+ ]) {
571
650
  const file = import_node_path.default.join(candidate, relative);
572
651
  if (import_node_fs.default.existsSync(file)) {
573
652
  return file;
@@ -614,6 +693,7 @@ var import_resolution5 = require("@suss/resolution");
614
693
  var import_values2 = require("@suss/values");
615
694
 
616
695
  // src/facts/resolve.ts
696
+ var import_behavioral_ir = require("@suss/behavioral-ir");
617
697
  var import_datalog = require("@suss/datalog");
618
698
  var import_resolution = require("@suss/resolution");
619
699
 
@@ -630,6 +710,71 @@ var CONVERTING_FUNCTIONS = /* @__PURE__ */ new Set([
630
710
  "String",
631
711
  "Float"
632
712
  ]);
713
+ var OBJECT_METHODS = /* @__PURE__ */ new Set([
714
+ "Array",
715
+ "Float",
716
+ "Hash",
717
+ "Integer",
718
+ "String",
719
+ "abort",
720
+ "binding",
721
+ "block_given?",
722
+ "caller",
723
+ "catch",
724
+ "class",
725
+ "clone",
726
+ "define_singleton_method",
727
+ "dup",
728
+ "eql?",
729
+ "equal?",
730
+ "eval",
731
+ "exit",
732
+ "extend",
733
+ "fail",
734
+ "format",
735
+ "freeze",
736
+ "frozen?",
737
+ "hash",
738
+ "inspect",
739
+ "instance_of?",
740
+ "instance_variable_defined?",
741
+ "instance_variable_get",
742
+ "instance_variable_set",
743
+ "instance_variables",
744
+ "is_a?",
745
+ "itself",
746
+ "kind_of?",
747
+ "lambda",
748
+ "loop",
749
+ "method",
750
+ "methods",
751
+ "nil?",
752
+ "object_id",
753
+ "p",
754
+ "pp",
755
+ "print",
756
+ "printf",
757
+ "proc",
758
+ "public_send",
759
+ "puts",
760
+ "raise",
761
+ "rand",
762
+ "require",
763
+ "require_relative",
764
+ "respond_to?",
765
+ "send",
766
+ "singleton_class",
767
+ "sleep",
768
+ "sprintf",
769
+ "srand",
770
+ "system",
771
+ "tap",
772
+ "then",
773
+ "throw",
774
+ "to_s",
775
+ "warn",
776
+ "yield_self"
777
+ ]);
633
778
  var CONSTRUCTOR = "new";
634
779
 
635
780
  // src/facts/resolve.ts
@@ -677,7 +822,13 @@ function resolveEnvObjects(db, objects) {
677
822
  }
678
823
  function writtenValueOf(db, key) {
679
824
  resolveValues(db, [key]);
680
- return (0, import_resolution.writtenValueOf)(db, key, (keys) => resolveValues(db, keys));
825
+ return (0, import_resolution.writtenValueOf)(
826
+ db,
827
+ key,
828
+ (keys) => resolveValues(db, keys),
829
+ void 0,
830
+ (0, import_resolution.answersFromTestsLeftOut)(key, import_behavioral_ir.isTestFile)
831
+ );
681
832
  }
682
833
  function writtenValueUnder(db, key, site) {
683
834
  const program = (0, import_resolution.resolutionUnderProgram)(RUBY_RULES);
@@ -709,7 +860,7 @@ function isFieldOf(parent, node, name) {
709
860
  function namedAt(parent, node) {
710
861
  return isFieldOf(parent, node, "name");
711
862
  }
712
- function always() {
863
+ function always2() {
713
864
  return true;
714
865
  }
715
866
  var SPELLS_A_NAME = {
@@ -724,14 +875,14 @@ var SPELLS_A_NAME = {
724
875
  hash_splat_parameter: namedAt,
725
876
  keyword_parameter: namedAt,
726
877
  block_parameter: namedAt,
727
- destructured_parameter: always,
728
- left_assignment_list: always,
729
- rest_assignment: always,
730
- destructured_left_assignment: always,
731
- exception_variable: always,
732
- undef: always,
733
- alias: always,
734
- ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
878
+ destructured_parameter: always2,
879
+ left_assignment_list: always2,
880
+ rest_assignment: always2,
881
+ destructured_left_assignment: always2,
882
+ exception_variable: always2,
883
+ undef: always2,
884
+ alias: always2,
885
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always2]))
735
886
  };
736
887
  function addNamesUnder(node, names) {
737
888
  if (node === null) {
@@ -1550,9 +1701,7 @@ function emitKeyedElement(emitter, node, parent) {
1550
1701
  if (object === null || isWriteTarget(node, parent)) {
1551
1702
  return;
1552
1703
  }
1553
- const index = node.namedChildren.find(
1554
- (child) => child !== null && child.id !== object.id
1555
- );
1704
+ const index = children(node).find((child) => child.id !== object.id);
1556
1705
  if (index !== void 0) {
1557
1706
  emitKeyedRead(emitter, node, object, index, node);
1558
1707
  emitWrittenKeyRead(emitter, node, object, index);
@@ -1593,17 +1742,17 @@ function fallbackBranchesOf(node, type, enclosing) {
1593
1742
  }
1594
1743
  return [left, right].filter((side) => !raises(side, enclosing));
1595
1744
  }
1596
- var RAISING_METHODS = /* @__PURE__ */ new Set(["raise", "fail"]);
1745
+ var RAISING_METHODS2 = /* @__PURE__ */ new Set(["raise", "fail"]);
1597
1746
  function raises(written, enclosing) {
1598
1747
  const node = readThrough(written);
1599
1748
  if (node.type === "identifier") {
1600
- return RAISING_METHODS.has(node.text) && isBareCall(node, enclosing);
1749
+ return RAISING_METHODS2.has(node.text) && isBareCall(node, enclosing);
1601
1750
  }
1602
1751
  if (node.type !== "call") {
1603
1752
  return false;
1604
1753
  }
1605
1754
  const receiver = field(node, "receiver");
1606
- return RAISING_METHODS.has(field(node, "method")?.text ?? "") && (receiver === null || receiver.text === "Kernel");
1755
+ return RAISING_METHODS2.has(field(node, "method")?.text ?? "") && (receiver === null || receiver.text === "Kernel");
1607
1756
  }
1608
1757
  function emitExpressionFacts(emitter, node) {
1609
1758
  emitDynamicDefinition(emitter, node, node.type, []);
@@ -2559,9 +2708,7 @@ var EXPRESSION_TYPES = {
2559
2708
  ),
2560
2709
  unary: (node) => {
2561
2710
  const operand2 = field(node, "operand");
2562
- const operator = node.children.find(
2563
- (child) => child !== null && !child.isNamed
2564
- );
2711
+ const operator = allChildren(node).find((child) => !child.isNamed);
2565
2712
  if (operand2 === null || operator === void 0) {
2566
2713
  return OPAQUE;
2567
2714
  }
@@ -2763,8 +2910,25 @@ function statementOf(node) {
2763
2910
  if (appending !== null) {
2764
2911
  return appending;
2765
2912
  }
2913
+ if (isRaiseCall(node) && !rescuedInSameFunction(node)) {
2914
+ return { kind: "throw" };
2915
+ }
2766
2916
  return isTailExpression(node) ? { kind: "return", value: node } : { kind: "expression", value: node };
2767
2917
  }
2918
+ var RESCUING_TYPES = /* @__PURE__ */ new Set(["begin", "body_statement"]);
2919
+ var UNRESCUED_PARTS = /* @__PURE__ */ new Set(["rescue", "else", "ensure"]);
2920
+ function rescuedInSameFunction(node) {
2921
+ let child = node;
2922
+ let parent = node.parent;
2923
+ while (parent !== null && !isRoot(parent)) {
2924
+ if (RESCUING_TYPES.has(parent.type) && !UNRESCUED_PARTS.has(child.type) && children(parent).some((part) => part.type === "rescue")) {
2925
+ return true;
2926
+ }
2927
+ child = parent;
2928
+ parent = parent.parent;
2929
+ }
2930
+ return false;
2931
+ }
2768
2932
  function appendingStatement(node) {
2769
2933
  if (node.type !== "binary" || field(node, "operator")?.text !== "<<") {
2770
2934
  return null;
@@ -3420,6 +3584,10 @@ function writtenNodeOf(node, db, site) {
3420
3584
  const context = db === void 0 ? void 0 : contexts.get(db);
3421
3585
  return context === void 0 ? null : context.writtenTo(node, site);
3422
3586
  }
3587
+ function calledDefinition(call, db) {
3588
+ const context = db === void 0 ? void 0 : contexts.get(db);
3589
+ return context === void 0 ? null : context.callable(call);
3590
+ }
3423
3591
  function askWrittenValues(nodes, db) {
3424
3592
  if (db === void 0 || !contexts.has(db)) {
3425
3593
  return;
@@ -3482,7 +3650,7 @@ function methodDefinitionsIn(file, root) {
3482
3650
  if (METHOD_TYPES.has(node.type)) {
3483
3651
  found.set(nodeId(file, node), node);
3484
3652
  }
3485
- for (const child of node.namedChildren) {
3653
+ for (const child of children(node)) {
3486
3654
  if (child !== null) {
3487
3655
  visit(child);
3488
3656
  }
@@ -3808,12 +3976,27 @@ function graphqlTypeNameFromQualified(qualifiedName, convention) {
3808
3976
 
3809
3977
  // src/ancestry.ts
3810
3978
  async function reachDefinition(qualifiedName, lookup) {
3979
+ const segments = qualifiedName.split("::");
3980
+ for (let length = segments.length; length > 0; length--) {
3981
+ const found = await definedInFileOf(
3982
+ segments.slice(0, length).join("::"),
3983
+ qualifiedName,
3984
+ lookup
3985
+ );
3986
+ if (found !== null) {
3987
+ return found;
3988
+ }
3989
+ }
3990
+ return null;
3991
+ }
3992
+ async function definedInFileOf(fileConstant, qualifiedName, lookup) {
3811
3993
  const filePath = resolveConstantFile(
3812
3994
  lookup.root,
3813
- qualifiedName,
3995
+ fileConstant,
3814
3996
  lookup.pathConvention,
3815
3997
  lookup.acronyms,
3816
- lookup.constantFiles
3998
+ lookup.constantFiles,
3999
+ lookup.autoloadRoots
3817
4000
  );
3818
4001
  if (filePath === null) {
3819
4002
  return null;
@@ -3958,7 +4141,7 @@ function superclassCandidatesOf(blocks) {
3958
4141
  return [];
3959
4142
  }
3960
4143
  function methodInAncestry(ancestry, name, read = {}) {
3961
- for (const entry of ancestry) {
4144
+ for (const [index, entry] of ancestry.entries()) {
3962
4145
  if (entry.type === "root") {
3963
4146
  return { type: "none" };
3964
4147
  }
@@ -3966,7 +4149,8 @@ function methodInAncestry(ancestry, name, read = {}) {
3966
4149
  return {
3967
4150
  type: "unsettled",
3968
4151
  reason: `inherited from ${entry.name}, which this run did not read`,
3969
- cause: "unreadAncestor"
4152
+ cause: "unreadAncestor",
4153
+ definedLater: definedInProjectAfter(ancestry, index, name, read)
3970
4154
  };
3971
4155
  }
3972
4156
  const found = definitionIn(entry.blocks, name, read);
@@ -3983,6 +4167,18 @@ function methodInAncestry(ancestry, name, read = {}) {
3983
4167
  }
3984
4168
  return { type: "none" };
3985
4169
  }
4170
+ function definedInProjectAfter(ancestry, index, name, read) {
4171
+ return ancestry.slice(index + 1).some(
4172
+ (entry) => entry.type !== "root" && entry.type !== "unfollowed" && definitionIn(entry.blocks, name, read).method !== null
4173
+ );
4174
+ }
4175
+ function methodPastUnreadAncestors(ancestry, name, read = {}) {
4176
+ return methodInAncestry(
4177
+ ancestry.filter((entry) => entry.type !== "unfollowed"),
4178
+ name,
4179
+ read
4180
+ );
4181
+ }
3986
4182
  function definitionIn(blocks, name, read) {
3987
4183
  let method = null;
3988
4184
  let block = null;
@@ -4027,12 +4223,12 @@ function inheritedStatements(ancestry, blockConfigures) {
4027
4223
  }
4028
4224
 
4029
4225
  // src/discovery.ts
4030
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
4031
- var import_extractor9 = require("@suss/extractor");
4226
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
4227
+ var import_extractor11 = require("@suss/extractor");
4032
4228
 
4033
4229
  // src/clientCalls.ts
4034
- var import_behavioral_ir = require("@suss/behavioral-ir");
4035
- var import_values13 = require("@suss/values");
4230
+ var import_behavioral_ir2 = require("@suss/behavioral-ir");
4231
+ var import_values14 = require("@suss/values");
4036
4232
 
4037
4233
  // src/paths/effects.ts
4038
4234
  var import_extractor2 = require("@suss/extractor");
@@ -4059,7 +4255,7 @@ function isRaise(node) {
4059
4255
  return false;
4060
4256
  }
4061
4257
  const receiver = field(node, "receiver");
4062
- const method = field(node, "method") ?? node.namedChildren[0] ?? null;
4258
+ const method = field(node, "method") ?? node.namedChild(0) ?? null;
4063
4259
  return receiver === null && method !== null && RAISE_NAMES.has(method.text);
4064
4260
  }
4065
4261
  function exitOf(node) {
@@ -4071,7 +4267,7 @@ function exitOf(node) {
4071
4267
  function exitKindOf(node) {
4072
4268
  let sawReturn = false;
4073
4269
  const raises2 = (current) => {
4074
- for (const child of current.namedChildren) {
4270
+ for (const child of children(current)) {
4075
4271
  if (child === null || CAPTURES_RETURN.has(child.type)) {
4076
4272
  continue;
4077
4273
  }
@@ -4105,13 +4301,52 @@ function callHeaderOf(call, block) {
4105
4301
  function blockStatements(block) {
4106
4302
  return block === null ? [] : children(block);
4107
4303
  }
4304
+ function attachedBlockOf(call) {
4305
+ return children(call).find(
4306
+ (child) => child !== null && (child.type === "do_block" || child.type === "block")
4307
+ ) ?? null;
4308
+ }
4309
+ function soleBlockParameter(block) {
4310
+ const names = children(field(block, "parameters")).filter(
4311
+ (child) => child.type === "identifier"
4312
+ );
4313
+ return names.length === 1 ? names[0].text : null;
4314
+ }
4315
+ function blockBodyStatements(block) {
4316
+ const body = field(block, "body");
4317
+ if (body !== null) {
4318
+ return children(body);
4319
+ }
4320
+ return children(block).filter((child) => child.type !== "block_parameters");
4321
+ }
4322
+ function armBody(block) {
4323
+ return field(block, "body") ?? block;
4324
+ }
4325
+ function formatArmOf(statement, parameter) {
4326
+ if (statement.type !== "call") {
4327
+ return null;
4328
+ }
4329
+ const receiver = field(statement, "receiver");
4330
+ const format = field(statement, "method")?.text;
4331
+ if (receiver?.text !== parameter || format === void 0) {
4332
+ return null;
4333
+ }
4334
+ const block = attachedBlockOf(statement);
4335
+ return {
4336
+ format,
4337
+ header: block === null ? statement.text : callHeaderOf(statement, block).sourceText,
4338
+ block
4339
+ };
4340
+ }
4108
4341
  var Lowerer = class {
4109
- constructor(terminals, responseCalls) {
4342
+ constructor(terminals, responseCalls, formatSwitch) {
4110
4343
  this.terminals = terminals;
4111
4344
  this.responseCalls = responseCalls;
4345
+ this.formatSwitch = formatSwitch;
4112
4346
  }
4113
4347
  terminals;
4114
4348
  responseCalls;
4349
+ formatSwitch;
4115
4350
  terminalsByStmt = /* @__PURE__ */ new Map();
4116
4351
  terminalHome = new NodeMap();
4117
4352
  attachTerminals(statement, found) {
@@ -4142,7 +4377,7 @@ var Lowerer = class {
4142
4377
  found.push(own);
4143
4378
  return found;
4144
4379
  }
4145
- for (const child of node.namedChildren) {
4380
+ for (const child of children(node)) {
4146
4381
  if (child !== null && !OWN_BODY_TYPES.has(child.type)) {
4147
4382
  this.responsesIn(child, found);
4148
4383
  }
@@ -4201,29 +4436,22 @@ var Lowerer = class {
4201
4436
  };
4202
4437
  }
4203
4438
  lowerBegin(node) {
4204
- const rescues = node.namedChildren.filter(
4205
- (child) => child !== null && child.type === "rescue"
4206
- );
4207
- const ensureClause = node.namedChildren.find(
4208
- (child) => child !== null && child.type === "ensure"
4209
- ) ?? null;
4210
- const tryBody = node.namedChildren.filter(
4439
+ const rescues2 = children(node).filter((child) => child.type === "rescue");
4440
+ const ensureClause = children(node).find((child) => child.type === "ensure") ?? null;
4441
+ const tryBody = children(node).filter(
4211
4442
  (child) => child !== null && child.type !== "rescue" && child.type !== "ensure" && child.type !== "else"
4212
4443
  );
4213
4444
  return {
4214
4445
  kind: "try",
4215
4446
  tryBody: tryBody.map((statement) => this.lower(statement)),
4216
- catchBody: rescues.length > 0 ? rescues.flatMap((clause) => this.lowerBlock(field(clause, "body"))) : null,
4447
+ catchBody: rescues2.length > 0 ? rescues2.flatMap((clause) => this.lowerBlock(field(clause, "body"))) : null,
4217
4448
  finallyBody: ensureClause === null ? null : this.lowerBlock(ensureClause),
4218
4449
  exitKind: exitKindOf(node)
4219
4450
  };
4220
4451
  }
4221
4452
  lowerCase(node) {
4222
4453
  const groups = [];
4223
- for (const child of node.namedChildren) {
4224
- if (child === null) {
4225
- continue;
4226
- }
4454
+ for (const child of children(node)) {
4227
4455
  if (child.type === "when") {
4228
4456
  groups.push({
4229
4457
  condition: handleOf(field(child, "pattern")),
@@ -4241,6 +4469,47 @@ var Lowerer = class {
4241
4469
  }
4242
4470
  return { kind: "switch", groups, exitKind: exitKindOf(node) };
4243
4471
  }
4472
+ /**
4473
+ * `respond_to do |format| ... end` runs one arm per request, picked by
4474
+ * the format the request accepts, so it lowers to a switch with an arm
4475
+ * per `format.x`. The arm that accepts every format is the default.
4476
+ * Without one, a request no arm accepts makes the library raise. Null
4477
+ * when the block contains anything besides those arms.
4478
+ */
4479
+ lowerFormatSwitch(node, block) {
4480
+ const declared = this.formatSwitch;
4481
+ if (declared === void 0 || node.type !== "call" || field(node, "receiver") !== null || field(node, "method")?.text !== declared.name) {
4482
+ return null;
4483
+ }
4484
+ const parameter = soleBlockParameter(block);
4485
+ if (parameter === null) {
4486
+ return null;
4487
+ }
4488
+ const arms = blockBodyStatements(block).map(
4489
+ (statement) => formatArmOf(statement, parameter)
4490
+ );
4491
+ if (arms.length === 0 || arms.some((arm) => arm === null)) {
4492
+ return null;
4493
+ }
4494
+ const groups = arms.map(
4495
+ (arm) => ({
4496
+ condition: arm.format === declared.matchesEvery ? null : { sourceText: arm.header, expression: null },
4497
+ hasTrailingBreak: true,
4498
+ body: arm.block === null ? [] : this.lowerBlock(armBody(arm.block))
4499
+ })
4500
+ );
4501
+ const named2 = groups.filter((group) => group.condition !== null);
4502
+ const fallback2 = groups.find((group) => group.condition === null) ?? {
4503
+ condition: null,
4504
+ hasTrailingBreak: true,
4505
+ body: [{ kind: "exit", exit: "throw", exitKind: "throw" }]
4506
+ };
4507
+ return {
4508
+ kind: "switch",
4509
+ groups: [...named2, fallback2],
4510
+ exitKind: exitKindOf(node)
4511
+ };
4512
+ }
4244
4513
  lower(node) {
4245
4514
  const exit = exitOf(node);
4246
4515
  if (exit !== null) {
@@ -4270,10 +4539,12 @@ var Lowerer = class {
4270
4539
  [header]
4271
4540
  );
4272
4541
  }
4273
- const attachedBlock = node.namedChildren.find(
4274
- (child) => child !== null && (child.type === "do_block" || child.type === "block")
4275
- );
4276
- if (node.type === "call" && attachedBlock !== void 0) {
4542
+ const attachedBlock = attachedBlockOf(node);
4543
+ const formatSwitch = attachedBlock === null ? null : this.lowerFormatSwitch(node, attachedBlock);
4544
+ if (formatSwitch !== null) {
4545
+ return this.attach(formatSwitch, [node]);
4546
+ }
4547
+ if (node.type === "call" && attachedBlock !== null) {
4277
4548
  return this.attach(
4278
4549
  {
4279
4550
  kind: "loop",
@@ -4300,10 +4571,11 @@ var Lowerer = class {
4300
4571
  return this.attach({ kind: "opaque", exitKind: exitKindOf(node) }, [node]);
4301
4572
  }
4302
4573
  };
4303
- function lowerRubyBody(body, terminals, responseCalls = []) {
4574
+ function lowerRubyBody(body, terminals, responseCalls = [], formatSwitch) {
4304
4575
  const lowerer = new Lowerer(
4305
4576
  new Map(terminals.map((terminal) => [terminal.id, terminal])),
4306
- new NodeSet(responseCalls)
4577
+ new NodeSet(responseCalls),
4578
+ formatSwitch
4307
4579
  );
4308
4580
  return {
4309
4581
  statements: lowerer.lowerBlock(body),
@@ -4464,6 +4736,14 @@ function enclosingStatement(call, body) {
4464
4736
  return current;
4465
4737
  }
4466
4738
  function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
4739
+ return invocationSites(
4740
+ definitionNode,
4741
+ inherited,
4742
+ keepsArglessCall,
4743
+ facts
4744
+ ).map((site) => site.effect);
4745
+ }
4746
+ function invocationSites(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
4467
4747
  const read = methodBody(definitionNode);
4468
4748
  if (read === null) {
4469
4749
  return [];
@@ -4476,7 +4756,7 @@ function moduleScopeInvocationEffects(root, inherited = NO_INHERITED_METHODS, ke
4476
4756
  inherited,
4477
4757
  keepsArglessCall,
4478
4758
  facts
4479
- );
4759
+ ).map((site) => site.effect);
4480
4760
  }
4481
4761
  function effectsOfBody(read, inherited, keepsArglessCall, facts) {
4482
4762
  const body = read.body;
@@ -4510,19 +4790,18 @@ function effectsOfBody(read, inherited, keepsArglessCall, facts) {
4510
4790
  statement === void 0 ? void 0 : enumerated.byTerminal.get(statement)
4511
4791
  );
4512
4792
  return {
4513
- type: "invocation",
4514
- callee: calleeText(call),
4515
- args: argsOf(call, facts),
4516
- async: false,
4517
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4793
+ call,
4794
+ effect: {
4795
+ type: "invocation",
4796
+ callee: calleeText(call),
4797
+ args: argsOf(call, facts),
4798
+ async: false,
4799
+ ...conditions.length > 0 ? { preconditions: conditions } : {}
4800
+ }
4518
4801
  };
4519
4802
  });
4520
4803
  }
4521
4804
 
4522
- // src/responseStatus.ts
4523
- var import_extractor4 = require("@suss/extractor");
4524
- var import_values12 = require("@suss/values");
4525
-
4526
4805
  // src/paths/predicates.ts
4527
4806
  var OPERATORS = {
4528
4807
  "==": "eq",
@@ -4541,11 +4820,11 @@ var opaqueOf = (node) => ({
4541
4820
  var JOINING = /* @__PURE__ */ new Set(["&&", "||", "and", "or"]);
4542
4821
  function predicateOf(node, inputs) {
4543
4822
  if (node.type === "parenthesized_statements") {
4544
- const inner = node.namedChildren[0];
4823
+ const inner = node.namedChild(0);
4545
4824
  return inner == null ? opaqueOf(node) : predicateOf(inner, inputs);
4546
4825
  }
4547
4826
  if (node.type === "unary" && field(node, "operator")?.text === "!") {
4548
- const operand2 = field(node, "operand") ?? node.namedChildren[0];
4827
+ const operand2 = field(node, "operand") ?? node.namedChild(0);
4549
4828
  return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2, inputs) };
4550
4829
  }
4551
4830
  if (node.type === "binary") {
@@ -4571,11 +4850,11 @@ function predicateOf(node, inputs) {
4571
4850
  }
4572
4851
  function operandsOf(node) {
4573
4852
  if (node.type === "parenthesized_statements") {
4574
- const inner = node.namedChildren[0];
4853
+ const inner = node.namedChild(0);
4575
4854
  return inner == null ? [] : operandsOf(inner);
4576
4855
  }
4577
4856
  if (node.type === "unary" && field(node, "operator")?.text === "!") {
4578
- const operand2 = field(node, "operand") ?? node.namedChildren[0];
4857
+ const operand2 = field(node, "operand") ?? node.namedChild(0);
4579
4858
  return operand2 == null ? [] : operandsOf(operand2);
4580
4859
  }
4581
4860
  if (node.type === "binary") {
@@ -4671,8 +4950,317 @@ function calledMethod(node) {
4671
4950
  return field(node, "method")?.text ?? null;
4672
4951
  }
4673
4952
 
4674
- // src/provenance.ts
4953
+ // src/responseHandOff.ts
4675
4954
  var import_extractor3 = require("@suss/extractor");
4955
+
4956
+ // src/exceptionClasses.ts
4957
+ var ROOT_EXCEPTION = "Exception";
4958
+ var RUBY_EXCEPTION_PARENTS = {
4959
+ NoMemoryError: ROOT_EXCEPTION,
4960
+ ScriptError: ROOT_EXCEPTION,
4961
+ LoadError: "ScriptError",
4962
+ NotImplementedError: "ScriptError",
4963
+ SyntaxError: "ScriptError",
4964
+ SecurityError: ROOT_EXCEPTION,
4965
+ SignalException: ROOT_EXCEPTION,
4966
+ Interrupt: "SignalException",
4967
+ StandardError: ROOT_EXCEPTION,
4968
+ SystemExit: ROOT_EXCEPTION,
4969
+ SystemStackError: ROOT_EXCEPTION,
4970
+ ArgumentError: "StandardError",
4971
+ UncaughtThrowError: "ArgumentError",
4972
+ EncodingError: "StandardError",
4973
+ FiberError: "StandardError",
4974
+ IOError: "StandardError",
4975
+ EOFError: "IOError",
4976
+ IndexError: "StandardError",
4977
+ KeyError: "IndexError",
4978
+ StopIteration: "IndexError",
4979
+ ClosedQueueError: "StopIteration",
4980
+ LocalJumpError: "StandardError",
4981
+ NameError: "StandardError",
4982
+ NoMethodError: "NameError",
4983
+ RangeError: "StandardError",
4984
+ FloatDomainError: "RangeError",
4985
+ RegexpError: "StandardError",
4986
+ RuntimeError: "StandardError",
4987
+ FrozenError: "RuntimeError",
4988
+ SystemCallError: "StandardError",
4989
+ ThreadError: "StandardError",
4990
+ TypeError: "StandardError",
4991
+ ZeroDivisionError: "StandardError"
4992
+ };
4993
+ var MESSAGE_ONLY_EXCEPTION = "RuntimeError";
4994
+ var ExceptionReader = class {
4995
+ constructor(lookup, library) {
4996
+ this.library = library;
4997
+ this.walking = {
4998
+ ...lookup,
4999
+ ancestryRootClassNames: [
5000
+ ...Object.keys(library),
5001
+ ...Object.keys(RUBY_EXCEPTION_PARENTS),
5002
+ ROOT_EXCEPTION
5003
+ ]
5004
+ };
5005
+ }
5006
+ library;
5007
+ walking;
5008
+ known = /* @__PURE__ */ new Map();
5009
+ read(ref) {
5010
+ const key = [ref.text, ...ref.candidates].join("\0");
5011
+ let found = this.known.get(key);
5012
+ if (found === void 0) {
5013
+ found = readException(ref, this.walking, this.library);
5014
+ this.known.set(key, found);
5015
+ }
5016
+ return found;
5017
+ }
5018
+ };
5019
+ function listedException(name, library) {
5020
+ const fromLibrary = library[name];
5021
+ if (fromLibrary !== void 0) {
5022
+ return {
5023
+ name,
5024
+ ancestors: fromLibrary.ancestors,
5025
+ incomplete: false,
5026
+ inheritableByUnread: false
5027
+ };
5028
+ }
5029
+ const fromRuby = rubyAncestors(name);
5030
+ return fromRuby === null ? null : {
5031
+ name,
5032
+ ancestors: fromRuby,
5033
+ incomplete: false,
5034
+ inheritableByUnread: true
5035
+ };
5036
+ }
5037
+ function unreadException(name) {
5038
+ return {
5039
+ name,
5040
+ ancestors: [ROOT_EXCEPTION],
5041
+ incomplete: true,
5042
+ inheritableByUnread: true
5043
+ };
5044
+ }
5045
+ function classesOf(exception) {
5046
+ return [exception.name, ...exception.ancestors];
5047
+ }
5048
+ async function readException(ref, lookup, library) {
5049
+ for (const candidate of ref.candidates) {
5050
+ const known = listedException(candidate, library);
5051
+ if (known !== null) {
5052
+ return known;
5053
+ }
5054
+ const defined = await reachConstant([candidate], lookup);
5055
+ if (defined !== null) {
5056
+ return projectException(defined, lookup, library);
5057
+ }
5058
+ }
5059
+ return unreadException(ref.candidates.at(-1) ?? ref.text);
5060
+ }
5061
+ async function projectException(defined, lookup, library) {
5062
+ const chain = await ancestryOf(defined.name, defined.blocks, lookup);
5063
+ const ancestors = [];
5064
+ let incomplete = false;
5065
+ for (const entry of chain) {
5066
+ if (entry.type === "bodies" && entry.name === defined.name) {
5067
+ continue;
5068
+ }
5069
+ ancestors.push(entry.name);
5070
+ if (entry.type === "root") {
5071
+ ancestors.push(
5072
+ ...listedException(entry.name, library)?.ancestors ?? []
5073
+ );
5074
+ }
5075
+ if (entry.type === "unfollowed") {
5076
+ incomplete = true;
5077
+ }
5078
+ }
5079
+ if (incomplete && !ancestors.includes(ROOT_EXCEPTION)) {
5080
+ ancestors.push(ROOT_EXCEPTION);
5081
+ }
5082
+ return {
5083
+ name: defined.name,
5084
+ ancestors,
5085
+ incomplete,
5086
+ inheritableByUnread: false
5087
+ };
5088
+ }
5089
+ function rubyAncestors(name) {
5090
+ if (name === ROOT_EXCEPTION) {
5091
+ return [];
5092
+ }
5093
+ if (RUBY_EXCEPTION_PARENTS[name] === void 0) {
5094
+ return null;
5095
+ }
5096
+ const ancestors = [];
5097
+ for (let parent = RUBY_EXCEPTION_PARENTS[name]; parent !== void 0; parent = RUBY_EXCEPTION_PARENTS[parent]) {
5098
+ ancestors.push(parent);
5099
+ }
5100
+ return ancestors;
5101
+ }
5102
+
5103
+ // src/raises.ts
5104
+ var RESCUING_BODIES = /* @__PURE__ */ new Set(["begin", "body_statement"]);
5105
+ var AFTER_CATCH = /* @__PURE__ */ new Set(["rescue", "else", "ensure"]);
5106
+ function escapingRaises(method) {
5107
+ const body = field(method, "body");
5108
+ if (body === null) {
5109
+ return [];
5110
+ }
5111
+ const found = [];
5112
+ const visit = (node) => {
5113
+ for (const child of children(node)) {
5114
+ if (child === null || OWN_BODY_TYPES.has(child.type)) {
5115
+ continue;
5116
+ }
5117
+ if (isRaise(child) && !caughtLocally(child, method)) {
5118
+ found.push(child);
5119
+ continue;
5120
+ }
5121
+ visit(child);
5122
+ }
5123
+ };
5124
+ visit(body);
5125
+ return found;
5126
+ }
5127
+ function caughtLocally(raise, method) {
5128
+ let child = raise;
5129
+ for (let parent = raise.parent; parent !== null && parent.id !== method.id; parent = parent.parent) {
5130
+ if (rescues(parent, child)) {
5131
+ return true;
5132
+ }
5133
+ child = parent;
5134
+ }
5135
+ return false;
5136
+ }
5137
+ function rescues(parent, child) {
5138
+ if (parent.type === "rescue_modifier") {
5139
+ return field(parent, "body")?.id === child.id;
5140
+ }
5141
+ return RESCUING_BODIES.has(parent.type) && !AFTER_CATCH.has(child.type) && children(parent).some((one) => one?.type === "rescue");
5142
+ }
5143
+ function raisedClassRef(call, nesting) {
5144
+ const first = readCallArgs(field(call, "arguments")).positional[0];
5145
+ if (first === void 0) {
5146
+ return null;
5147
+ }
5148
+ if (first.type === "string") {
5149
+ return {
5150
+ text: MESSAGE_ONLY_EXCEPTION,
5151
+ candidates: [MESSAGE_ONLY_EXCEPTION]
5152
+ };
5153
+ }
5154
+ const constant2 = constructedClass(first) ?? first;
5155
+ const candidates = constantRefCandidates(constant2, nesting);
5156
+ return candidates.length === 0 ? null : { text: constant2.text, candidates };
5157
+ }
5158
+ function constructedClass(node) {
5159
+ if (node.type !== "call" || field(node, "method")?.text !== "new") {
5160
+ return null;
5161
+ }
5162
+ return field(node, "receiver");
5163
+ }
5164
+
5165
+ // src/responseHandOff.ts
5166
+ function raiseEndings(method) {
5167
+ return escapingRaises(method).map((call) => ({
5168
+ call,
5169
+ terminal: raiseTerminal(call)
5170
+ }));
5171
+ }
5172
+ function raiseTerminal(call) {
5173
+ const [first, second] = readCallArgs(field(call, "arguments")).positional;
5174
+ const message = first === void 0 ? null : stringLiteralValue(first);
5175
+ return {
5176
+ kind: "throw",
5177
+ statusCode: null,
5178
+ body: null,
5179
+ exceptionType: raisedClassRef(call, [])?.text ?? null,
5180
+ message: message ?? (second === void 0 ? null : stringLiteralValue(second)),
5181
+ component: null,
5182
+ renderTree: null,
5183
+ delegateTarget: null,
5184
+ emitEvent: null,
5185
+ location: rangeOf(call)
5186
+ };
5187
+ }
5188
+ function throughResponseHelpers(branches, sites, options) {
5189
+ const handOffs = [];
5190
+ for (const { call, effect } of sites) {
5191
+ const helper = helperBranchesAt(call, options);
5192
+ if (helper !== null) {
5193
+ handOffs.push({
5194
+ helper,
5195
+ preconditions: effect.preconditions,
5196
+ line: rangeOf(call).start,
5197
+ at: rangeOf(call)
5198
+ });
5199
+ }
5200
+ }
5201
+ return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => (0, import_extractor3.splitAtHandOffs)(branch, handOffs));
5202
+ }
5203
+ function helperBranchesAt(call, options) {
5204
+ if (call.type !== "call") {
5205
+ return null;
5206
+ }
5207
+ const args = readCallArgs(field(call, "arguments"));
5208
+ const handedAt = args.positional.flatMap(
5209
+ (argument, position) => argument.type === "identifier" && refersToResponse(argument, options) ? [{ position, variable: argument.text }] : []
5210
+ );
5211
+ const handedAs = Object.entries(args.keyword).flatMap(
5212
+ ([name, argument]) => argument.type === "identifier" && refersToResponse(argument, options) ? [{ name, variable: argument.text }] : []
5213
+ );
5214
+ if (handedAt.length === 0 && handedAs.length === 0) {
5215
+ return null;
5216
+ }
5217
+ const helper = calledDefinition(call, options.facts);
5218
+ if (helper === null) {
5219
+ return null;
5220
+ }
5221
+ const passed = /* @__PURE__ */ new Map();
5222
+ const names = parametersOf(helper).map(
5223
+ (parameter) => paramNameOf(parameter)?.text ?? null
5224
+ );
5225
+ for (const { position, variable } of handedAt) {
5226
+ const name = names[position];
5227
+ if (name !== void 0 && name !== null) {
5228
+ passed.set(name, variable);
5229
+ }
5230
+ }
5231
+ for (const { name, variable } of handedAs) {
5232
+ if (names.includes(name)) {
5233
+ passed.set(name, variable);
5234
+ }
5235
+ }
5236
+ return passed.size === 0 ? null : (0, import_extractor3.helperPathsOnVariables)(options.readHelper(helper), passed);
5237
+ }
5238
+ function refersToResponse(name, options) {
5239
+ const written = writtenNodeOf(name, options.facts);
5240
+ if (written === null) {
5241
+ return false;
5242
+ }
5243
+ if (sameNode(written, options.request)) {
5244
+ return true;
5245
+ }
5246
+ const member = written.type === "call" ? field(written, "method") : null;
5247
+ const receiver = written.type === "call" ? field(written, "receiver") : null;
5248
+ if (member === null || receiver?.type !== "identifier" || !options.bodyMethods.includes(member.text)) {
5249
+ return false;
5250
+ }
5251
+ const parsedFrom = writtenNodeOf(receiver, options.facts);
5252
+ return parsedFrom !== null && sameNode(parsedFrom, options.request);
5253
+ }
5254
+ function sameNode(a, b) {
5255
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.tree === b.tree;
5256
+ }
5257
+
5258
+ // src/responseStatus.ts
5259
+ var import_extractor6 = require("@suss/extractor");
5260
+ var import_values13 = require("@suss/values");
5261
+
5262
+ // src/provenance.ts
5263
+ var import_extractor4 = require("@suss/extractor");
4676
5264
  var import_values11 = require("@suss/values");
4677
5265
 
4678
5266
  // src/storage.ts
@@ -4738,7 +5326,7 @@ function pickedSource(call, loader) {
4738
5326
  return null;
4739
5327
  }
4740
5328
  const args = field(pick, "arguments");
4741
- const given = args === null ? void 0 : args.namedChildren[source.at];
5329
+ const given = args === null ? void 0 : args.namedChild(source.at);
4742
5330
  return given?.type === "constant" || given?.type === "scope_resolution" ? given : null;
4743
5331
  }
4744
5332
 
@@ -5383,10 +5971,7 @@ function recognizeMethodStorage(method, file, options) {
5383
5971
  return { effects, slots };
5384
5972
  }
5385
5973
  function callsUnder(node, found = []) {
5386
- for (const child of node.namedChildren) {
5387
- if (child === null) {
5388
- continue;
5389
- }
5974
+ for (const child of children(node)) {
5390
5975
  if (child.type === "call") {
5391
5976
  found.push(child);
5392
5977
  }
@@ -5528,7 +6113,7 @@ function valueSources(values, context, question = "wantedSource") {
5528
6113
  const spelling = spellingFor(context);
5529
6114
  const found = new NodeMap();
5530
6115
  for (const [value, key] of keys) {
5531
- found.set(value, (0, import_extractor3.sourceRefsOf)(leaves.get(key) ?? [], spelling));
6116
+ found.set(value, (0, import_extractor4.sourceRefsOf)(leaves.get(key) ?? [], spelling));
5532
6117
  }
5533
6118
  return found;
5534
6119
  }
@@ -5582,16 +6167,121 @@ function bareMethodOf(node) {
5582
6167
  if (node.type !== "call" || field(node, "arguments") !== null) {
5583
6168
  return null;
5584
6169
  }
5585
- return field(node, "method")?.text ?? null;
6170
+ return field(node, "method")?.text ?? null;
6171
+ }
6172
+ function literalAt(key, facts) {
6173
+ const node = nodeOfResolutionKey(key, facts);
6174
+ if (node === null) {
6175
+ return void 0;
6176
+ }
6177
+ const value = evaluatedValue(node, facts);
6178
+ const constant2 = (0, import_values11.constantOf)(value);
6179
+ return constant2 !== void 0 ? constant2 : (0, import_values11.literalOf)(value) ?? void 0;
6180
+ }
6181
+
6182
+ // src/raisedStatuses.ts
6183
+ function rescuedBy(handlers, exception) {
6184
+ if (handlers === "eachRoute") {
6185
+ return true;
6186
+ }
6187
+ const classes = new Set(classesOf(exception));
6188
+ return handlers.some(
6189
+ (caught) => caught.someUnread || caught.classes.some(
6190
+ (one) => classes.has(one.name) || exception.incomplete && one.inheritableByUnread
6191
+ )
6192
+ );
6193
+ }
6194
+ function raisedTerminal(pattern, exception, handlers, location) {
6195
+ const libraryStatus = exception === null ? void 0 : pattern.libraryExceptions?.[exception.name]?.status;
6196
+ const status = exception === null || rescuedBy(handlers, exception) ? void 0 : libraryStatus;
6197
+ return {
6198
+ kind: status === void 0 ? "throw" : "response",
6199
+ statusCode: status === void 0 ? null : { type: "literal", value: status },
6200
+ body: null,
6201
+ exceptionType: status === void 0 ? exception?.name ?? null : null,
6202
+ ...status === void 0 && exception !== null ? {
6203
+ exceptionAncestry: {
6204
+ ancestors: exception.ancestors,
6205
+ incomplete: exception.incomplete
6206
+ }
6207
+ } : {},
6208
+ ...status === void 0 && libraryStatus !== void 0 ? { statusWhenUncaught: libraryStatus } : {},
6209
+ message: null,
6210
+ component: null,
6211
+ renderTree: null,
6212
+ delegateTarget: null,
6213
+ emitEvent: null,
6214
+ location
6215
+ };
6216
+ }
6217
+ function raisedStatusBranches(pattern, storage, effects, handlers, location) {
6218
+ const operations = new Set(
6219
+ (effects ?? []).flatMap(
6220
+ (effect) => effect.type === "interaction" && effect.interaction.class === "storage-access" && effect.interaction.operation !== void 0 ? [effect.interaction.operation] : []
6221
+ )
6222
+ );
6223
+ const raised = new Set(
6224
+ storage.flatMap(
6225
+ (one) => (one.raises ?? []).flatMap(
6226
+ (raise) => raise.methods.some((method) => operations.has(method)) ? [raise.exception] : []
6227
+ )
6228
+ )
6229
+ );
6230
+ return [...raised].flatMap((name) => {
6231
+ const exception = listedException(name, pattern.libraryExceptions ?? {}) ?? unreadException(name);
6232
+ const terminal = raisedTerminal(pattern, exception, handlers, location);
6233
+ if (terminal.kind === "throw" && !rescuedBy(handlers, exception)) {
6234
+ return [];
6235
+ }
6236
+ return [
6237
+ {
6238
+ conditions: [
6239
+ {
6240
+ sourceText: `${name} raised`,
6241
+ structured: null,
6242
+ polarity: "positive",
6243
+ source: "earlyThrow"
6244
+ }
6245
+ ],
6246
+ terminal,
6247
+ effects: [],
6248
+ location,
6249
+ isDefault: false
6250
+ }
6251
+ ];
6252
+ });
6253
+ }
6254
+
6255
+ // src/responseBody.ts
6256
+ var import_extractor5 = require("@suss/extractor");
6257
+ var import_values12 = require("@suss/values");
6258
+ var BODY_KINDS = /* @__PURE__ */ new Set([
6259
+ "record",
6260
+ "sequence",
6261
+ "unbounded"
6262
+ ]);
6263
+ function serializedValue(node, serializers) {
6264
+ const receiver = field(node, "receiver");
6265
+ const method = field(node, "method")?.text;
6266
+ if (node.type !== "call" || receiver === null || method === void 0 || !serializers.has(method) || field(node, "arguments") !== null) {
6267
+ return node;
6268
+ }
6269
+ return serializedValue(receiver, serializers);
5586
6270
  }
5587
- function literalAt(key, facts) {
5588
- const node = nodeOfResolutionKey(key, facts);
5589
- if (node === null) {
5590
- return void 0;
6271
+ function bodyReadingOfCall(call, declaration, facts) {
6272
+ if (declaration.bodyKeyword === void 0 || call.type !== "call") {
6273
+ return import_extractor5.absentReading;
5591
6274
  }
5592
- const value = evaluatedValue(node, facts);
5593
- const constant2 = (0, import_values11.constantOf)(value);
5594
- return constant2 !== void 0 ? constant2 : (0, import_values11.literalOf)(value) ?? void 0;
6275
+ const argument = readCallArgs(field(call, "arguments")).keyword[declaration.bodyKeyword];
6276
+ if (argument === void 0) {
6277
+ return import_extractor5.absentReading;
6278
+ }
6279
+ const body = serializedValue(
6280
+ argument,
6281
+ new Set(declaration.bodySerializers ?? [])
6282
+ );
6283
+ const value = evaluatedValue(body, facts);
6284
+ return BODY_KINDS.has(value.kind) ? (0, import_extractor5.writtenReading)((0, import_values12.shapeOfValue)(value), rangeOf(argument)) : import_extractor5.absentReading;
5595
6285
  }
5596
6286
 
5597
6287
  // src/responseStatus.ts
@@ -5606,13 +6296,17 @@ function statusArgumentOf(call, declaration) {
5606
6296
  }
5607
6297
  return args.positional[declaration.statusArgument] ?? null;
5608
6298
  }
5609
- function statusNumberOf(node, names, facts) {
6299
+ function statusNumberOf(node, names, facts, bound = /* @__PURE__ */ new Map()) {
6300
+ const passed = node.type === "identifier" ? bound.get(node.text) : void 0;
6301
+ if (passed !== void 0) {
6302
+ return passed;
6303
+ }
5610
6304
  const value = evaluatedValue(node, facts);
5611
- const constant2 = (0, import_values12.constantOf)(value);
6305
+ const constant2 = (0, import_values13.constantOf)(value);
5612
6306
  if (typeof constant2 === "number") {
5613
6307
  return constant2;
5614
6308
  }
5615
- const name = (0, import_values12.literalOf)(value);
6309
+ const name = (0, import_values13.literalOf)(value);
5616
6310
  return name === null ? null : names[name] ?? null;
5617
6311
  }
5618
6312
  function declarationsByName(declarations) {
@@ -5630,21 +6324,42 @@ function declarationOf(node, byName, locals) {
5630
6324
  const name = field(node, "method")?.text;
5631
6325
  return name === void 0 ? void 0 : byName.get(name);
5632
6326
  }
5633
- function collectResponseCalls(node, byName, locals, found) {
5634
- for (const child of node.namedChildren) {
6327
+ function responderOf(node, responders) {
6328
+ const declaration = declarationOf(node, responders.byName, responders.locals);
6329
+ if (declaration !== void 0) {
6330
+ return { kind: "declared", declaration };
6331
+ }
6332
+ const name = receiverlessName(node, responders.locals);
6333
+ const reading = name === null || responders.helper === void 0 ? null : responders.helper(name, node);
6334
+ return reading === null ? void 0 : { kind: "helper", reading };
6335
+ }
6336
+ function receiverlessName(node, locals) {
6337
+ if (node.type === "identifier") {
6338
+ return isBareMethodCall(node, locals) ? node.text : null;
6339
+ }
6340
+ if (node.type !== "call" || field(node, "receiver") !== null) {
6341
+ return null;
6342
+ }
6343
+ return field(node, "method")?.text ?? null;
6344
+ }
6345
+ function readingOfResponder(call, responder, names, options) {
6346
+ return responder.kind === "helper" ? responder.reading : readingOfCall(call, responder.declaration, names, options);
6347
+ }
6348
+ function collectResponseCalls(node, responders, found) {
6349
+ for (const child of children(node)) {
5635
6350
  if (child === null || OWN_BODY_TYPES.has(child.type)) {
5636
6351
  continue;
5637
6352
  }
5638
- if (declarationOf(child, byName, locals) !== void 0) {
6353
+ if (responderOf(child, responders) !== void 0) {
5639
6354
  found.push(child);
5640
6355
  continue;
5641
6356
  }
5642
- collectResponseCalls(child, byName, locals, found);
6357
+ collectResponseCalls(child, responders, found);
5643
6358
  }
5644
6359
  return found;
5645
6360
  }
5646
6361
  function collectReturns(node, found) {
5647
- for (const child of node.namedChildren) {
6362
+ for (const child of children(node)) {
5648
6363
  if (child === null || OWN_BODY_TYPES.has(child.type)) {
5649
6364
  continue;
5650
6365
  }
@@ -5656,23 +6371,28 @@ function collectReturns(node, found) {
5656
6371
  }
5657
6372
  return found;
5658
6373
  }
5659
- function readingOfCall(call, declaration, names, facts) {
6374
+ function readingOfCall(call, declaration, names, options) {
5660
6375
  const argument = statusArgumentOf(call, declaration);
5661
6376
  if (argument === null) {
5662
- return declaration.defaultStatusCode === void 0 ? import_extractor4.absentReading : (0, import_extractor4.writtenReading)(declaration.defaultStatusCode, rangeOf(call));
6377
+ return declaration.defaultStatusCode === void 0 ? import_extractor6.absentReading : (0, import_extractor6.writtenReading)(declaration.defaultStatusCode, rangeOf(call));
5663
6378
  }
5664
- const status = statusNumberOf(argument, names, facts);
6379
+ const status = statusNumberOf(
6380
+ argument,
6381
+ names,
6382
+ options.facts,
6383
+ options.boundStatuses
6384
+ );
5665
6385
  if (status === null) {
5666
- return (0, import_extractor4.unreadableReading)(
6386
+ return (0, import_extractor6.unreadableReading)(
5667
6387
  "This response writes a status that does not settle on a number here, so this outcome claims none",
5668
6388
  rangeOf(argument)
5669
6389
  );
5670
6390
  }
5671
- return (0, import_extractor4.writtenReading)(status, rangeOf(argument));
6391
+ return (0, import_extractor6.writtenReading)(status, rangeOf(argument));
5672
6392
  }
5673
6393
  function effectsReaching(effects, conditions) {
5674
6394
  return effects.filter(
5675
- (effect) => (0, import_extractor4.guardsHoldOn)(
6395
+ (effect) => (0, import_extractor6.guardsHoldOn)(
5676
6396
  effect.type === "invocation" ? effect.preconditions : void 0,
5677
6397
  conditions
5678
6398
  )
@@ -5697,6 +6417,45 @@ function conditionInputs(method, paths, facts) {
5697
6417
  );
5698
6418
  return operands.length === 0 ? void 0 : guardInputs(operands, { facts, unit: method });
5699
6419
  }
6420
+ function boundStatusArguments(helper, call, pattern, facts) {
6421
+ const parameters = children(field(helper, "parameters")).filter(
6422
+ (child) => child.type === "identifier"
6423
+ );
6424
+ const args = call.type === "call" ? readCallArgs(field(call, "arguments")).positional : [];
6425
+ const bound = /* @__PURE__ */ new Map();
6426
+ parameters.forEach((parameter, index) => {
6427
+ const arg = args[index];
6428
+ const status = arg === void 0 ? null : statusNumberOf(arg, pattern.statusCodeNames ?? {}, facts);
6429
+ if (status !== null) {
6430
+ bound.set(parameter.text, status);
6431
+ }
6432
+ });
6433
+ return bound;
6434
+ }
6435
+ function helperResponse(helper, pattern, facts, boundStatuses = /* @__PURE__ */ new Map()) {
6436
+ const branches = responseBranches(helper, pattern, [], void 0, {
6437
+ fallthrough: "handOn",
6438
+ facts,
6439
+ boundStatuses
6440
+ });
6441
+ if (branches === null || branches.some((branch) => branch.terminal.kind !== "response")) {
6442
+ return null;
6443
+ }
6444
+ const statuses = new Set(
6445
+ branches.map((branch) => {
6446
+ const reading = branch.statusCodeReading?.reading;
6447
+ if (reading?.kind === "absent") {
6448
+ return branch.statusCodeReading?.libraryDefault ?? null;
6449
+ }
6450
+ return reading?.kind === "written" ? reading.value : null;
6451
+ })
6452
+ );
6453
+ const [only] = [...statuses];
6454
+ return statuses.size === 1 && typeof only === "number" ? (0, import_extractor6.writtenReading)(only, rangeOf(helper)) : (0, import_extractor6.unreadableReading)(
6455
+ "This response is sent by a helper whose status does not settle on one number here, so this outcome claims none",
6456
+ rangeOf(helper)
6457
+ );
6458
+ }
5700
6459
  function branchOf(outcome, pattern, effects, extraEffects, options) {
5701
6460
  if (outcome.fellThrough === true && options.fallthrough === "handOn") {
5702
6461
  return handsOnBranch(outcome, effects, extraEffects);
@@ -5719,6 +6478,7 @@ function branchOf(outcome, pattern, effects, extraEffects, options) {
5719
6478
  reading: outcome.reading,
5720
6479
  libraryDefault: pattern.defaultStatusCode
5721
6480
  },
6481
+ ...outcome.body === void 0 ? {} : { bodyShapeReading: { reading: outcome.body } },
5722
6482
  effects: effectsReaching(effects, outcome.conditions),
5723
6483
  ...extraEffects === void 0 ? {} : { extraEffects },
5724
6484
  location: outcome.location,
@@ -5746,20 +6506,26 @@ function handsOnBranch(outcome, effects, extraEffects) {
5746
6506
  isDefault: outcome.conditions.length === 0
5747
6507
  };
5748
6508
  }
5749
- function returnPathBranches(method, effects, exits = [], facts) {
6509
+ function returnPathBranches(method, effects, exits = [], facts, keepsArms, raises2 = []) {
5750
6510
  const body = field(method, "body");
5751
6511
  if (body === null) {
5752
6512
  return null;
5753
6513
  }
5754
6514
  const returns = collectReturns(body, []);
5755
6515
  const exitCalls = exits.map((exit) => exit.call);
5756
- const lowered = lowerRubyBody(body, [...returns, ...exitCalls], exitCalls);
5757
- const enumerated = (0, import_extractor4.enumerateOrDegrade)(
6516
+ const terminals = [
6517
+ ...returns,
6518
+ ...exitCalls,
6519
+ ...raises2.map((raise) => raise.call)
6520
+ ];
6521
+ const lowered = lowerRubyBody(body, terminals, exitCalls);
6522
+ const enumerated = (0, import_extractor6.enumerateOrDegrade)(
5758
6523
  {
5759
6524
  statements: lowered.statements,
5760
- terminalsByStmt: lowered.terminalsByStmt
6525
+ terminalsByStmt: lowered.terminalsByStmt,
6526
+ ...keepsArms === void 0 ? {} : { keepsArms }
5761
6527
  },
5762
- [...returns, ...exitCalls]
6528
+ terminals
5763
6529
  );
5764
6530
  const inputs = conditionInputs(
5765
6531
  method,
@@ -5787,8 +6553,8 @@ function returnPathBranches(method, effects, exits = [], facts) {
5787
6553
  returnTerminal(rangeOf(statement))
5788
6554
  );
5789
6555
  }
5790
- for (const exit of exits) {
5791
- push(enumerated.byTerminal.get(exit.call) ?? [], exit.terminal);
6556
+ for (const ending of [...exits, ...raises2]) {
6557
+ push(enumerated.byTerminal.get(ending.call) ?? [], ending.terminal);
5792
6558
  }
5793
6559
  push(enumerated.fallthrough, returnTerminal(rangeOf(method)));
5794
6560
  return branches.length === 0 ? null : branches;
@@ -5813,21 +6579,30 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
5813
6579
  if (declarations.length === 0 || body === null) {
5814
6580
  return null;
5815
6581
  }
5816
- const byName = declarationsByName(declarations);
5817
- const locals = localNamesIn(method);
5818
- const responses = collectResponseCalls(body, byName, locals, []);
6582
+ const responders = {
6583
+ byName: declarationsByName(declarations),
6584
+ locals: localNamesIn(method),
6585
+ helper: options.respondingHelper
6586
+ };
6587
+ const responses = collectResponseCalls(body, responders, []);
5819
6588
  const returns = collectReturns(body, []);
5820
- const lowered = lowerRubyBody(body, returns, responses);
6589
+ const raises2 = options.raises === void 0 ? [] : escapingRaises(method);
6590
+ const lowered = lowerRubyBody(
6591
+ body,
6592
+ [...returns, ...raises2],
6593
+ responses,
6594
+ pattern.formatSwitch
6595
+ );
5821
6596
  const bareReturns = returns.filter(
5822
- (node) => lowered.terminalHome.has(node) && collectResponseCalls(node, byName, locals, []).length === 0
6597
+ (node) => lowered.terminalHome.has(node) && collectResponseCalls(node, responders, []).length === 0
5823
6598
  );
5824
6599
  const terminals = [...responses, ...bareReturns];
5825
- const enumerated = (0, import_extractor4.enumerateOrDegrade)(
6600
+ const enumerated = (0, import_extractor6.enumerateOrDegrade)(
5826
6601
  {
5827
6602
  statements: lowered.statements,
5828
6603
  terminalsByStmt: lowered.terminalsByStmt
5829
6604
  },
5830
- terminals
6605
+ [...terminals, ...raises2]
5831
6606
  );
5832
6607
  const statusNames = pattern.statusCodeNames ?? {};
5833
6608
  const inputs = conditionInputs(
@@ -5838,30 +6613,58 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
5838
6613
  const conditionsOf = (path7) => path7.map((condition) => conditionOf(condition, inputs));
5839
6614
  const outcomes = [];
5840
6615
  for (const terminal of terminals) {
5841
- const declaration = declarationOf(terminal, byName, locals);
5842
- const reading = declaration === void 0 ? import_extractor4.absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
6616
+ const responder = responderOf(terminal, responders);
6617
+ const reading = responder === void 0 ? import_extractor6.absentReading : readingOfResponder(terminal, responder, statusNames, options);
6618
+ const body2 = responder?.kind === "declared" ? bodyReadingOfCall(terminal, responder.declaration, options.facts) : void 0;
5843
6619
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
5844
6620
  outcomes.push({
5845
6621
  conditions: conditionsOf(path7),
5846
6622
  reading,
5847
- location: rangeOf(terminal)
6623
+ ...body2 === void 0 ? {} : { body: body2 },
6624
+ location: rangeOf(terminal),
6625
+ // A bare `return` wrote no response, so a filter that takes it
6626
+ // hands the request on the same as one that reaches its end.
6627
+ ...responder === void 0 ? { fellThrough: true } : {}
5848
6628
  });
5849
6629
  }
5850
6630
  }
5851
6631
  for (const path7 of enumerated.fallthrough) {
5852
6632
  outcomes.push({
5853
6633
  conditions: conditionsOf(path7),
5854
- reading: import_extractor4.absentReading,
6634
+ reading: import_extractor6.absentReading,
5855
6635
  location: rangeOf(method),
5856
6636
  fellThrough: true
5857
6637
  });
5858
6638
  }
5859
- if (outcomes.length === 0) {
6639
+ const read = options.raises;
6640
+ const raised = raises2.flatMap((call) => {
6641
+ const terminal = raisedTerminal(
6642
+ pattern,
6643
+ read?.classes.get(call) ?? null,
6644
+ read?.handlers ?? [],
6645
+ rangeOf(call)
6646
+ );
6647
+ return (enumerated.byTerminal.get(call) ?? []).map((path7) => {
6648
+ const conditions = conditionsOf(path7);
6649
+ return {
6650
+ conditions,
6651
+ terminal,
6652
+ effects: effectsReaching(effects, conditions),
6653
+ ...extraEffects === void 0 ? {} : { extraEffects },
6654
+ location: terminal.location,
6655
+ isDefault: false
6656
+ };
6657
+ });
6658
+ });
6659
+ if (outcomes.length === 0 && raised.length === 0) {
5860
6660
  return null;
5861
6661
  }
5862
- return outcomes.map(
5863
- (outcome) => branchOf(outcome, pattern, effects, extraEffects, options)
5864
- );
6662
+ return [
6663
+ ...outcomes.map(
6664
+ (outcome) => branchOf(outcome, pattern, effects, extraEffects, options)
6665
+ ),
6666
+ ...raised
6667
+ ];
5865
6668
  }
5866
6669
 
5867
6670
  // src/clientCalls.ts
@@ -5874,7 +6677,16 @@ function clientCallUnits(root, pack, pattern, options) {
5874
6677
  }
5875
6678
  for (const call of callsUnder2(method)) {
5876
6679
  for (const request of requestCalls(call, pattern, options)) {
5877
- units.push(clientUnit(method, name, request, pattern, pack, options));
6680
+ units.push(
6681
+ clientUnit(
6682
+ method,
6683
+ name,
6684
+ { ...request, call },
6685
+ pattern,
6686
+ pack,
6687
+ options
6688
+ )
6689
+ );
5878
6690
  }
5879
6691
  }
5880
6692
  }
@@ -5932,10 +6744,7 @@ function requestArgument(call, called, pattern) {
5932
6744
  return pattern.requestObject?.attribute === called ? args.positional[0] : void 0;
5933
6745
  }
5934
6746
  function methodDefinitions(node, found = []) {
5935
- for (const child of node.namedChildren) {
5936
- if (child === null) {
5937
- continue;
5938
- }
6747
+ for (const child of children(node)) {
5939
6748
  if (child.type === "method" || child.type === "singleton_method") {
5940
6749
  found.push(child);
5941
6750
  }
@@ -5944,10 +6753,7 @@ function methodDefinitions(node, found = []) {
5944
6753
  return found;
5945
6754
  }
5946
6755
  function callsUnder2(node, found = []) {
5947
- for (const child of node.namedChildren) {
5948
- if (child === null) {
5949
- continue;
5950
- }
6756
+ for (const child of children(node)) {
5951
6757
  if (child.type === "call") {
5952
6758
  found.push(child);
5953
6759
  }
@@ -5964,20 +6770,23 @@ function requestCalls(call, pattern, options) {
5964
6770
  const read = (site) => requestCall(
5965
6771
  call,
5966
6772
  called,
5967
- receiverPrefix(receiver, pattern, options, site),
6773
+ receiverBase(receiver, pattern, options, site),
5968
6774
  pattern,
5969
6775
  options,
5970
6776
  site
5971
6777
  );
5972
6778
  const plain = read();
5973
- if (plain !== null && !(0, import_behavioral_ir.hasNameHole)(plain.path)) {
6779
+ if (plain !== null && !(0, import_behavioral_ir2.hasNameHole)(plain.path)) {
5974
6780
  return [plain];
5975
6781
  }
5976
6782
  const byBoundary = /* @__PURE__ */ new Map();
5977
6783
  for (const site of constructionSitesOf(call, options.facts)) {
5978
6784
  const stated = read(site);
5979
6785
  if (stated !== null) {
5980
- byBoundary.set(`${stated.method} ${stated.path}`, stated);
6786
+ byBoundary.set(
6787
+ `${stated.method} ${stated.host ?? ""} ${stated.path}`,
6788
+ stated
6789
+ );
5981
6790
  }
5982
6791
  }
5983
6792
  if (byBoundary.size > 0) {
@@ -5985,12 +6794,12 @@ function requestCalls(call, pattern, options) {
5985
6794
  }
5986
6795
  return plain === null ? [] : [plain];
5987
6796
  }
5988
- function requestCall(call, called, prefix, pattern, options, site) {
6797
+ function requestCall(call, called, base, pattern, options, site) {
5989
6798
  const args = readCallArgs(field(call, "arguments"));
5990
6799
  const verb = pattern.verbMethodNames[called];
5991
6800
  if (verb !== void 0) {
5992
- const path8 = urlIn(args, pattern, options, site);
5993
- return path8 === null ? null : { method: verb, path: prefix + path8 };
6801
+ const target2 = targetAt(urlNodeIn(args, pattern), options, site);
6802
+ return target2 === null ? null : { method: verb, ...underBase(base, target2) };
5994
6803
  }
5995
6804
  const sent = pattern.requestObject;
5996
6805
  if (sent === void 0 || sent.attribute !== called) {
@@ -6000,8 +6809,14 @@ function requestCall(call, called, prefix, pattern, options, site) {
6000
6809
  if (built === null) {
6001
6810
  return null;
6002
6811
  }
6003
- const path7 = pathAt(built.url, options, site);
6004
- return path7 === null ? null : { method: built.method, path: prefix + path7 };
6812
+ const target = targetAt(built.url, options, site);
6813
+ return target === null ? null : { method: built.method, ...underBase(base, target) };
6814
+ }
6815
+ function underBase(base, target) {
6816
+ if (base === null || target.host !== void 0) {
6817
+ return target;
6818
+ }
6819
+ return { path: base.path + target.path, host: base.host };
6005
6820
  }
6006
6821
  function requestBuilt(argument, sent, options, site) {
6007
6822
  if (argument === void 0) {
@@ -6022,16 +6837,19 @@ function requestBuilt(argument, sent, options, site) {
6022
6837
  function isLibraryReceiver(receiver, pattern, options) {
6023
6838
  return namesConstant2(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
6024
6839
  }
6025
- function receiverPrefix(receiver, pattern, options, site) {
6840
+ function receiverBase(receiver, pattern, options, site) {
6026
6841
  const built = builderCallBehind(receiver, pattern, options.facts);
6027
6842
  if (built === null) {
6028
- return "";
6843
+ return null;
6029
6844
  }
6030
6845
  const args = readCallArgs(field(built, "arguments"));
6031
6846
  const keyword = pattern.builderUrlKeyword;
6032
6847
  const base = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[0];
6033
- const path7 = base === void 0 ? null : (0, import_values13.pathOf)(evaluatedValue(base, options.facts, void 0, site));
6034
- return path7 === void 0 || path7 === null ? "" : trimmed(path7);
6848
+ if (base === void 0) {
6849
+ return null;
6850
+ }
6851
+ const value = evaluatedValue(base, options.facts, void 0, site);
6852
+ return { path: trimmed((0, import_values14.pathOf)(value) ?? ""), host: (0, import_values14.hostOf)(value) };
6035
6853
  }
6036
6854
  function trimmed(path7) {
6037
6855
  return path7 === "/" ? "" : path7.replace(/\/+$/, "");
@@ -6054,22 +6872,32 @@ function builderCallBehind(receiver, pattern, facts) {
6054
6872
  const constant2 = field(written, "receiver");
6055
6873
  return constant2 !== null && namesConstant2(constant2, pattern.constantName) ? written : null;
6056
6874
  }
6057
- function urlIn(args, pattern, options, site) {
6058
- return pathAt(urlNodeIn(args, pattern), options, site);
6059
- }
6060
6875
  function urlNodeIn(args, pattern) {
6061
6876
  const keyword = pattern.url.keyword;
6062
6877
  return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
6063
6878
  }
6064
- function pathAt(written, options, site) {
6879
+ function targetAt(written, options, site) {
6065
6880
  if (written === void 0) {
6066
6881
  return null;
6067
6882
  }
6068
- const path7 = (0, import_values13.pathOf)(evaluatedValue(written, options.facts, void 0, site));
6069
- return path7 === void 0 || (0, import_behavioral_ir.namesNothing)(path7) ? null : path7;
6883
+ const value = evaluatedValue(written, options.facts, void 0, site);
6884
+ const path7 = (0, import_values14.pathOf)(value);
6885
+ return path7 === void 0 || (0, import_behavioral_ir2.namesNothing)(path7) ? null : { path: path7, host: (0, import_values14.hostOf)(value) };
6070
6886
  }
6071
6887
  function clientUnit(method, name, request, pattern, pack, options) {
6072
6888
  const range = rangeOf(method);
6889
+ const accessors = responseAccessors(pattern);
6890
+ const statusMembers = (0, import_behavioral_ir2.statusMembersOf)(accessors);
6891
+ const reading = {
6892
+ facts: options.facts,
6893
+ statusMembers,
6894
+ handOff: {
6895
+ request: request.call,
6896
+ bodyMethods: accessors.bodyAccessors ?? [],
6897
+ facts: options.facts,
6898
+ readHelper: (helper) => callerBranches(helper, { facts: options.facts, statusMembers })
6899
+ }
6900
+ };
6073
6901
  return {
6074
6902
  identity: {
6075
6903
  name,
@@ -6081,25 +6909,34 @@ function clientUnit(method, name, request, pattern, pack, options) {
6081
6909
  exportName: name,
6082
6910
  exportPath: [name]
6083
6911
  },
6084
- boundaryBinding: (0, import_behavioral_ir.restBinding)({
6912
+ boundaryBinding: (0, import_behavioral_ir2.restBinding)({
6085
6913
  transport: pack.protocol,
6086
6914
  method: request.method,
6087
6915
  path: request.path,
6916
+ host: request.host,
6088
6917
  recognition: pack.name
6089
6918
  }),
6090
6919
  parameters: [],
6091
- branches: callerBranches(method, range, options.facts),
6092
- ...responseAccessors(pattern),
6920
+ branches: callerBranches(method, reading),
6921
+ ...accessors,
6093
6922
  bodyContent: "statements",
6094
6923
  dependencyCalls: [],
6095
6924
  declaredContract: null
6096
6925
  };
6097
6926
  }
6098
- function callerBranches(method, range, facts) {
6099
- const effects = invocationEffects(method, void 0, void 0, facts);
6100
- return returnPathBranches(method, effects, [], facts) ?? [
6101
- returnBranch(range, effects)
6102
- ];
6927
+ function callerBranches(method, reading) {
6928
+ const sites = invocationSites(method, void 0, void 0, reading.facts);
6929
+ const effects = sites.map((site) => site.effect);
6930
+ const keepsArms = (condition) => (0, import_behavioral_ir2.testsStatus)(predicateOf(condition), reading.statusMembers);
6931
+ const found = returnPathBranches(
6932
+ method,
6933
+ effects,
6934
+ [],
6935
+ reading.facts,
6936
+ keepsArms,
6937
+ raiseEndings(method)
6938
+ ) ?? [returnBranch(rangeOf(method), effects)];
6939
+ return reading.handOff === void 0 ? found : throughResponseHelpers(found, sites, reading.handOff);
6103
6940
  }
6104
6941
  function responseAccessors(pattern) {
6105
6942
  const response = pattern.response;
@@ -6110,7 +6947,8 @@ function responseAccessors(pattern) {
6110
6947
  ...response.body === void 0 ? {} : { bodyAccessors: response.body },
6111
6948
  ...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
6112
6949
  ...response.success === void 0 ? {} : { successAccessors: response.success },
6113
- ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
6950
+ ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery },
6951
+ ...response.redirectDelivery === void 0 ? {} : { redirectDelivery: response.redirectDelivery }
6114
6952
  };
6115
6953
  }
6116
6954
  function returnBranch(range, effects) {
@@ -6135,8 +6973,8 @@ function returnBranch(range, effects) {
6135
6973
  }
6136
6974
 
6137
6975
  // src/envReads.ts
6138
- var import_behavioral_ir2 = require("@suss/behavioral-ir");
6139
- var import_extractor5 = require("@suss/extractor");
6976
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
6977
+ var import_extractor7 = require("@suss/extractor");
6140
6978
  var import_resolution9 = require("@suss/resolution");
6141
6979
 
6142
6980
  // src/envSpellings.ts
@@ -6161,9 +6999,7 @@ function elementSite(node) {
6161
6999
  if (object === null || !isEnv(object) || isAssignedTo(node)) {
6162
7000
  return null;
6163
7001
  }
6164
- const index = node.namedChildren.find(
6165
- (child) => child !== null && child.id !== object.id
6166
- );
7002
+ const index = children(node).find((child) => child.id !== object.id);
6167
7003
  if (index === void 0) {
6168
7004
  return null;
6169
7005
  }
@@ -6344,7 +7180,7 @@ function followsExitWhenAbsent(parent, child, subject) {
6344
7180
  if (!STATEMENT_LIST_TYPES.has(parent.type)) {
6345
7181
  return false;
6346
7182
  }
6347
- const statements = parent.namedChildren;
7183
+ const statements = children(parent);
6348
7184
  const at = statements.findIndex((statement) => statement?.id === child.id);
6349
7185
  return statements.slice(0, at).some(
6350
7186
  (statement) => statement !== null && leavesWhenAbsent(statement, subject)
@@ -6396,7 +7232,7 @@ function continuationRaises(from) {
6396
7232
  let parent = current.parent;
6397
7233
  while (parent !== null) {
6398
7234
  if (STATEMENT_LIST_TYPES.has(parent.type)) {
6399
- const statements = parent.namedChildren;
7235
+ const statements = children(parent);
6400
7236
  const at = statements.findIndex((one) => one?.id === current.id);
6401
7237
  const exit = firstExit(statements.slice(at + 1));
6402
7238
  if (exit !== null) {
@@ -6430,20 +7266,13 @@ function firstExit(statements) {
6430
7266
  return null;
6431
7267
  }
6432
7268
  function exitOf2(statement) {
6433
- if (isRaise3(statement)) {
7269
+ if (isRaiseCall(statement)) {
6434
7270
  return "raise";
6435
7271
  }
6436
7272
  if (LEAVING_TYPES.has(statement.type)) {
6437
7273
  return "leave";
6438
7274
  }
6439
- return STATEMENT_LIST_TYPES.has(statement.type) ? firstExit(statement.namedChildren) : null;
6440
- }
6441
- var RAISING_METHODS2 = /* @__PURE__ */ new Set(["raise", "fail"]);
6442
- function isRaise3(node) {
6443
- if (node.type === "identifier") {
6444
- return RAISING_METHODS2.has(node.text);
6445
- }
6446
- return node.type === "call" && field(node, "receiver") === null && RAISING_METHODS2.has(field(node, "method")?.text ?? "");
7275
+ return STATEMENT_LIST_TYPES.has(statement.type) ? firstExit(children(statement)) : null;
6447
7276
  }
6448
7277
  function isPresentWhen(test, outcome, subject) {
6449
7278
  const expression = peelParens(test);
@@ -6511,7 +7340,7 @@ function isNilCheckOn(call, receiver) {
6511
7340
  return call.type === "call" && field(call, "method")?.text === "nil?" && field(call, "receiver")?.id === receiver.id;
6512
7341
  }
6513
7342
  function peelParens(node) {
6514
- const inner = node.type === "parenthesized_statements" && node.namedChildCount === 1 ? node.namedChildren[0] : null;
7343
+ const inner = node.type === "parenthesized_statements" && node.namedChildCount === 1 ? node.namedChild(0) : null;
6515
7344
  return inner === null || inner === void 0 ? node : peelParens(inner);
6516
7345
  }
6517
7346
  function climbParens(node) {
@@ -6556,7 +7385,7 @@ var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method",
6556
7385
  function envReadEffects(root, facts) {
6557
7386
  const helpers = helperFactsOf(facts);
6558
7387
  const slots = [];
6559
- (0, import_extractor5.walkDescendants)(root, null, {
7388
+ (0, import_extractor7.walkDescendants)(root, null, children, {
6560
7389
  at: (node) => {
6561
7390
  const read = envReadAt(node);
6562
7391
  if (read !== null) {
@@ -6567,7 +7396,7 @@ function envReadEffects(root, facts) {
6567
7396
  slots.push({ call: node });
6568
7397
  }
6569
7398
  },
6570
- into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? import_extractor5.SKIP_CHILDREN : null
7399
+ into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? import_extractor7.SKIP_CHILDREN : null
6571
7400
  });
6572
7401
  const calls = slots.flatMap((slot) => "call" in slot ? [slot.call] : []);
6573
7402
  const throughHelpers = helpers === null ? new NodeMap() : helperReads(calls, helpers);
@@ -6636,7 +7465,7 @@ function namedParameters(db) {
6636
7465
  return named2;
6637
7466
  }
6638
7467
  function emitEnvFacts(db, file, root) {
6639
- (0, import_extractor5.walkDescendants)(root, null, {
7468
+ (0, import_extractor7.walkDescendants)(root, null, children, {
6640
7469
  at: (node) => {
6641
7470
  if (isEnv(node) && handsOnward(node)) {
6642
7471
  db.add("environmentObject", [
@@ -6734,7 +7563,7 @@ function readsAtCall(db, call, args) {
6734
7563
  function configReadEffect(read) {
6735
7564
  return {
6736
7565
  type: "interaction",
6737
- binding: (0, import_behavioral_ir2.runtimeConfigBinding)({ recognition: RUBY_ENV_RECOGNITION }),
7566
+ binding: (0, import_behavioral_ir3.runtimeConfigBinding)({ recognition: RUBY_ENV_RECOGNITION }),
6738
7567
  callee: `ENV["${read.name}"]`,
6739
7568
  interaction: {
6740
7569
  class: "config-read",
@@ -6764,6 +7593,7 @@ function envSiteAt(node) {
6764
7593
  }
6765
7594
 
6766
7595
  // src/filters.ts
7596
+ var RESCUES_NOTHING = { refs: [], someUnread: false };
6767
7597
  function controllerFilters(pattern, ancestry, read = {}) {
6768
7598
  const forms = pattern.filters ?? [];
6769
7599
  if (forms.length === 0) {
@@ -6777,18 +7607,29 @@ function controllerFilters(pattern, ancestry, read = {}) {
6777
7607
  }
6778
7608
  }
6779
7609
  let declared = [];
6780
- for (const { statement } of inheritedStatements(ancestry)) {
7610
+ const inlineSeen = /* @__PURE__ */ new Map();
7611
+ for (const { block, statement } of inheritedStatements(ancestry)) {
6781
7612
  const called = calledName(statement);
6782
7613
  if (called === null) {
6783
7614
  continue;
6784
7615
  }
6785
7616
  const form = byName.get(called);
6786
7617
  if (form !== void 0) {
6787
- for (const declaration of declarationsOf(statement, form, read.facts)) {
6788
- declared = [
6789
- ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
6790
- declaration
6791
- ];
7618
+ const inlineKey = `${block.file}\0${block.info.qualifiedName}\0${form.name}`;
7619
+ const ordinal = inlineSeen.get(inlineKey) ?? 0;
7620
+ const declarations = declarationsOf(
7621
+ statement,
7622
+ form,
7623
+ block,
7624
+ read.facts,
7625
+ ordinal
7626
+ );
7627
+ if (declarations.some((one) => one.inline !== void 0)) {
7628
+ inlineSeen.set(inlineKey, ordinal + 1);
7629
+ }
7630
+ for (const declaration of declarations) {
7631
+ const earlier = declaration.filter.onThrow === true ? void 0 : declared.find((one) => sameFilter(one, declaration));
7632
+ declared = [...declared.filter((one) => one !== earlier), declaration];
6792
7633
  }
6793
7634
  continue;
6794
7635
  }
@@ -6799,62 +7640,105 @@ function controllerFilters(pattern, ancestry, read = {}) {
6799
7640
  }
6800
7641
  const resolved = [];
6801
7642
  for (const declaration of declared) {
6802
- const found = methodInAncestry(ancestry, declaration.methodName, read);
6803
- if (found.type !== "found") {
7643
+ const written = writtenAt(
7644
+ declaration,
7645
+ filterMethod(ancestry, declaration.methodName, read)
7646
+ );
7647
+ if (written === null) {
6804
7648
  continue;
6805
7649
  }
6806
7650
  resolved.push({
6807
7651
  filter: declaration.filter,
6808
- methodName: declaration.methodName,
6809
- method: found.method,
6810
- file: found.block.file,
6811
- enclosingQualifiedName: found.block.info.qualifiedName,
7652
+ ...written,
6812
7653
  only: declaration.only,
6813
- except: declaration.except
7654
+ except: declaration.except,
7655
+ rescues: declaration.rescues
6814
7656
  });
6815
7657
  }
6816
7658
  return [
6817
7659
  ...resolved.filter((one) => one.filter.onThrow !== true),
6818
- ...resolved.filter((one) => one.filter.onThrow === true)
7660
+ ...resolved.filter((one) => one.filter.onThrow === true).reverse()
6819
7661
  ];
6820
7662
  }
7663
+ function writtenAt(declaration, found) {
7664
+ if (found.type === "found") {
7665
+ return {
7666
+ methodName: declaration.methodName,
7667
+ nameKind: "binding",
7668
+ method: found.method,
7669
+ file: found.block.file,
7670
+ enclosingQualifiedName: found.block.info.qualifiedName,
7671
+ nesting: found.block.info.bodyNesting
7672
+ };
7673
+ }
7674
+ const inline = declaration.inline;
7675
+ if (inline === void 0) {
7676
+ return null;
7677
+ }
7678
+ return {
7679
+ methodName: inline.label,
7680
+ nameKind: "label",
7681
+ method: inline.block,
7682
+ file: inline.declaredIn.file,
7683
+ enclosingQualifiedName: inline.declaredIn.info.qualifiedName,
7684
+ nesting: inline.declaredIn.info.bodyNesting
7685
+ };
7686
+ }
7687
+ function filterMethod(ancestry, name, read) {
7688
+ const found = methodInAncestry(ancestry, name, read);
7689
+ if (found.type === "unsettled" && found.cause === "unreadAncestor") {
7690
+ return methodPastUnreadAncestors(ancestry, name, read);
7691
+ }
7692
+ return found;
7693
+ }
6821
7694
  function filterCoversAction(filter, actionName) {
6822
7695
  if (filter.except.has(actionName)) {
6823
7696
  return false;
6824
7697
  }
6825
7698
  return filter.only === null || filter.only.has(actionName);
6826
7699
  }
6827
- function filterReference(filter, displayPath) {
7700
+ function filterReference(filter, displayPath, caught) {
6828
7701
  return {
6829
7702
  file: displayPath,
6830
7703
  name: filter.methodName,
6831
- ...filter.filter.onThrow === true ? { onThrow: true } : {}
7704
+ ...filter.filter.onThrow === true ? { onThrow: true } : {},
7705
+ ...caught === void 0 ? {} : {
7706
+ catches: caught.classes.map((one) => one.name),
7707
+ ...caught.classes.some((one) => one.inheritableByUnread) ? { mayCatchUnreadClasses: true } : {},
7708
+ ...caught.someUnread ? { mayCatchAny: true } : {}
7709
+ }
6832
7710
  };
6833
7711
  }
6834
- function filterUnit(filter, pattern, displayPath, body, facts) {
7712
+ function filterUnit(filter, pattern, displayPath, body, reading = {}) {
6835
7713
  const range = rangeOf(filter.method);
7714
+ const { respondingHelper, raises: raises2 } = reading;
6836
7715
  const branches = responseBranches(
6837
7716
  filter.method,
6838
7717
  pattern,
6839
7718
  body.effects ?? [],
6840
7719
  body.extraEffects,
6841
- { fallthrough: "handOn", facts }
7720
+ {
7721
+ fallthrough: "handOn",
7722
+ facts: reading.facts,
7723
+ ...respondingHelper === void 0 ? {} : { respondingHelper },
7724
+ ...raises2 === void 0 ? {} : { raises: raises2 }
7725
+ }
6842
7726
  );
6843
7727
  return {
6844
7728
  identity: {
6845
7729
  name: filter.methodName,
6846
- nameKind: "binding",
7730
+ nameKind: filter.nameKind,
6847
7731
  kind: "middleware",
6848
7732
  file: displayPath,
6849
7733
  range,
6850
7734
  span: spanOf(filter.method),
6851
- exportName: filter.methodName,
7735
+ exportName: filter.nameKind === "binding" ? filter.methodName : null,
6852
7736
  exportPath: [filter.enclosingQualifiedName, filter.methodName]
6853
7737
  },
6854
7738
  boundaryBinding: null,
6855
7739
  parameters: [],
6856
7740
  branches: withSlotSources(
6857
- branches ?? [handsOn(range, body)],
7741
+ [...branches ?? [handsOn(range, body)], ...reading.raised ?? []],
6858
7742
  body.provenance
6859
7743
  ),
6860
7744
  bodyContent: body.bodyContent ?? "absent",
@@ -6889,25 +7773,92 @@ function calledName(statement) {
6889
7773
  }
6890
7774
  return field(statement, "method")?.text ?? null;
6891
7775
  }
6892
- function declarationsOf(statement, filter, facts) {
7776
+ function declarationsOf(statement, filter, declaredIn, facts, inlineOrdinal) {
6893
7777
  const args = readCallArgs(field(statement, "arguments"));
6894
7778
  const keywords = filter.actionKeywords;
6895
7779
  const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
6896
7780
  const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
6897
- return methodNamesOf(args, filter, facts).map((methodName) => ({
7781
+ const rescues2 = filter.onThrow === true ? rescuedClasses(args, declaredIn.info.bodyNesting, facts) : RESCUES_NOTHING;
7782
+ const inline = inlineHandler(
7783
+ statement,
7784
+ args,
7785
+ filter,
7786
+ declaredIn,
7787
+ inlineOrdinal
7788
+ );
7789
+ return methodNamesOf(args, filter, facts, inline).map((methodName) => ({
6898
7790
  filter,
6899
7791
  methodName,
6900
7792
  only,
6901
- except: except ?? /* @__PURE__ */ new Set()
7793
+ except: except ?? /* @__PURE__ */ new Set(),
7794
+ rescues: rescues2,
7795
+ ...inline === void 0 ? {} : { inline }
6902
7796
  }));
6903
7797
  }
6904
- function methodNamesOf(args, filter, facts) {
7798
+ function inlineHandler(statement, args, filter, declaredIn, ordinal) {
7799
+ const block = blockInPlaceOfMethod(statement, args, filter);
7800
+ if (block === null || blockStatements2(block).length === 0) {
7801
+ return void 0;
7802
+ }
7803
+ return { block, label: `${filter.name}#${ordinal}`, declaredIn };
7804
+ }
7805
+ function rescuedClasses(args, nesting, facts) {
7806
+ const refs = [];
7807
+ let someUnread = args.positional.length === 0;
7808
+ for (const arg of args.positional) {
7809
+ const candidates = constantRefCandidates(arg, nesting);
7810
+ const named2 = candidates.length === 0 ? stringValueOf(arg, facts) : null;
7811
+ if (candidates.length === 0 && named2 === null) {
7812
+ someUnread = true;
7813
+ continue;
7814
+ }
7815
+ refs.push({
7816
+ text: arg.text,
7817
+ candidates: named2 === null ? candidates : [named2]
7818
+ });
7819
+ }
7820
+ return { refs, someUnread };
7821
+ }
7822
+ function methodNamesOf(args, filter, facts, inline) {
7823
+ const named2 = filter.methodFrom === "withKeyword" ? withKeywordName(args, facts) : symbolArgumentNames(args, facts);
7824
+ if (named2.length > 0 || inline === void 0) {
7825
+ return named2;
7826
+ }
7827
+ return [blockHandlerName(inline.block) ?? inline.label];
7828
+ }
7829
+ function withKeywordName(args, facts) {
7830
+ const named2 = args.keyword.with;
7831
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
7832
+ return value === null ? [] : [value];
7833
+ }
7834
+ function blockInPlaceOfMethod(statement, args, filter) {
6905
7835
  if (filter.methodFrom === "withKeyword") {
6906
- const named2 = args.keyword.with;
6907
- const value = named2 === void 0 ? null : stringValueOf(named2, facts);
6908
- return value === null ? [] : [value];
7836
+ return args.keyword.with === void 0 ? field(statement, "block") : null;
7837
+ }
7838
+ const lambda = args.positional.find((arg) => arg.type === "lambda");
7839
+ if (lambda !== void 0) {
7840
+ return field(lambda, "body");
7841
+ }
7842
+ return args.positional.length === 0 ? field(statement, "block") : null;
7843
+ }
7844
+ function blockStatements2(block) {
7845
+ return children(field(block, "body")).filter(
7846
+ (child) => child.type !== "comment"
7847
+ );
7848
+ }
7849
+ function blockHandlerName(block) {
7850
+ const statements = blockStatements2(block);
7851
+ const only = statements.length === 1 ? statements[0] : void 0;
7852
+ if (only === void 0) {
7853
+ return null;
7854
+ }
7855
+ const parameters = new Set(
7856
+ children(field(block, "parameters")).map((parameter) => parameter.text)
7857
+ );
7858
+ if (only.type === "identifier") {
7859
+ return parameters.has(only.text) ? null : only.text;
6909
7860
  }
6910
- return symbolArgumentNames(args, facts);
7861
+ return calledName(only);
6911
7862
  }
6912
7863
  function symbolArgumentNames(args, facts) {
6913
7864
  return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
@@ -6918,13 +7869,16 @@ function skipsOf(statement, filterName, facts) {
6918
7869
  return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
6919
7870
  }
6920
7871
  function sameFilter(one, other) {
6921
- return one.filter.name === other.filter.name && one.methodName === other.methodName;
7872
+ return namedBySymbol(one) && namedBySymbol(other) && one.filter.name === other.filter.name && one.methodName === other.methodName;
7873
+ }
7874
+ function namedBySymbol(declaration) {
7875
+ return declaration.inline === void 0;
6922
7876
  }
6923
7877
  function applySkips(declared, skips) {
6924
7878
  const left = [];
6925
7879
  for (const declaration of declared) {
6926
7880
  const reaching = skips.filter(
6927
- (skip) => skip.filterName === declaration.filter.name && skip.methodName === declaration.methodName
7881
+ (skip) => namedBySymbol(declaration) && skip.filterName === declaration.filter.name && skip.methodName === declaration.methodName
6928
7882
  );
6929
7883
  if (reaching.some((skip) => skip.actions === null)) {
6930
7884
  continue;
@@ -6948,8 +7902,8 @@ function actionsUnder(node, facts) {
6948
7902
  }
6949
7903
 
6950
7904
  // src/streamWrites.ts
6951
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
6952
- var import_extractor6 = require("@suss/extractor");
7905
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
7906
+ var import_extractor8 = require("@suss/extractor");
6953
7907
  var RUBY_STREAM_RECOGNITION = "ruby-stdlib";
6954
7908
  var KERNEL_WRITES = /* @__PURE__ */ new Map([
6955
7909
  ["puts", "stdout"],
@@ -6967,14 +7921,14 @@ var STREAM_METHODS = /* @__PURE__ */ new Set(["puts", "print", "write", "<<"]);
6967
7921
  var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
6968
7922
  function streamWriteEffects(root) {
6969
7923
  const found = [];
6970
- (0, import_extractor6.walkDescendants)(root, null, {
7924
+ (0, import_extractor8.walkDescendants)(root, null, children, {
6971
7925
  at: (node) => {
6972
7926
  const write = node.type === "call" ? streamWriteAt(node) : null;
6973
7927
  if (write !== null) {
6974
7928
  found.push(write);
6975
7929
  }
6976
7930
  },
6977
- into: (node) => DEFERRED_BODY_TYPES2.has(node.type) ? import_extractor6.SKIP_CHILDREN : null
7931
+ into: (node) => DEFERRED_BODY_TYPES2.has(node.type) ? import_extractor8.SKIP_CHILDREN : null
6978
7932
  });
6979
7933
  return found;
6980
7934
  }
@@ -7005,7 +7959,7 @@ function writeEffect(call, target, args) {
7005
7959
  const method = field(call, "method")?.text ?? "";
7006
7960
  return {
7007
7961
  type: "interaction",
7008
- binding: (0, import_behavioral_ir3.ioBinding)({ recognition: RUBY_STREAM_RECOGNITION, target }),
7962
+ binding: (0, import_behavioral_ir4.ioBinding)({ recognition: RUBY_STREAM_RECOGNITION, target }),
7009
7963
  callee: receiver === null ? method : `${receiver.text}.${method}`,
7010
7964
  groupId: `${call.startPosition.row + 1}:${call.startPosition.column + 1}`,
7011
7965
  interaction: { class: "stream-write", ...payloadOf(args) }
@@ -7058,7 +8012,7 @@ function writtenShapeOf(node) {
7058
8012
  return hashShape(node);
7059
8013
  }
7060
8014
  if (node.type === "array") {
7061
- const first = node.namedChildren.find((child) => child !== null);
8015
+ const first = children(node).find((child) => child !== null);
7062
8016
  return {
7063
8017
  type: "array",
7064
8018
  items: first ? writtenShapeOf(first) : { type: "unknown" }
@@ -7068,7 +8022,7 @@ function writtenShapeOf(node) {
7068
8022
  }
7069
8023
  function hashShape(node) {
7070
8024
  const properties = {};
7071
- for (const pair of node.namedChildren) {
8025
+ for (const pair of children(node)) {
7072
8026
  if (pair?.type !== "pair") {
7073
8027
  continue;
7074
8028
  }
@@ -7086,10 +8040,10 @@ function keyName(key) {
7086
8040
  }
7087
8041
 
7088
8042
  // src/testCases.ts
7089
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
7090
- var import_extractor7 = require("@suss/extractor");
8043
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
8044
+ var import_extractor9 = require("@suss/extractor");
7091
8045
  var import_resolution10 = require("@suss/resolution");
7092
- var import_values16 = require("@suss/values");
8046
+ var import_values17 = require("@suss/values");
7093
8047
  var BLOCK_TYPES6 = /* @__PURE__ */ new Set(["block", "do_block"]);
7094
8048
  function testPatternsIn(packs) {
7095
8049
  return packs.flatMap((pack) => pack.tests ?? []);
@@ -7273,7 +8227,7 @@ function groupStatements(group) {
7273
8227
  }
7274
8228
  function patternForFile(file, patterns) {
7275
8229
  return patterns.find(
7276
- (pattern) => (0, import_extractor7.matchesTestFileName)(file, pattern.filePatterns) && (0, import_extractor7.isListedTestFile)(file, pattern.files)
8230
+ (pattern) => (0, import_extractor9.matchesTestFileName)(file, pattern.filePatterns) && (0, import_extractor9.isListedTestFile)(file, pattern.files)
7277
8231
  ) ?? null;
7278
8232
  }
7279
8233
  function groupNamesIn(pattern) {
@@ -7431,14 +8385,14 @@ var NodesSeen = class {
7431
8385
  };
7432
8386
  function exampleRun(example, pattern, shared) {
7433
8387
  const byName = /* @__PURE__ */ new Map();
7434
- const always2 = [];
8388
+ const always3 = [];
7435
8389
  for (const group of withInlineIncludes(example.groups, pattern, shared)) {
7436
8390
  for (const one of groupBlocks(group, pattern)) {
7437
8391
  for (const name of one.names) {
7438
8392
  byName.set(name, one.block);
7439
8393
  }
7440
8394
  if (one.always) {
7441
- always2.push(one.block);
8395
+ always3.push(one.block);
7442
8396
  }
7443
8397
  }
7444
8398
  }
@@ -7457,7 +8411,7 @@ function exampleRun(example, pattern, shared) {
7457
8411
  }
7458
8412
  };
7459
8413
  include(example.block);
7460
- for (const block of always2) {
8414
+ for (const block of always3) {
7461
8415
  include(block);
7462
8416
  }
7463
8417
  const valueNames = /* @__PURE__ */ new Set([
@@ -7565,7 +8519,7 @@ function exampleCalls(run, inherited) {
7565
8519
  }
7566
8520
  function testCaseUnits(root, patterns, options) {
7567
8521
  return patterns.flatMap((pattern) => {
7568
- if (!(0, import_extractor7.matchesTestFileName)(options.absoluteFile, pattern.filePatterns) || !(0, import_extractor7.isListedTestFile)(options.absoluteFile, pattern.files)) {
8522
+ if (!(0, import_extractor9.matchesTestFileName)(options.absoluteFile, pattern.filePatterns) || !(0, import_extractor9.isListedTestFile)(options.absoluteFile, pattern.files)) {
7569
8523
  return [];
7570
8524
  }
7571
8525
  return examplesIn(root, options.absoluteFile, pattern, options.shared).map(
@@ -7638,7 +8592,7 @@ function testUnitOf(parts) {
7638
8592
  const range = rangeOf(anchor);
7639
8593
  return {
7640
8594
  identity: {
7641
- name: (0, import_behavioral_ir4.testUnitName)(titles.map((title) => title.text)),
8595
+ name: (0, import_behavioral_ir5.testUnitName)(titles.map((title) => title.text)),
7642
8596
  nameKind: "label",
7643
8597
  kind: "test",
7644
8598
  file: options.filePath,
@@ -7684,7 +8638,7 @@ function titleOf(call, facts) {
7684
8638
  if (first.type === "constant" || first.type === "scope_resolution") {
7685
8639
  return { text: first.text, unresolved: false };
7686
8640
  }
7687
- const read = (0, import_values16.literalOf)(evaluatedValue(first, facts));
8641
+ const read = (0, import_values17.literalOf)(evaluatedValue(first, facts));
7688
8642
  return read === null ? { text: first.text, unresolved: true } : { text: read, unresolved: false };
7689
8643
  }
7690
8644
  function isSkipped(example, pattern) {
@@ -7825,7 +8779,7 @@ function moduleOfConstant(node, options) {
7825
8779
  const key = classBehind(facts, options.absoluteFile, node);
7826
8780
  return key === void 0 ? null : options.displayPathOf((0, import_resolution10.fileOfKey)(key));
7827
8781
  }
7828
- const name = (0, import_values16.literalOf)(evaluatedValue(node, facts));
8782
+ const name = (0, import_values17.literalOf)(evaluatedValue(node, facts));
7829
8783
  const keys = name === null ? [] : facts.lookup("rbConstantName", 1, name.replace(/^::/, "")).map((row) => String(row[0]));
7830
8784
  const files = [...new Set(keys.map(import_resolution10.fileOfKey))];
7831
8785
  return files.length === 1 ? options.displayPathOf(files[0]) : null;
@@ -7835,8 +8789,8 @@ function oneLine(text2) {
7835
8789
  }
7836
8790
 
7837
8791
  // src/testClasses.ts
7838
- var import_extractor8 = require("@suss/extractor");
7839
- var import_values18 = require("@suss/values");
8792
+ var import_extractor10 = require("@suss/extractor");
8793
+ var import_values19 = require("@suss/values");
7840
8794
  var BLOCK_TYPES7 = /* @__PURE__ */ new Set(["block", "do_block"]);
7841
8795
  function testClassPatternsIn(packs) {
7842
8796
  return packs.flatMap((pack) => pack.testClasses ?? []);
@@ -7879,7 +8833,7 @@ function testClassUnits(root, patterns, options) {
7879
8833
  });
7880
8834
  }
7881
8835
  function readsFile(file, pattern) {
7882
- return (0, import_extractor8.matchesTestFileName)(file, pattern.filePatterns) && (0, import_extractor8.isListedTestFile)(file, pattern.files);
8836
+ return (0, import_extractor10.matchesTestFileName)(file, pattern.filePatterns) && (0, import_extractor10.isListedTestFile)(file, pattern.files);
7883
8837
  }
7884
8838
  function testClassesIn(root, file, pattern, facts) {
7885
8839
  const found = [];
@@ -7925,7 +8879,7 @@ function testBlockOf(statement, pattern) {
7925
8879
  }
7926
8880
  function descriptionOf(call, facts) {
7927
8881
  const first = firstArgument(call);
7928
- return first === null ? null : (0, import_values18.literalOf)(evaluatedValue(first, facts));
8882
+ return first === null ? null : (0, import_values19.literalOf)(evaluatedValue(first, facts));
7929
8883
  }
7930
8884
  function anchorOf2(node) {
7931
8885
  return BLOCK_TYPES7.has(node.type) ? node.parent ?? node : node;
@@ -8102,6 +9056,7 @@ function fieldReadContext(pattern, cache, fileBlocks, bodyRead, facts) {
8102
9056
  },
8103
9057
  lookup: {
8104
9058
  root: pattern.root,
9059
+ autoloadRoots: pattern.autoloadRoots ?? [],
8105
9060
  pathConvention: pattern.pathConvention,
8106
9061
  acronyms: pattern.acronyms ?? [],
8107
9062
  ancestryRootClassNames: pattern.ancestryRootClassNames,
@@ -8210,7 +9165,7 @@ function unitsFor(pattern, pack, info, ownBlocks, fileBlocks, options) {
8210
9165
  graphqlObjectFields: (p) => graphqlObjectFieldUnits(p, pack, info, ownBlocks, fileBlocks, options),
8211
9166
  controllerActions: (p) => controllerActionUnits(p, pack, info, ownBlocks, fileBlocks, options)
8212
9167
  };
8213
- return (0, import_behavioral_ir5.dispatchByType)(table, pattern);
9168
+ return (0, import_behavioral_ir6.dispatchByType)(table, pattern);
8214
9169
  }
8215
9170
  async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlocks, options) {
8216
9171
  if (info.bodyNode === null || pattern.baseClassNames.includes(info.qualifiedName)) {
@@ -8257,6 +9212,7 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
8257
9212
  }
8258
9213
  const lookup = {
8259
9214
  root: pattern.root,
9215
+ autoloadRoots: pattern.autoloadRoots ?? [],
8260
9216
  pathConvention: pattern.pathConvention,
8261
9217
  acronyms: pattern.acronyms ?? [],
8262
9218
  ancestryRootClassNames: pattern.ancestryRootClassNames,
@@ -8295,15 +9251,41 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
8295
9251
  options.facts,
8296
9252
  options.storage
8297
9253
  );
9254
+ const respondingHelper = respondingHelpersOf(ancestry, pattern, options);
9255
+ const exceptions = new ExceptionReader(
9256
+ lookup,
9257
+ pattern.libraryExceptions ?? {}
9258
+ );
9259
+ const caught = await handlerClasses(filters, exceptions);
9260
+ const handlersAround = (actionName) => filters.filter((filter) => filterCoversAction(filter, actionName)).flatMap((filter) => {
9261
+ const classes = caught.get(filter);
9262
+ return classes === void 0 ? [] : [classes];
9263
+ });
8298
9264
  for (const filter of filters) {
8299
9265
  const displayPath = options.displayPathOf?.(filter.file) ?? filter.file;
8300
- const raw = filterUnit(
8301
- filter,
8302
- pattern,
8303
- displayPath,
8304
- bodyOfMethod(filter.method, filter.file, options),
8305
- options.facts
8306
- );
9266
+ const body = bodyOfMethod(filter.method, filter.file, options);
9267
+ const handler = filter.filter.onThrow === true;
9268
+ const raw = filterUnit(filter, pattern, displayPath, body, {
9269
+ facts: options.facts,
9270
+ respondingHelper,
9271
+ ...handler ? {} : {
9272
+ raises: {
9273
+ classes: await raisedClasses(
9274
+ filter.method,
9275
+ filter.nesting,
9276
+ exceptions
9277
+ ),
9278
+ handlers: "eachRoute"
9279
+ },
9280
+ raised: raisedStatusBranches(
9281
+ pattern,
9282
+ options.storage?.patterns ?? [],
9283
+ body.extraEffects,
9284
+ "eachRoute",
9285
+ rangeOf(filter.method)
9286
+ )
9287
+ }
9288
+ });
8307
9289
  units.push(raw);
8308
9290
  options.onReachSeed?.(raw, {
8309
9291
  file: filter.file,
@@ -8311,38 +9293,71 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
8311
9293
  enclosingQualifiedName: filter.enclosingQualifiedName
8312
9294
  });
8313
9295
  }
8314
- const emitAction = (actionName, method, block) => {
9296
+ const emitAction = async (actionName, method, block, routes) => {
8315
9297
  const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
8316
9298
  (filter) => filterReference(
8317
9299
  filter,
8318
- options.displayPathOf?.(filter.file) ?? filter.file
9300
+ options.displayPathOf?.(filter.file) ?? filter.file,
9301
+ caught.get(filter)
8319
9302
  )
8320
9303
  );
8321
- const raw = buildControllerActionUnit(
8322
- pack,
8323
- pattern,
8324
- info.qualifiedName,
8325
- actionName,
8326
- method,
8327
- {
8328
- display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
8329
- absolute: block.file
8330
- },
8331
- options,
8332
- around
8333
- );
8334
- units.push(raw);
8335
- options.onReachSeed?.(raw, {
8336
- file: block.file,
8337
- node: method,
8338
- enclosingQualifiedName: block.info.qualifiedName
8339
- });
9304
+ const raises2 = {
9305
+ classes: await raisedClasses(method, block.info.bodyNesting, exceptions),
9306
+ handlers: handlersAround(actionName)
9307
+ };
9308
+ for (const route of routes.length === 0 ? [null] : routes) {
9309
+ const raw = buildControllerActionUnit(
9310
+ pack,
9311
+ pattern,
9312
+ info.qualifiedName,
9313
+ actionName,
9314
+ route,
9315
+ method,
9316
+ {
9317
+ display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
9318
+ absolute: block.file
9319
+ },
9320
+ options,
9321
+ around,
9322
+ respondingHelper,
9323
+ raises2
9324
+ );
9325
+ units.push(raw);
9326
+ options.onReachSeed?.(raw, {
9327
+ file: block.file,
9328
+ node: method,
9329
+ enclosingQualifiedName: block.info.qualifiedName
9330
+ });
9331
+ }
8340
9332
  };
8341
9333
  for (const [actionName, method, block] of actions) {
8342
- emitAction(actionName, method, block);
9334
+ const routes = pattern.routesFor(info.qualifiedName, actionName);
9335
+ await emitAction(actionName, method, block, routes);
8343
9336
  }
8344
9337
  return units;
8345
9338
  }
9339
+ async function handlerClasses(filters, exceptions) {
9340
+ const caught = /* @__PURE__ */ new Map();
9341
+ for (const filter of filters) {
9342
+ if (filter.filter.onThrow !== true) {
9343
+ continue;
9344
+ }
9345
+ const classes = [];
9346
+ for (const ref of filter.rescues.refs) {
9347
+ classes.push(await exceptions.read(ref));
9348
+ }
9349
+ caught.set(filter, { classes, someUnread: filter.rescues.someUnread });
9350
+ }
9351
+ return caught;
9352
+ }
9353
+ async function raisedClasses(method, nesting, exceptions) {
9354
+ const classes = new NodeMap();
9355
+ for (const call of escapingRaises(method)) {
9356
+ const ref = raisedClassRef(call, nesting);
9357
+ classes.set(call, ref === null ? null : await exceptions.read(ref));
9358
+ }
9359
+ return classes;
9360
+ }
8346
9361
  function controllerActions(pattern, info, ownBlocks, ancestry, options) {
8347
9362
  const actions = publicInstanceMethods(ownBlocks, options.bodyBlocks);
8348
9363
  const seen = new Set(actions.map(([actionName]) => actionName));
@@ -8359,7 +9374,7 @@ function controllerActions(pattern, info, ownBlocks, ancestry, options) {
8359
9374
  continue;
8360
9375
  }
8361
9376
  seen.add(actionName);
8362
- if (pattern.routeFor(info.qualifiedName, actionName) !== null) {
9377
+ if (pattern.routesFor(info.qualifiedName, actionName).length > 0) {
8363
9378
  actions.push(action);
8364
9379
  }
8365
9380
  }
@@ -8384,16 +9399,52 @@ function publicInstanceMethods(blocks, bodyBlocks) {
8384
9399
  }
8385
9400
  return found;
8386
9401
  }
8387
- function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, file, bodyRead, wrappers = []) {
9402
+ function respondingHelpersOf(ancestry, pattern, options) {
9403
+ const methods = /* @__PURE__ */ new Map();
9404
+ const known = /* @__PURE__ */ new Map();
9405
+ const declared = new Set(
9406
+ (pattern.responseStatusCalls ?? []).map((call) => call.name)
9407
+ );
9408
+ const methodNamed = (name) => {
9409
+ if (!methods.has(name)) {
9410
+ const found = methodInAncestry(ancestry, name, {
9411
+ facts: options.facts,
9412
+ bodyBlocks: options.bodyBlocks,
9413
+ dynamicNames: options.dynamicNames
9414
+ });
9415
+ methods.set(name, found.type === "found" ? found.method : null);
9416
+ }
9417
+ return methods.get(name) ?? null;
9418
+ };
9419
+ return (name, call) => {
9420
+ const method = declared.has(name) ? null : methodNamed(name);
9421
+ if (method === null) {
9422
+ return null;
9423
+ }
9424
+ const bound = boundStatusArguments(method, call, pattern, options.facts);
9425
+ const key = `${name}(${[...bound].join(",")})`;
9426
+ if (!known.has(key)) {
9427
+ known.set(key, helperResponse(method, pattern, options.facts, bound));
9428
+ }
9429
+ return known.get(key) ?? null;
9430
+ };
9431
+ }
9432
+ function servesFormat(pattern, route) {
9433
+ return pattern.optionalFormat === true && route.formatless !== true && !route.path.endsWith("/");
9434
+ }
9435
+ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, route, method, file, bodyRead, wrappers, respondingHelper, raises2) {
8388
9436
  const range = rangeOf(method);
8389
- const route = pattern.routeFor(controllerQualifiedName, actionName);
8390
9437
  const body = bodyOfMethod(method, file.absolute, bodyRead);
8391
9438
  const perResponse = responseBranches(
8392
9439
  method,
8393
9440
  pattern,
8394
9441
  body.effects ?? [],
8395
9442
  body.extraEffects,
8396
- { facts: bodyRead.facts }
9443
+ {
9444
+ facts: bodyRead.facts,
9445
+ respondingHelper,
9446
+ raises: raises2
9447
+ }
8397
9448
  );
8398
9449
  return {
8399
9450
  identity: {
@@ -8406,11 +9457,12 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
8406
9457
  exportName: actionName,
8407
9458
  exportPath: [controllerQualifiedName, actionName]
8408
9459
  },
8409
- boundaryBinding: route === null ? null : (0, import_behavioral_ir5.restBinding)({
9460
+ boundaryBinding: route === null ? null : (0, import_behavioral_ir6.restBinding)({
8410
9461
  transport: pack.protocol,
8411
9462
  method: route.method,
8412
9463
  path: route.path,
8413
- recognition: pack.name
9464
+ recognition: pack.name,
9465
+ optionalFormat: servesFormat(pattern, route)
8414
9466
  }),
8415
9467
  parameters: [],
8416
9468
  branches: withSlotSources(
@@ -8430,7 +9482,7 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
8430
9482
  location: range
8431
9483
  },
8432
9484
  statusCodeReading: {
8433
- reading: import_extractor9.absentReading,
9485
+ reading: import_extractor11.absentReading,
8434
9486
  libraryDefault: pattern.defaultStatusCode
8435
9487
  },
8436
9488
  effects: body.effects ?? [],
@@ -8440,6 +9492,14 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
8440
9492
  }
8441
9493
  ],
8442
9494
  body.provenance
9495
+ ).concat(
9496
+ raisedStatusBranches(
9497
+ pattern,
9498
+ bodyRead.storage?.patterns ?? [],
9499
+ body.extraEffects,
9500
+ raises2.handlers,
9501
+ range
9502
+ )
8443
9503
  ),
8444
9504
  bodyContent: body.bodyContent ?? "absent",
8445
9505
  dependencyCalls: [],
@@ -8481,7 +9541,7 @@ function routingGapUnit(pattern, gaps) {
8481
9541
  isDefault: true
8482
9542
  }
8483
9543
  ],
8484
- readings: gaps.map((gap) => (0, import_extractor9.unreadableReading)(gap, range)),
9544
+ readings: gaps.map((gap) => (0, import_extractor11.unreadableReading)(gap, range)),
8485
9545
  dependencyCalls: [],
8486
9546
  declaredContract: null
8487
9547
  };
@@ -8554,7 +9614,7 @@ var NO_METHOD_BEHIND_IT = {
8554
9614
  readings: []
8555
9615
  };
8556
9616
  function methodNotSettled(reason, range) {
8557
- return { readings: [(0, import_extractor9.unreadableReading)(reason, range)] };
9617
+ return { readings: [(0, import_extractor11.unreadableReading)(reason, range)] };
8558
9618
  }
8559
9619
  function bodyFromLookup(found, range, subject, nothingThere, bodyRead) {
8560
9620
  const table = {
@@ -8572,7 +9632,7 @@ function bodyFromLookup(found, range, subject, nothingThere, bodyRead) {
8572
9632
  ),
8573
9633
  none: () => nothingThere
8574
9634
  };
8575
- return (0, import_behavioral_ir5.dispatchByType)(table, found);
9635
+ return (0, import_behavioral_ir6.dispatchByType)(table, found);
8576
9636
  }
8577
9637
  async function readFieldCall(stmt, scope, ctx, ancestry) {
8578
9638
  if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
@@ -8615,7 +9675,7 @@ function computedNameDeclaration(stmt, nameArg) {
8615
9675
  contract: null,
8616
9676
  body: {
8617
9677
  readings: [
8618
- (0, import_extractor9.unreadableReading)(
9678
+ (0, import_extractor11.unreadableReading)(
8619
9679
  `This field is named by ${nameArg.text}, which is worked out when the class body runs, so the name the schema exposes it under was not read here`,
8620
9680
  range
8621
9681
  )
@@ -8846,7 +9906,7 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
8846
9906
  exportName: null,
8847
9907
  exportPath: null
8848
9908
  },
8849
- boundaryBinding: decl.namedOnTheWire ? (0, import_behavioral_ir5.graphqlResolverBinding)({
9909
+ boundaryBinding: decl.namedOnTheWire ? (0, import_behavioral_ir6.graphqlResolverBinding)({
8850
9910
  transport: pack.protocol,
8851
9911
  recognition: pack.name,
8852
9912
  typeName,
@@ -9262,15 +10322,15 @@ function parseWith(language, source) {
9262
10322
  // src/project.ts
9263
10323
  var import_node_fs3 = __toESM(require("fs"), 1);
9264
10324
  var import_node_path4 = __toESM(require("path"), 1);
9265
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
10325
+ var import_behavioral_ir9 = require("@suss/behavioral-ir");
9266
10326
  var import_datalog3 = require("@suss/datalog");
9267
- var import_extractor17 = require("@suss/extractor");
10327
+ var import_extractor19 = require("@suss/extractor");
9268
10328
  var import_resolution16 = require("@suss/resolution");
9269
10329
 
9270
10330
  // src/diagnostics.ts
9271
- var import_extractor10 = require("@suss/extractor");
10331
+ var import_extractor12 = require("@suss/extractor");
9272
10332
  function buildRubyExtractionReport(args) {
9273
- return (0, import_extractor10.buildUngatedExtractionReport)({
10333
+ return (0, import_extractor12.buildUngatedExtractionReport)({
9274
10334
  packs: args.packs.map((pack) => ({
9275
10335
  name: pack.name,
9276
10336
  version: pack.version ?? null,
@@ -9283,9 +10343,9 @@ function buildRubyExtractionReport(args) {
9283
10343
  }
9284
10344
 
9285
10345
  // src/exits.ts
9286
- var import_extractor11 = require("@suss/extractor");
10346
+ var import_extractor13 = require("@suss/extractor");
9287
10347
  var import_resolution11 = require("@suss/resolution");
9288
- var import_values21 = require("@suss/values");
10348
+ var import_values22 = require("@suss/values");
9289
10349
  var EXIT_METHODS = /* @__PURE__ */ new Map([
9290
10350
  ["exit", 0],
9291
10351
  ["exit!", 1],
@@ -9294,14 +10354,14 @@ var EXIT_METHODS = /* @__PURE__ */ new Map([
9294
10354
  var DEFERRED_BODY_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
9295
10355
  function exitSites(root) {
9296
10356
  const found = [];
9297
- (0, import_extractor11.walkDescendants)(root, null, {
10357
+ (0, import_extractor13.walkDescendants)(root, null, children, {
9298
10358
  at: (node) => {
9299
10359
  const site = node.type === "call" ? exitSiteAt(node) : null;
9300
10360
  if (site !== null) {
9301
10361
  found.push(site);
9302
10362
  }
9303
10363
  },
9304
- into: (node) => DEFERRED_BODY_TYPES3.has(node.type) ? import_extractor11.SKIP_CHILDREN : null
10364
+ into: (node) => DEFERRED_BODY_TYPES3.has(node.type) ? import_extractor13.SKIP_CHILDREN : null
9305
10365
  });
9306
10366
  return found;
9307
10367
  }
@@ -9338,7 +10398,7 @@ function exitCodeOf(site, facts) {
9338
10398
  if (written !== void 0) {
9339
10399
  return { type: "literal", value: written };
9340
10400
  }
9341
- const value = (0, import_values21.constantOf)(evaluatedValue(code, facts));
10401
+ const value = (0, import_values22.constantOf)(evaluatedValue(code, facts));
9342
10402
  return typeof value === "number" ? { type: "literal", value } : { type: "dynamic", sourceText: code.text };
9343
10403
  }
9344
10404
  function exitingBranches(method, effects, facts) {
@@ -9359,7 +10419,7 @@ function markExitCodeFunctions(summaries, exits, facts, workspaceRoot) {
9359
10419
  (site) => site.code === null ? [] : [nodeId(file, site.code)]
9360
10420
  )
9361
10421
  );
9362
- (0, import_extractor11.markReturnsAsExitCode)(
10422
+ (0, import_extractor13.markReturnsAsExitCode)(
9363
10423
  summaries,
9364
10424
  (0, import_resolution11.exitCodeFunctions)(facts, sinks),
9365
10425
  workspaceRoot
@@ -9409,9 +10469,9 @@ function eventsOf(args, called, callbacks, db) {
9409
10469
 
9410
10470
  // src/facts.ts
9411
10471
  var import_node_path3 = __toESM(require("path"), 1);
9412
- var import_extractor12 = require("@suss/extractor");
10472
+ var import_extractor14 = require("@suss/extractor");
9413
10473
  function emitRequireFacts(db, filePath, root, known) {
9414
- (0, import_extractor12.walkDescendants)(root, null, {
10474
+ (0, import_extractor14.walkDescendants)(root, null, children, {
9415
10475
  at: (node) => {
9416
10476
  const target = requiredRelativePath(node);
9417
10477
  if (target === null) {
@@ -9425,7 +10485,7 @@ function emitRequireFacts(db, filePath, root, known) {
9425
10485
  db.add("importsFile", [filePath, resolved]);
9426
10486
  }
9427
10487
  },
9428
- into: (node) => node.type === "argument_list" ? import_extractor12.SKIP_CHILDREN : null
10488
+ into: (node) => node.type === "argument_list" ? import_extractor14.SKIP_CHILDREN : null
9429
10489
  });
9430
10490
  }
9431
10491
  function requiredRelativePath(node) {
@@ -9437,17 +10497,17 @@ function requiredRelativePath(node) {
9437
10497
  }
9438
10498
 
9439
10499
  // src/moduleSurface.ts
9440
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
9441
- var import_extractor15 = require("@suss/extractor");
10500
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
10501
+ var import_extractor17 = require("@suss/extractor");
9442
10502
 
9443
10503
  // src/reach/closure.ts
9444
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
10504
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
9445
10505
  var import_datalog2 = require("@suss/datalog");
9446
- var import_extractor14 = require("@suss/extractor");
10506
+ var import_extractor16 = require("@suss/extractor");
9447
10507
  var import_resolution13 = require("@suss/resolution");
9448
10508
 
9449
10509
  // src/flags.ts
9450
- var import_extractor13 = require("@suss/extractor");
10510
+ var import_extractor15 = require("@suss/extractor");
9451
10511
  var PARSE_METHODS = /* @__PURE__ */ new Set(["parse", "parse!", "permute", "permute!"]);
9452
10512
  var DEFERRED_BODY_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
9453
10513
  function optionParserFlagReads(method, parameterNames3) {
@@ -9476,9 +10536,9 @@ function optionParserFlagReads(method, parameterNames3) {
9476
10536
  return reads;
9477
10537
  }
9478
10538
  function walkBody2(body, at) {
9479
- (0, import_extractor13.walkDescendants)(body, null, {
10539
+ (0, import_extractor15.walkDescendants)(body, null, children, {
9480
10540
  at,
9481
- into: (node) => DEFERRED_BODY_TYPES4.has(node.type) ? import_extractor13.SKIP_CHILDREN : null
10541
+ into: (node) => DEFERRED_BODY_TYPES4.has(node.type) ? import_extractor15.SKIP_CHILDREN : null
9482
10542
  });
9483
10543
  }
9484
10544
  var OPTION_PARSER_SPELLINGS = /* @__PURE__ */ new Set(["OptionParser", "::OptionParser"]);
@@ -9492,7 +10552,7 @@ function flagsDeclaredIn(construction) {
9492
10552
  return [];
9493
10553
  }
9494
10554
  const flags = [];
9495
- (0, import_extractor13.walkDescendants)(block, null, {
10555
+ (0, import_extractor15.walkDescendants)(block, null, children, {
9496
10556
  at: (node) => {
9497
10557
  if (node.type !== "call" || field(node, "method")?.text !== "on") {
9498
10558
  return;
@@ -9564,6 +10624,11 @@ var UNSETTLED_ON_SELF = {
9564
10624
  reason: "noDeclaration",
9565
10625
  matchByName: true
9566
10626
  };
10627
+ var SELF_CALL_IN_MIXIN = {
10628
+ kind: "stopped",
10629
+ reason: "selfCallInMixin",
10630
+ matchByName: true
10631
+ };
9567
10632
  function followed(target) {
9568
10633
  return { kind: "followed", target };
9569
10634
  }
@@ -9752,22 +10817,27 @@ function leavesRoomForATopLevelMethod(reason) {
9752
10817
  return reason === "noDeclaration" || reason === "outsideRun";
9753
10818
  }
9754
10819
  function resolveImplicitSelf(methodName, site, ctx) {
9755
- if (site.enclosingQualifiedName !== null) {
9756
- const onClass = methodOnSelf(
9757
- site,
9758
- site.enclosingQualifiedName,
9759
- methodName,
9760
- ctx
9761
- );
9762
- if (onClass.kind === "followed" || !leavesRoomForATopLevelMethod(onClass.reason)) {
9763
- return onClass;
9764
- }
10820
+ const onClass = site.enclosingQualifiedName === null ? null : methodOnSelf(site, site.enclosingQualifiedName, methodName, ctx);
10821
+ if (onClass !== null && (onClass.kind === "followed" || !leavesRoomForATopLevelMethod(onClass.reason))) {
10822
+ return onClass;
9765
10823
  }
10824
+ const readWholeAncestry = onClass?.kind === "stopped" && onClass.reason === "noDeclaration";
9766
10825
  const topLevel = resolveTopLevelName(methodName, ctx);
9767
10826
  if (topLevel.kind === "followed" || topLevel.reason !== "noDeclaration") {
9768
10827
  return topLevel;
9769
10828
  }
9770
- return declaredForTheOtherSelf(site, methodName, ctx) ? NO_DECLARATION : UNSETTLED_ON_SELF;
10829
+ if (declaredForTheOtherSelf(site, methodName, ctx)) {
10830
+ return NO_DECLARATION;
10831
+ }
10832
+ return readWholeAncestry && !OBJECT_METHODS.has(methodName) && inModuleInstanceMethod(site, ctx) ? SELF_CALL_IN_MIXIN : UNSETTLED_ON_SELF;
10833
+ }
10834
+ function inModuleInstanceMethod(site, ctx) {
10835
+ const qualifiedName = site.enclosingQualifiedName;
10836
+ if (qualifiedName === null || site.method === null || inClassMethod(site, ctx)) {
10837
+ return false;
10838
+ }
10839
+ const own = ctx.ancestries.get(qualifiedName)?.[0];
10840
+ return own?.type === "bodies" && own.blocks.every((block) => block.info.kind === "module");
9771
10841
  }
9772
10842
  function declaredForTheOtherSelf(site, methodName, ctx) {
9773
10843
  const qualifiedName = site.enclosingQualifiedName;
@@ -9810,12 +10880,16 @@ function methodOnAncestryOf(qualifiedName, methodName, ctx) {
9810
10880
  return followed(reachedMethod(found.method, found.block, methodName));
9811
10881
  }
9812
10882
  if (found.type === "unsettled") {
9813
- return stop(
9814
- found.cause === "dynamicDefine" ? "definedAtLoadTime" : "outsideRun"
9815
- );
10883
+ return stop(unsettledReason(found));
9816
10884
  }
9817
10885
  return NO_DECLARATION;
9818
10886
  }
10887
+ function unsettledReason(found) {
10888
+ if (found.cause === "dynamicDefine") {
10889
+ return "definedAtLoadTime";
10890
+ }
10891
+ return found.definedLater === true ? "behindUnreadAncestor" : "outsideRun";
10892
+ }
9819
10893
  function reachedMethod(node, block, name) {
9820
10894
  return {
9821
10895
  file: block.file,
@@ -9975,15 +11049,15 @@ async function reachedFunctions(seeds, options) {
9975
11049
  const build = () => {
9976
11050
  const raw = libraryUnit(target, options);
9977
11051
  dropPropertyReads(raw, propertyReadsByKey.get(key));
9978
- const summary2 = (0, import_extractor14.assembleSummary)(raw, { gapHandling });
11052
+ const summary2 = (0, import_extractor16.assembleSummary)(raw, { gapHandling });
9979
11053
  if (gapHandling !== "silent") {
9980
11054
  summary2.gaps.push(
9981
- ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
11055
+ ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir7.unfollowedCallGap)
9982
11056
  );
9983
11057
  }
9984
- (0, import_behavioral_ir6.placeCalls)(summary2, targetsByKey.get(key));
9985
- (0, import_behavioral_ir6.placeArgTargets)(summary2, argTargetsByKey.get(key));
9986
- (0, import_behavioral_ir6.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
11058
+ (0, import_behavioral_ir7.placeCalls)(summary2, targetsByKey.get(key));
11059
+ (0, import_behavioral_ir7.placeArgTargets)(summary2, argTargetsByKey.get(key));
11060
+ (0, import_behavioral_ir7.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
9987
11061
  return summary2;
9988
11062
  };
9989
11063
  const summary = ledger === void 0 ? build() : ledger.charging(chargeOf(charges, key), () => {
@@ -9998,7 +11072,7 @@ async function reachedFunctions(seeds, options) {
9998
11072
  summaries.push(summary);
9999
11073
  }
10000
11074
  if (gapHandling !== "silent") {
10001
- (0, import_behavioral_ir6.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
11075
+ (0, import_behavioral_ir7.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
10002
11076
  }
10003
11077
  return {
10004
11078
  summaries,
@@ -10211,9 +11285,9 @@ function scanBody(source, ctx, options, read) {
10211
11285
  );
10212
11286
  const ownParameters = positionalParameters(source.node).map((p) => p.name);
10213
11287
  const followed2 = [];
10214
- const stops = [];
10215
- const placements = new import_behavioral_ir6.TargetPlacements();
10216
- const receiverClasses = new import_behavioral_ir6.ReceiverClasses();
11288
+ const stops = new import_behavioral_ir7.StopList();
11289
+ const placements = new import_behavioral_ir7.TargetPlacements();
11290
+ const receiverClasses = new import_behavioral_ir7.ReceiverClasses();
10217
11291
  const parameterCalls = [];
10218
11292
  const passedPositions = /* @__PURE__ */ new Set();
10219
11293
  const seen = /* @__PURE__ */ new Set();
@@ -10295,11 +11369,13 @@ function scanBody(source, ctx, options, read) {
10295
11369
  outcome.kind === "followed" ? keyOf(outcome.target) : null
10296
11370
  );
10297
11371
  if (outcome.kind === "stopped") {
10298
- const stopKey = `${outcome.reason}:${callee}`;
10299
11372
  const claimed = options.storage !== void 0 && storageClaims(call, callSite.file, options.storage, callSite.method);
10300
- if (!seen.has(stopKey) && !claimed && (0, import_behavioral_ir6.worthRecording)(outcome.reason)) {
10301
- seen.add(stopKey);
10302
- stops.push({ callee, reason: outcome.reason });
11373
+ if (!claimed && (0, import_behavioral_ir7.worthRecording)(outcome.reason)) {
11374
+ stops.add({
11375
+ callee,
11376
+ reason: outcome.reason,
11377
+ ...resultUsedAsValue(call) ? { usedAsValue: true } : {}
11378
+ });
10303
11379
  }
10304
11380
  if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
10305
11381
  const receiverName = field(call, "receiver")?.text;
@@ -10322,7 +11398,7 @@ function scanBody(source, ctx, options, read) {
10322
11398
  }
10323
11399
  return {
10324
11400
  followed: followed2,
10325
- stops,
11401
+ stops: stops.stops,
10326
11402
  targets: placements.targets,
10327
11403
  argTargets: placements.argTargets,
10328
11404
  parameterCalls,
@@ -10363,7 +11439,7 @@ function propertyReadsAmong(argless, made, keptArgless) {
10363
11439
  }
10364
11440
  return reads;
10365
11441
  }
10366
- function libraryUnit(target, options, binding = (0, import_behavioral_ir6.functionCallBinding)({
11442
+ function libraryUnit(target, options, binding = (0, import_behavioral_ir7.functionCallBinding)({
10367
11443
  transport: "in-process",
10368
11444
  recognition: "reachable"
10369
11445
  })) {
@@ -10448,10 +11524,10 @@ function parameterName(param) {
10448
11524
 
10449
11525
  // src/moduleSurface.ts
10450
11526
  function settleRubyModules(modules) {
10451
- return (0, import_extractor15.settleModules)(modules, ["{name}.rb", "../{name}.rb"]);
11527
+ return (0, import_extractor17.settleModules)(modules, ["{name}.rb", "../{name}.rb"]);
10452
11528
  }
10453
11529
  function moduleExportUnits(root, file, options) {
10454
- const module2 = (0, import_extractor15.moduleOfFile)(options.modules, file);
11530
+ const module2 = (0, import_extractor17.moduleOfFile)(options.modules, file);
10455
11531
  if (module2 === void 0 || !module2.public.includes(file)) {
10456
11532
  return [];
10457
11533
  }
@@ -10478,8 +11554,8 @@ function moduleExportUnits(root, file, options) {
10478
11554
  exportPath: [info.qualifiedName, method]
10479
11555
  },
10480
11556
  options,
10481
- (0, import_behavioral_ir7.moduleExportBinding)({
10482
- recognition: import_extractor15.MODULE_SURFACE_RECOGNITION,
11557
+ (0, import_behavioral_ir8.moduleExportBinding)({
11558
+ recognition: import_extractor17.MODULE_SURFACE_RECOGNITION,
10483
11559
  module: module2.name,
10484
11560
  exportName: name
10485
11561
  })
@@ -10787,11 +11863,11 @@ var WalkReplay = class {
10787
11863
  };
10788
11864
 
10789
11865
  // src/testFactories.ts
10790
- var import_values28 = require("@suss/values");
11866
+ var import_values29 = require("@suss/values");
10791
11867
  function factoriesIn(packs) {
10792
11868
  return packs.flatMap((pack) => pack.factories ?? []);
10793
11869
  }
10794
- function emitFactoryFacts(db, files, isTestFile, patterns, inflections) {
11870
+ function emitFactoryFacts(db, files, isTestFile2, patterns, inflections) {
10795
11871
  for (const pattern of patterns) {
10796
11872
  const definitions = /* @__PURE__ */ new Map();
10797
11873
  for (const { file, root } of files) {
@@ -10799,7 +11875,7 @@ function emitFactoryFacts(db, files, isTestFile, patterns, inflections) {
10799
11875
  }
10800
11876
  const classes = new FactoryClasses(db, definitions, inflections);
10801
11877
  for (const { file, root } of files) {
10802
- if (!isTestFile(file)) {
11878
+ if (!isTestFile2(file)) {
10803
11879
  continue;
10804
11880
  }
10805
11881
  for (const [call, name] of buildsIn(db, root, pattern)) {
@@ -10849,7 +11925,7 @@ function classRefOf(db, file, node) {
10849
11925
  if (node.type === "constant" || node.type === "scope_resolution") {
10850
11926
  return { key: readKey(file, node, null) };
10851
11927
  }
10852
- const written = (0, import_values28.literalOf)(evaluatedValue(node, db));
11928
+ const written = (0, import_values29.literalOf)(evaluatedValue(node, db));
10853
11929
  if (written === null) {
10854
11930
  return null;
10855
11931
  }
@@ -10858,7 +11934,7 @@ function classRefOf(db, file, node) {
10858
11934
  };
10859
11935
  }
10860
11936
  function nameOf(db, node) {
10861
- return symbolValue(node) ?? (0, import_values28.literalOf)(evaluatedValue(node, db));
11937
+ return symbolValue(node) ?? (0, import_values29.literalOf)(evaluatedValue(node, db));
10862
11938
  }
10863
11939
  var FactoryClasses = class {
10864
11940
  constructor(db, definitions, inflections) {
@@ -10930,7 +12006,7 @@ function builtAt(db, call, pattern) {
10930
12006
 
10931
12007
  // src/testSharedGroups.ts
10932
12008
  var import_resolution15 = require("@suss/resolution");
10933
- var import_values30 = require("@suss/values");
12009
+ var import_values31 = require("@suss/values");
10934
12010
  var ByNode = class {
10935
12011
  byTree = /* @__PURE__ */ new WeakMap();
10936
12012
  set(node, value) {
@@ -11034,7 +12110,7 @@ var SharedGroupIndex = class {
11034
12110
  }
11035
12111
  nameOf(call) {
11036
12112
  const first = firstArgument(call);
11037
- return first === null ? null : (0, import_values30.literalOf)(evaluatedValue(first, this.db));
12113
+ return first === null ? null : (0, import_values31.literalOf)(evaluatedValue(first, this.db));
11038
12114
  }
11039
12115
  noteRead(definition) {
11040
12116
  if (definition !== null) {
@@ -11111,16 +12187,16 @@ function visibleAt(index, site, pattern) {
11111
12187
  }
11112
12188
 
11113
12189
  // src/version.ts
11114
- var import_extractor16 = require("@suss/extractor");
12190
+ var import_extractor18 = require("@suss/extractor");
11115
12191
  var ADAPTER_VERSION = "0.1.0";
11116
- var adapterStamp = (0, import_extractor16.createAdapterStamp)({
12192
+ var adapterStamp = (0, import_extractor18.createAdapterStamp)({
11117
12193
  moduleUrl: importMetaUrl,
11118
12194
  version: ADAPTER_VERSION
11119
12195
  });
11120
12196
 
11121
12197
  // src/project.ts
11122
12198
  function keptRubyParses() {
11123
- return new import_extractor17.KeptParses((root) => root.tree.delete());
12199
+ return new import_extractor19.KeptParses((root) => root.tree.delete());
11124
12200
  }
11125
12201
  async function parseRubyAhead(files, kept) {
11126
12202
  for (const file of files) {
@@ -11225,7 +12301,7 @@ var RunFacts = class {
11225
12301
  ...this.testFiles.map(({ file }) => file),
11226
12302
  ...this.parsed.map(({ file }) => file).filter(
11227
12303
  (file) => classTests.some(
11228
- (one) => (0, import_extractor17.matchesTestFileName)(file, one.filePatterns)
12304
+ (one) => (0, import_extractor19.matchesTestFileName)(file, one.filePatterns)
11229
12305
  )
11230
12306
  )
11231
12307
  ]);
@@ -11260,7 +12336,7 @@ function factsForFile(options) {
11260
12336
  return facts.db;
11261
12337
  }
11262
12338
  function moduleInitUnit(options) {
11263
- const raw = (0, import_extractor17.moduleInitStructure)({
12339
+ const raw = (0, import_extractor19.moduleInitStructure)({
11264
12340
  name: options.name,
11265
12341
  file: options.file,
11266
12342
  range: options.range,
@@ -11274,7 +12350,7 @@ function moduleInitUnit(options) {
11274
12350
  }
11275
12351
  function discoveredAs(raw) {
11276
12352
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
11277
- const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir8.boundaryKey)(raw.boundaryBinding) ?? "";
12353
+ const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir9.boundaryKey)(raw.boundaryBinding) ?? "";
11278
12354
  return `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
11279
12355
  }
11280
12356
  function replaysCleanly(units, discovered) {
@@ -11291,16 +12367,16 @@ function replaysCleanly(units, discovered) {
11291
12367
  return true;
11292
12368
  }
11293
12369
  async function extractRubyProject(options) {
11294
- const timer = options.onTiming !== void 0 ? (0, import_extractor17.createTimer)() : (0, import_extractor17.noopTimer)();
12370
+ const timer = options.onTiming !== void 0 ? (0, import_extractor19.createTimer)() : (0, import_extractor19.noopTimer)();
11295
12371
  const cacheDir = adapterStamp.declineWhenRunFromSource(
11296
12372
  options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? import_node_path4.default.join(options.projectRoot, ".suss", "cache") : null)
11297
12373
  );
11298
- const layer = (0, import_extractor17.createCacheLayer)(cacheDir);
12374
+ const layer = (0, import_extractor19.createCacheLayer)(cacheDir);
11299
12375
  const packsDigest = `${adapterStamp.packsDigest(
11300
12376
  options.packs.map(
11301
12377
  (pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
11302
12378
  )
11303
- )}|${(0, import_extractor17.extractionConfigStamp)({
12379
+ )}|${(0, import_extractor19.extractionConfigStamp)({
11304
12380
  gapHandling: options.gapHandling,
11305
12381
  workspaceRoot: options.workspaceRoot,
11306
12382
  projectRoot: options.projectRoot,
@@ -11308,7 +12384,7 @@ async function extractRubyProject(options) {
11308
12384
  })}`;
11309
12385
  const input = {
11310
12386
  files: cacheDir === null ? [] : options.files,
11311
- adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor17.runDigest)(packsDigest, options.packs, options.files)
12387
+ adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor19.runDigest)(packsDigest, options.packs, options.files)
11312
12388
  };
11313
12389
  const lookup = await timer.timeAsync(
11314
12390
  "cache.lookup",
@@ -11363,7 +12439,7 @@ async function runRuby(options, cache, previous) {
11363
12439
  const db = new import_datalog3.Database();
11364
12440
  const summaries = [];
11365
12441
  const gapHandling = options.gapHandling ?? "permissive";
11366
- const tallies = (0, import_extractor10.createPackTallies)(options.packs);
12442
+ const tallies = (0, import_extractor12.createPackTallies)(options.packs);
11367
12443
  const ledger = cacheDir === null ? null : new import_resolution16.DependencyLedger(options.files);
11368
12444
  const log = ledger === null ? null : new import_resolution16.FactLog(db, ledger);
11369
12445
  if (ledger !== null) {
@@ -11465,7 +12541,7 @@ async function runRuby(options, cache, previous) {
11465
12541
  continue;
11466
12542
  }
11467
12543
  discovered.add(unit.dedup);
11468
- (0, import_extractor10.tallyUnit)(tallies, unit.recognition);
12544
+ (0, import_extractor12.tallyUnit)(tallies, unit.recognition);
11469
12545
  found.push({
11470
12546
  replayed: unit.summary ?? null,
11471
12547
  seedKey: unit.seed?.key ?? null
@@ -11546,7 +12622,7 @@ async function runRuby(options, cache, previous) {
11546
12622
  continue;
11547
12623
  }
11548
12624
  discovered.add(dedup);
11549
- (0, import_extractor10.tallyUnit)(tallies, recognition);
12625
+ (0, import_extractor12.tallyUnit)(tallies, recognition);
11550
12626
  found.push({ raw, seedKey, draft: units[units.length - 1] });
11551
12627
  if (seed !== void 0 && seedKey !== null && !seedKeys.has(seedKey)) {
11552
12628
  seedKeys.add(seedKey);
@@ -11609,7 +12685,7 @@ async function runRuby(options, cache, previous) {
11609
12685
  }
11610
12686
  const summary = timer.time(
11611
12687
  "summarize",
11612
- () => (0, import_extractor17.assembleSummary)(routingGapUnit(pattern, gaps), { gapHandling })
12688
+ () => (0, import_extractor19.assembleSummary)(routingGapUnit(pattern, gaps), { gapHandling })
11613
12689
  );
11614
12690
  summary.confidence = { source: "inferred_static", level: "low" };
11615
12691
  found.push({ summary });
@@ -11665,7 +12741,7 @@ async function runRuby(options, cache, previous) {
11665
12741
  }
11666
12742
  const summary = timer.time(
11667
12743
  "summarize",
11668
- () => (0, import_extractor17.assembleSummary)(raw, { gapHandling })
12744
+ () => (0, import_extractor19.assembleSummary)(raw, { gapHandling })
11669
12745
  );
11670
12746
  summaries.push(summary);
11671
12747
  assembledHere.add(summary);
@@ -11683,13 +12759,13 @@ async function runRuby(options, cache, previous) {
11683
12759
  }
11684
12760
  if (gapHandling !== "silent") {
11685
12761
  summary.gaps.push(
11686
- ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir8.unfollowedCallGap)
12762
+ ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir9.unfollowedCallGap)
11687
12763
  );
11688
12764
  }
11689
- (0, import_behavioral_ir8.placeCalls)(summary, reached.targetsByKey.get(key));
11690
- (0, import_behavioral_ir8.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
11691
- (0, import_behavioral_ir8.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
11692
- (0, import_behavioral_ir8.placeReceiverClasses)(summary, reached.receiverClassesByKey.get(key));
12765
+ (0, import_behavioral_ir9.placeCalls)(summary, reached.targetsByKey.get(key));
12766
+ (0, import_behavioral_ir9.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
12767
+ (0, import_behavioral_ir9.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
12768
+ (0, import_behavioral_ir9.placeReceiverClasses)(summary, reached.receiverClassesByKey.get(key));
11693
12769
  }
11694
12770
  }
11695
12771
  if (ledger !== null) {
@@ -11698,7 +12774,7 @@ async function runRuby(options, cache, previous) {
11698
12774
  }
11699
12775
  }
11700
12776
  if (gapHandling !== "silent") {
11701
- (0, import_behavioral_ir8.recordParameterGaps)(
12777
+ (0, import_behavioral_ir9.recordParameterGaps)(
11702
12778
  reached.parameterCallsByKey,
11703
12779
  summariesBySeed,
11704
12780
  reached.passedPositions
@@ -11711,20 +12787,20 @@ async function runRuby(options, cache, previous) {
11711
12787
  throw new ReplayFailed("");
11712
12788
  }
11713
12789
  const dependencies = (0, import_resolution16.importedFilesByFile)(db, displayPathOf);
11714
- (0, import_extractor17.stampModuleImports)(summaries, (file) => dependencies.get(file) ?? []);
11715
- (0, import_extractor17.stampModules)(summaries, declaredModules, options.workspaceRoot);
12790
+ (0, import_extractor19.stampModuleImports)(summaries, (file) => dependencies.get(file) ?? []);
12791
+ (0, import_extractor19.stampModules)(summaries, declaredModules, options.workspaceRoot);
11716
12792
  const idRoot = options.projectRoot ?? options.workspaceRoot;
11717
12793
  for (const summary of summaries) {
11718
12794
  const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : import_node_path4.default.resolve(options.workspaceRoot, summary.location.file);
11719
- summary.identity.id = (0, import_behavioral_ir8.summaryIdFromParts)({
12795
+ summary.identity.id = (0, import_behavioral_ir9.summaryIdFromParts)({
11720
12796
  workspace: void 0,
11721
12797
  file: idRoot === void 0 ? absoluteFile : import_node_path4.default.relative(idRoot, absoluteFile),
11722
12798
  name: summary.identity.name,
11723
12799
  exportPath: summary.identity.exportPath
11724
12800
  });
11725
12801
  }
11726
- (0, import_behavioral_ir8.disambiguateSummaryIds)(summaries);
11727
- (0, import_behavioral_ir8.linkCallsToSummaries)(summaries);
12802
+ (0, import_behavioral_ir9.disambiguateSummaryIds)(summaries);
12803
+ (0, import_behavioral_ir9.linkCallsToSummaries)(summaries);
11728
12804
  const composed = timer.time("summarize", () => {
11729
12805
  markExitCodeFunctions(
11730
12806
  summaries,
@@ -11732,7 +12808,7 @@ async function runRuby(options, cache, previous) {
11732
12808
  db,
11733
12809
  options.workspaceRoot
11734
12810
  );
11735
- return (0, import_extractor17.composeWrappers)(summaries, { gapHandling });
12811
+ return (0, import_extractor19.composeWrappers)(summaries, { gapHandling });
11736
12812
  });
11737
12813
  await timer.timeAsync("cache.write", async () => {
11738
12814
  if (cacheDir === null || composed.length === 0) {
@@ -12011,7 +13087,7 @@ var RubyWhySession = class {
12011
13087
  // src/stubEvidence.ts
12012
13088
  var import_node_fs5 = __toESM(require("fs"), 1);
12013
13089
  var import_node_path6 = __toESM(require("path"), 1);
12014
- var import_extractor18 = require("@suss/extractor");
13090
+ var import_extractor20 = require("@suss/extractor");
12015
13091
  function bareLetters(name) {
12016
13092
  return name.replaceAll(/[-_]/g, "").toLowerCase();
12017
13093
  }
@@ -12035,14 +13111,14 @@ function requireTarget(node) {
12035
13111
  }
12036
13112
  function requireSitesIn(root, file) {
12037
13113
  const found = [];
12038
- (0, import_extractor18.walkDescendants)(root, null, {
13114
+ (0, import_extractor20.walkDescendants)(root, null, children, {
12039
13115
  at: (node) => {
12040
13116
  const target = requireTarget(node);
12041
13117
  if (target !== null) {
12042
13118
  found.push({ target, file, line: rangeOf(node).start });
12043
13119
  }
12044
13120
  },
12045
- into: (node) => node.type === "argument_list" ? import_extractor18.SKIP_CHILDREN : null
13121
+ into: (node) => node.type === "argument_list" ? import_extractor20.SKIP_CHILDREN : null
12046
13122
  });
12047
13123
  return found;
12048
13124
  }
@@ -12086,6 +13162,7 @@ async function rubyStubEvidence(options) {
12086
13162
  bindEvaluator,
12087
13163
  bodyStatements,
12088
13164
  booleanLiteralValue,
13165
+ children,
12089
13166
  collectFileConstants,
12090
13167
  createConstantFileCache,
12091
13168
  createFileCache,