@suss/adapter-ruby 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,7 +63,52 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
63
63
  "singleton_class"
64
64
  ]);
65
65
  var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
66
+ var INCLUDE_CALL = "include";
67
+ var PREPEND_CALL = "prepend";
66
68
  var ARGUMENT_LIST_TYPE = "argument_list";
69
+ var BLOCK_TYPES = /* @__PURE__ */ new Set(["block", "do_block"]);
70
+ var NO_BODY_BLOCKS = /* @__PURE__ */ new Map();
71
+ function isModuleBody(body) {
72
+ return body.parent?.type === "module";
73
+ }
74
+ function declaredBlockAt(statement, isModule, blocks) {
75
+ if (statement.type !== "call" || field(statement, "receiver") !== null) {
76
+ return null;
77
+ }
78
+ const method = field(statement, "method");
79
+ const kind = method === null ? void 0 : blocks.get(method.text);
80
+ if (kind === void 0 || kind.moduleOnly && !isModule) {
81
+ return null;
82
+ }
83
+ return field(statement, "block") === null ? null : kind;
84
+ }
85
+ function bodyStatementsRun(body, isModule, blocks) {
86
+ return bodyStatements(body).flatMap((statement) => {
87
+ if (declaredBlockAt(statement, isModule, blocks) === null) {
88
+ return [statement];
89
+ }
90
+ const block = field(statement, "block");
91
+ const inner = block === null ? null : field(block, "body");
92
+ return inner === null ? [] : bodyStatementsRun(inner, isModule, blocks);
93
+ });
94
+ }
95
+ function definedAtClassLevel(node, body, blocks) {
96
+ if (blocks.size === 0) {
97
+ return false;
98
+ }
99
+ const isModule = isModuleBody(body);
100
+ let current = node.parent;
101
+ while (current !== null && current.id !== body.id) {
102
+ const parent = current.parent;
103
+ if (BLOCK_TYPES.has(current.type) && parent !== null) {
104
+ if (declaredBlockAt(parent, isModule, blocks)?.definesClassMethods) {
105
+ return true;
106
+ }
107
+ }
108
+ current = parent;
109
+ }
110
+ return false;
111
+ }
67
112
  function runStatements(body, blockConfigures = () => false) {
68
113
  const found = [];
69
114
  const readsItsChildren = (node) => {
@@ -99,10 +144,10 @@ function stringLiteralValue(node) {
99
144
  function symbolValue(node) {
100
145
  return node.type === "simple_symbol" ? node.text.slice(1) : null;
101
146
  }
102
- function instanceMethodsByName(body) {
147
+ function instanceMethodsByName(body, blocks = NO_BODY_BLOCKS) {
103
148
  const methods = /* @__PURE__ */ new Map();
104
149
  for (const stmt of runStatements(body)) {
105
- if (stmt.type !== "method") {
150
+ if (stmt.type !== "method" || definedAtClassLevel(stmt, body, blocks)) {
106
151
  continue;
107
152
  }
108
153
  const name = field(stmt, "name")?.text;
@@ -164,10 +209,11 @@ function instanceMethodVisibility(body) {
164
209
  }
165
210
  return visibility;
166
211
  }
167
- function singletonMethodsByName(body) {
212
+ function singletonMethodsByName(body, blocks = NO_BODY_BLOCKS) {
168
213
  const methods = /* @__PURE__ */ new Map();
169
214
  for (const stmt of runStatements(body)) {
170
- if (stmt.type !== "singleton_method") {
215
+ const classLevel = stmt.type === "method" && definedAtClassLevel(stmt, body, blocks);
216
+ if (stmt.type !== "singleton_method" && !classLevel) {
171
217
  continue;
172
218
  }
173
219
  const name = field(stmt, "name")?.text;
@@ -280,7 +326,8 @@ function autoloadDirectories(root) {
280
326
  }
281
327
 
282
328
  // src/defineMethod.ts
283
- import { force as force2, literalOf as literalOf2 } from "@suss/values";
329
+ import { nodeOfKey as nodeOfKey2 } from "@suss/resolution";
330
+ import { hole as hole2, literalOf as literalOf3, piecesOf } from "@suss/values";
284
331
 
285
332
  // src/values/evaluator.ts
286
333
  import { nodeOfKey } from "@suss/resolution";
@@ -289,40 +336,56 @@ import { Evaluator, force, literalOf, text } from "@suss/values";
289
336
  // src/facts/resolve.ts
290
337
  import { constant, lit, rule, variable as v } from "@suss/datalog";
291
338
  import {
339
+ allocationSitesOf,
340
+ alsoSteps,
292
341
  askResolution,
342
+ askResolutionUnder,
343
+ INSTANCE_STEP,
293
344
  resolutionProgram,
345
+ resolutionUnderProgram,
294
346
  writtenValueOf as sharedWrittenValueOf,
347
+ writtenValueUnder as sharedWrittenValueUnder,
295
348
  VALUE_STEP
296
349
  } from "@suss/resolution";
297
- var RUBY_RULES = [
298
- // `Loader.new` makes one of the class, which the shared rules already say
299
- // about calling a class. Ruby writes it as a method read off the constant
300
- // instead, so the read is what steps to the class here.
350
+ var RUBY_RULES = alsoSteps([
351
+ // Making one of a class is a call of the class, which the shared rules
352
+ // already say. Ruby writes the callee as `new` read off the constant, so
353
+ // what this adds is that the callee is the class.
301
354
  rule(
302
- "stepsTo",
303
- [v("x"), v("cls"), VALUE_STEP],
355
+ "hop",
356
+ [v("x"), v("cls"), INSTANCE_STEP],
304
357
  [
305
358
  lit("readsProperty", v("x"), v("o"), constant("new")),
306
359
  lit("comesTo", v("o"), v("cls")),
307
360
  lit("objectValue", v("cls"))
308
361
  ]
309
362
  ),
310
- // The shared `declared finder` step, for the same spelling: `Account.first`
311
- // and `Account.where(x).first` write no arguments, so Ruby reads them as
312
- // property reads and neither gets a `call` fact to hang that step on.
363
+ // The same spelling again, for the caller asking where a class was
364
+ // made. `callsNamed` reaches a callee written as the name itself, and
365
+ // Ruby writes `new` off the constant instead.
313
366
  rule(
314
- "stepsTo",
315
- [v("x"), v("cls"), VALUE_STEP],
367
+ "constructsNamed",
368
+ [v("r"), v("cls")],
316
369
  [
317
- lit("readsProperty", v("x"), v("o"), v("m")),
318
- lit("objectOf", v("o"), v("cls")),
319
- lit("objectValue", v("cls")),
320
- lit("libraryBase", v("cls"), v("n")),
321
- lit("givesBackOne", v("n"), v("m"))
322
- ],
323
- "declared finder"
370
+ lit("binds", v("o"), v("cls")),
371
+ lit("readsProperty", v("c"), v("o"), constant("new")),
372
+ lit("call", v("r"), v("c"))
373
+ ]
374
+ ),
375
+ // `%i[a b].freeze` is worth the list it was written as. The evaluator
376
+ // says the same in its row table, for a value it reads in one file.
377
+ ...["freeze", "dup"].map(
378
+ (method) => rule(
379
+ "hop",
380
+ [v("r"), v("o"), VALUE_STEP],
381
+ [
382
+ lit("call", v("r"), v("c")),
383
+ lit("readsProperty", v("c"), v("o"), constant(method))
384
+ ],
385
+ "hands back the receiver"
386
+ )
324
387
  )
325
- ];
388
+ ]);
326
389
  var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
327
390
  function resolveValues(db, keys) {
328
391
  askResolution(db, keys, "wanted", RUBY_PROGRAM);
@@ -334,14 +397,122 @@ function writtenValueOf(db, key) {
334
397
  resolveValues(db, [key]);
335
398
  return sharedWrittenValueOf(db, key, (keys) => resolveValues(db, keys));
336
399
  }
400
+ function writtenValueUnder(db, key, site) {
401
+ askResolutionUnder(db, [[key, site]], resolutionUnderProgram(RUBY_RULES));
402
+ return sharedWrittenValueUnder(db, key, site);
403
+ }
404
+ function constructionSites(db, classKey) {
405
+ askResolution(db, [classKey], "wantedSites", RUBY_PROGRAM);
406
+ return allocationSitesOf(db, classKey);
407
+ }
337
408
 
338
409
  // src/facts/values.ts
339
410
  import { startsAtName, valueLeftByWrites } from "@suss/resolution";
340
411
 
412
+ // src/paths/bareCalls.ts
413
+ var PARAMETER_LIST_TYPES = [
414
+ "method_parameters",
415
+ "block_parameters",
416
+ "lambda_parameters",
417
+ "parameters"
418
+ ];
419
+ function isFieldOf(parent, node, name) {
420
+ return field(parent, name)?.id === node.id;
421
+ }
422
+ function namedAt(parent, node) {
423
+ return isFieldOf(parent, node, "name");
424
+ }
425
+ function always() {
426
+ return true;
427
+ }
428
+ var SPELLS_A_NAME = {
429
+ call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
430
+ method: namedAt,
431
+ singleton_method: namedAt,
432
+ assignment: (parent, node) => isFieldOf(parent, node, "left"),
433
+ operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
434
+ for: (parent, node) => isFieldOf(parent, node, "pattern"),
435
+ optional_parameter: namedAt,
436
+ splat_parameter: namedAt,
437
+ hash_splat_parameter: namedAt,
438
+ keyword_parameter: namedAt,
439
+ block_parameter: namedAt,
440
+ destructured_parameter: always,
441
+ left_assignment_list: always,
442
+ rest_assignment: always,
443
+ destructured_left_assignment: always,
444
+ exception_variable: always,
445
+ undef: always,
446
+ alias: always,
447
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
448
+ };
449
+ function addNamesUnder(node, names) {
450
+ if (node === null) {
451
+ return;
452
+ }
453
+ if (node.type === "identifier") {
454
+ names.add(node.text);
455
+ return;
456
+ }
457
+ for (const child of bodyStatements(node)) {
458
+ addNamesUnder(child, names);
459
+ }
460
+ }
461
+ function parameterNames(node, names) {
462
+ for (const parameter of bodyStatements(node)) {
463
+ if (parameter.type === "identifier") {
464
+ names.add(parameter.text);
465
+ continue;
466
+ }
467
+ const declared = field(parameter, "name");
468
+ if (declared !== null && declared.type === "identifier") {
469
+ names.add(declared.text);
470
+ continue;
471
+ }
472
+ addNamesUnder(parameter, names);
473
+ }
474
+ }
475
+ function assignedNames(node, names) {
476
+ addNamesUnder(field(node, "left"), names);
477
+ }
478
+ var BINDS_A_NAME = {
479
+ assignment: assignedNames,
480
+ operator_assignment: assignedNames,
481
+ for: (node, names) => addNamesUnder(field(node, "pattern"), names),
482
+ exception_variable: (node, names) => addNamesUnder(node, names),
483
+ ...Object.fromEntries(
484
+ PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
485
+ )
486
+ };
487
+ function localNamesIn(definitionNode) {
488
+ const names = /* @__PURE__ */ new Set();
489
+ const visit = (node) => {
490
+ BINDS_A_NAME[node.type]?.(node, names);
491
+ for (const child of bodyStatements(node)) {
492
+ visit(child);
493
+ }
494
+ };
495
+ visit(definitionNode);
496
+ return names;
497
+ }
498
+ function spellsAName(node) {
499
+ const parent = node.parent;
500
+ if (parent === null) {
501
+ return true;
502
+ }
503
+ return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
504
+ }
505
+ function isBareMethodCall(node, locals) {
506
+ if (node.type !== "identifier" || locals.has(node.text)) {
507
+ return false;
508
+ }
509
+ return !spellsAName(node);
510
+ }
511
+
341
512
  // src/facts/locals.ts
342
513
  import { writesRunInOrder } from "@suss/resolution";
343
- var BLOCK_TYPES = /* @__PURE__ */ new Set(["block", "do_block"]);
344
- var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES]);
514
+ var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
515
+ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES2]);
345
516
  var RUBY_NAME_TYPES = /* @__PURE__ */ new Set([
346
517
  "identifier",
347
518
  "instance_variable"
@@ -362,7 +533,7 @@ function parametersOf(owner) {
362
533
  const params = field(owner, "parameters");
363
534
  return params === null ? [] : bodyStatements(params);
364
535
  }
365
- function parameterNames(owner) {
536
+ function parameterNames2(owner) {
366
537
  const names = /* @__PURE__ */ new Set();
367
538
  for (const param of parametersOf(owner)) {
368
539
  const name = paramNameOf(param);
@@ -410,6 +581,13 @@ function readForVariable(node) {
410
581
  }
411
582
  return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
412
583
  }
584
+ function readRescueVariable(node) {
585
+ const variable = field(node, "variable");
586
+ if (variable === null) {
587
+ return [];
588
+ }
589
+ return bodyStatements(variable).flatMap(targetsOf).map((target) => writeOf(target, null, node, false));
590
+ }
413
591
  function readBlockParameters(node) {
414
592
  const found = [];
415
593
  for (const param of parametersOf(node)) {
@@ -424,6 +602,7 @@ var WRITE_READERS = {
424
602
  assignment: readAssignment,
425
603
  operator_assignment: readOperatorAssignment,
426
604
  for: readForVariable,
605
+ rescue: readRescueVariable,
427
606
  block: readBlockParameters,
428
607
  do_block: readBlockParameters
429
608
  };
@@ -469,7 +648,7 @@ function namesOfMethod(method) {
469
648
  if (remembered !== void 0) {
470
649
  return remembered;
471
650
  }
472
- const names = scopeNames(parameterNames(method), field(method, "body"));
651
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
473
652
  cache.byMethod.set(method, names);
474
653
  return names;
475
654
  }
@@ -491,10 +670,17 @@ function ownerOfName(node, name, enclosing) {
491
670
  }
492
671
  return enclosing !== null && names.locals.has(name) ? enclosing : null;
493
672
  }
673
+ function isLocalName(node, name, enclosing) {
674
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
675
+ if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
676
+ return true;
677
+ }
678
+ return names.locals.has(name);
679
+ }
494
680
  function blockDeclaring(node, name) {
495
681
  let current = node.parent;
496
682
  while (current !== null) {
497
- if (BLOCK_TYPES.has(current.type) && parameterNames(current).has(name)) {
683
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
498
684
  return current;
499
685
  }
500
686
  if (OWN_BODY_TYPES.has(current.type)) {
@@ -550,6 +736,17 @@ function collectWrites(method, body) {
550
736
  }
551
737
  return [...groups.values()];
552
738
  }
739
+ function instanceWritesRunInOrder(body, name, targets) {
740
+ return writesRunInOrder(
741
+ body,
742
+ name,
743
+ targets.map((target) => ({
744
+ at: target,
745
+ direct: target.parent?.parent?.id === body.id
746
+ })),
747
+ nameReads(new Set(targets.map((target) => target.id)))
748
+ );
749
+ }
553
750
  function orderedWrite(write, scope) {
554
751
  return {
555
752
  at: write.at,
@@ -595,12 +792,28 @@ function readKey(filePath, written, enclosing) {
595
792
  if (node.type !== "identifier" && node.type !== "constant") {
596
793
  return nodeId(filePath, node);
597
794
  }
598
- const parent = node.parent;
599
- if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
600
- return isPropertyRead(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
795
+ if (isMethodOfReceiver(node) || isBareCall(node, enclosing)) {
796
+ return nodeId(filePath, node);
601
797
  }
602
798
  return nameKey(filePath, node, enclosing);
603
799
  }
800
+ function isMethodOfReceiver(node) {
801
+ const parent = node.parent;
802
+ return parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null;
803
+ }
804
+ function isReceiverOfCall(node) {
805
+ const parent = node.parent;
806
+ return parent !== null && parent.type === "call" && field(parent, "receiver")?.id === node.id;
807
+ }
808
+ function isBareCall(node, enclosing) {
809
+ if (node.type !== "identifier") {
810
+ return false;
811
+ }
812
+ if (spellsAName(node) && !isReceiverOfCall(node)) {
813
+ return false;
814
+ }
815
+ return !isLocalName(node, node.text, enclosing);
816
+ }
604
817
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
605
818
  "string",
606
819
  "integer",
@@ -629,11 +842,7 @@ function add(emitter, relation, ...tuple) {
629
842
  emitter.db.add(relation, tuple);
630
843
  }
631
844
  function valueKey(emitter, written) {
632
- const value = readThrough(written);
633
- if (value.type !== "identifier" && value.type !== "constant") {
634
- return nodeId(emitter.filePath, value);
635
- }
636
- return nameKey(emitter.filePath, value, emitter.enclosing);
845
+ return readKey(emitter.filePath, written, emitter.enclosing);
637
846
  }
638
847
  function pairKeyText(key) {
639
848
  if (key.type === "hash_key_symbol") {
@@ -647,35 +856,29 @@ function pairKeyText(key) {
647
856
  }
648
857
  return null;
649
858
  }
650
- function isPropertyRead(node) {
651
- return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null && children(node).every(
652
- (child) => child.type !== "do_block" && child.type !== "block"
653
- );
654
- }
655
- function emitPropertyRead(emitter, node) {
656
- const receiver = field(node, "receiver");
657
- const method = field(node, "method");
658
- if (receiver === null || method === null) {
659
- return;
859
+ function calleeKeyOf(filePath, node, enclosing) {
860
+ if (isBareCall(node, enclosing)) {
861
+ return nameKey(filePath, node, enclosing);
660
862
  }
661
- add(
662
- emitter,
663
- "readsProperty",
664
- nodeId(emitter.filePath, node),
665
- valueKey(emitter, receiver),
666
- method.text
667
- );
863
+ const method = node.type === "call" ? field(node, "method") : null;
864
+ if (method === null) {
865
+ return null;
866
+ }
867
+ return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
668
868
  }
669
869
  function emitCall(emitter, call) {
670
870
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
671
- const method = field(call, "method");
672
- if (method === null) {
871
+ const method = call.type === "identifier" ? call : field(call, "method");
872
+ const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
873
+ if (method === null || calleeKey === null) {
673
874
  return;
674
875
  }
675
876
  const receiver = field(call, "receiver");
676
877
  const callKey = nodeId(emitter.filePath, call);
677
- const calleeKey = receiver === null ? valueKey(emitter, method) : nodeId(emitter.filePath, method);
678
878
  add(emitter, "call", callKey, calleeKey);
879
+ if (!emitter.insideMethod) {
880
+ add(emitter, "callOutsideMethod", callKey);
881
+ }
679
882
  if (receiver !== null) {
680
883
  add(
681
884
  emitter,
@@ -684,6 +887,8 @@ function emitCall(emitter, call) {
684
887
  valueKey(emitter, receiver),
685
888
  method.text
686
889
  );
890
+ } else if (emitter.selfKey !== null) {
891
+ add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
687
892
  }
688
893
  const args = field(call, "arguments");
689
894
  let position = 0;
@@ -744,20 +949,71 @@ function emitHash(emitter, hash) {
744
949
  add(emitter, "holdsProperty", objectKey, keyText, valueKey(emitter, value));
745
950
  }
746
951
  }
747
- function walkExpressions(node, visit) {
952
+ var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
953
+ var LOOP_METHODS = /* @__PURE__ */ new Set(["each", "each_with_index", "map"]);
954
+ var DEFINE_METHOD_CALL = "define_method";
955
+ function loopTurnAt(emitter, block) {
956
+ const call = block.parent;
957
+ if (call?.type !== "call") {
958
+ return null;
959
+ }
960
+ const receiver = field(call, "receiver");
961
+ const method = field(call, "method");
962
+ if (receiver === null || method === null || !LOOP_METHODS.has(method.text)) {
963
+ return null;
964
+ }
965
+ const parameters = field(block, "parameters");
966
+ const names = parameters === null ? [] : children(parameters).map((p) => p.text);
967
+ const element = names[0];
968
+ if (element === void 0) {
969
+ return null;
970
+ }
971
+ return {
972
+ element,
973
+ index: names[1] ?? "",
974
+ overKey: nodeId(emitter.filePath, receiver)
975
+ };
976
+ }
977
+ function emitDynamicDefinition(emitter, call, turns) {
978
+ if (emitter.selfKey === null || // A call inside a method runs when that method does, not at load time.
979
+ emitter.enclosing !== null || call.type !== "call" || field(call, "receiver") !== null || field(call, "method")?.text !== DEFINE_METHOD_CALL) {
980
+ return;
981
+ }
982
+ const args = field(call, "arguments");
983
+ const first = args === null ? void 0 : children(args)[0];
984
+ const nameKey2 = nodeId(emitter.filePath, first ?? call);
985
+ add(emitter, "definesMethodFrom", emitter.selfKey, nameKey2);
986
+ for (const turn of turns) {
987
+ add(
988
+ emitter,
989
+ "nameTurnsOn",
990
+ nameKey2,
991
+ turn.element,
992
+ turn.index,
993
+ turn.overKey
994
+ );
995
+ }
996
+ }
997
+ function walkExpressions(node, emitter, visit, turns = []) {
748
998
  for (const child of children(node)) {
749
999
  if (OWN_BODY_TYPES.has(child.type)) {
750
1000
  continue;
751
1001
  }
752
- visit(child);
753
- walkExpressions(child, visit);
1002
+ visit(child, turns);
1003
+ const opened = BLOCK_TYPES3.has(child.type) ? loopTurnAt(emitter, child) : null;
1004
+ walkExpressions(
1005
+ child,
1006
+ emitter,
1007
+ visit,
1008
+ opened === null ? turns : [...turns, opened]
1009
+ );
754
1010
  }
755
1011
  }
756
1012
  function emitExpressionFacts(emitter, node) {
757
- walkExpressions(node, (child) => {
758
- if (isPropertyRead(child)) {
759
- emitPropertyRead(emitter, child);
760
- } else if (child.type === "call") {
1013
+ emitDynamicDefinition(emitter, node, []);
1014
+ walkExpressions(node, emitter, (child, turns) => {
1015
+ emitDynamicDefinition(emitter, child, turns);
1016
+ if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
761
1017
  emitCall(emitter, child);
762
1018
  }
763
1019
  if (ARRAY_TYPES.has(child.type)) {
@@ -813,28 +1069,52 @@ function collectInstanceWrite(emitter, node) {
813
1069
  const operator = field(node, "operator")?.text;
814
1070
  const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
815
1071
  const written = collector.get(left.text) ?? [];
816
- written.push(
817
- describeWrite(emitter, {
1072
+ written.push({
1073
+ write: describeWrite(emitter, {
818
1074
  name: left.text,
819
1075
  target: left,
820
1076
  value,
821
1077
  at: node,
822
1078
  fromParameter: false
823
- })
824
- );
1079
+ }),
1080
+ target: left
1081
+ });
825
1082
  collector.set(left.text, written);
826
1083
  }
1084
+ function settledWrites(writes, ordered) {
1085
+ const described = writes.map((written) => written.write);
1086
+ const settled = valueLeftByWrites(described, ordered);
1087
+ if (settled !== null) {
1088
+ return [settled];
1089
+ }
1090
+ const left = [];
1091
+ for (const write of described) {
1092
+ if (write.value !== null && !write.narrowsName) {
1093
+ left.push(write.value);
1094
+ }
1095
+ }
1096
+ return left;
1097
+ }
827
1098
  function emitInstanceWrites(emitter, classKey, collected) {
828
1099
  for (const [name, writes] of collected) {
829
- const settled = valueLeftByWrites(writes, false);
830
- if (settled !== null) {
831
- add(emitter, "holdsProperty", classKey, name, settled);
1100
+ for (const value of settledWrites(writes, false)) {
1101
+ add(emitter, "holdsProperty", classKey, name, value);
1102
+ }
1103
+ }
1104
+ }
1105
+ function emitInstanceStores(emitter, funcKey, body, collected) {
1106
+ for (const [name, writes] of collected) {
1107
+ const deciding = writes.filter((written) => !written.write.narrowsName);
1108
+ if (deciding.length === 0) {
832
1109
  continue;
833
1110
  }
834
- for (const write of writes) {
835
- if (write.value !== null && !write.narrowsName) {
836
- add(emitter, "holdsProperty", classKey, name, write.value);
837
- }
1111
+ const ordered = instanceWritesRunInOrder(
1112
+ body,
1113
+ name,
1114
+ deciding.map((written) => written.target)
1115
+ );
1116
+ for (const value of settledWrites(deciding, ordered)) {
1117
+ add(emitter, "storesProperty", funcKey, name, value);
838
1118
  }
839
1119
  }
840
1120
  }
@@ -843,7 +1123,20 @@ function implicitReturn(body) {
843
1123
  (child) => child.type !== "rescue" && child.type !== "ensure"
844
1124
  );
845
1125
  const last = statements[statements.length - 1];
846
- return last === void 0 || last.type === "return" ? null : last;
1126
+ if (last === void 0 || last.type === "return") {
1127
+ return null;
1128
+ }
1129
+ return assignedValueOf(last) ?? last;
1130
+ }
1131
+ function assignedValueOf(node) {
1132
+ if (!ASSIGNMENT_TYPES.has(node.type)) {
1133
+ return null;
1134
+ }
1135
+ const operator = field(node, "operator")?.text;
1136
+ if (node.type !== "assignment" && !WHOLE_VALUE_OPERATORS.has(operator ?? "")) {
1137
+ return null;
1138
+ }
1139
+ return field(node, "right");
847
1140
  }
848
1141
  function emitMethodFacts(emitter, method) {
849
1142
  const funcKey = nodeId(emitter.filePath, method);
@@ -873,7 +1166,7 @@ function emitMethodFacts(emitter, method) {
873
1166
  }
874
1167
  };
875
1168
  recordNested(body);
876
- walkExpressions(body, (child) => {
1169
+ walkExpressions(body, inside, (child) => {
877
1170
  if (child.type === "return") {
878
1171
  const first = children(child)[0];
879
1172
  const returned = first?.type === "argument_list" ? children(first)[0] : first;
@@ -881,7 +1174,7 @@ function emitMethodFacts(emitter, method) {
881
1174
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
882
1175
  }
883
1176
  }
884
- if (child.type === "call") {
1177
+ if (child.type === "call" || isBareCall(child, method)) {
885
1178
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
886
1179
  }
887
1180
  });
@@ -949,37 +1242,64 @@ function emitScopeWrites(emitter, method, body) {
949
1242
  }
950
1243
  }
951
1244
  function emitCandidates(emitter, key, group) {
1245
+ let unstated = false;
1246
+ let stated = 0;
952
1247
  for (const write of group.writes) {
953
1248
  if (write.value === null) {
954
1249
  add(emitter, "writesUnstated", key);
1250
+ unstated = true;
955
1251
  continue;
956
1252
  }
957
1253
  if (write.fromParameter) {
1254
+ unstated = true;
958
1255
  continue;
959
1256
  }
960
1257
  if (!describeWrite(emitter, write).narrowsName) {
961
1258
  add(emitter, "mayHold", key, valueKey(emitter, write.value));
1259
+ stated += 1;
962
1260
  }
963
1261
  }
1262
+ if (!unstated && stated > 0) {
1263
+ add(emitter, "writesAllStated", key);
1264
+ }
964
1265
  }
965
1266
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1267
+ var INITIALIZE_METHOD = "initialize";
1268
+ function receiverKeyOf(filePath, method, classKey) {
1269
+ return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
1270
+ }
1271
+ var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
1272
+ function mixedInConstants(body, callName) {
1273
+ return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
1274
+ }
1275
+ function emitMixinFacts(emitter, classKey, body, callName) {
1276
+ for (const mixin of mixedInConstants(body, callName)) {
1277
+ add(emitter, "extends", classKey, valueKey(emitter, mixin));
1278
+ }
1279
+ }
966
1280
  function emitClassFacts(emitter, cls) {
967
1281
  const classKey = nodeId(emitter.filePath, cls);
968
1282
  add(emitter, "objectValue", classKey);
1283
+ const body = field(cls, "body");
1284
+ if (body !== null) {
1285
+ emitMixinFacts(emitter, classKey, body, PREPEND_CALL);
1286
+ emitMixinFacts(emitter, classKey, body, INCLUDE_CALL);
1287
+ }
969
1288
  const superclass = field(cls, "superclass");
970
1289
  const base = superclass === null ? null : children(superclass)[0] ?? null;
971
1290
  if (base !== null) {
972
1291
  add(emitter, "extends", classKey, valueKey(emitter, base));
973
1292
  add(emitter, "extendsNamed", classKey, base.text);
974
1293
  }
975
- const body = field(cls, "body");
976
1294
  const collected = /* @__PURE__ */ new Map();
977
1295
  const within = {
978
1296
  ...emitter,
979
1297
  selfKey: classKey,
1298
+ insideMethod: false,
980
1299
  instanceWrites: collected
981
1300
  };
982
- for (const statement of body === null ? [] : children(body)) {
1301
+ const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
1302
+ for (const statement of statements) {
983
1303
  if (statement.type === "assignment") {
984
1304
  const left = field(statement, "left");
985
1305
  const right = field(statement, "right");
@@ -1000,24 +1320,47 @@ function emitClassFacts(emitter, cls) {
1000
1320
  continue;
1001
1321
  }
1002
1322
  if (!METHOD_TYPES.has(statement.type)) {
1323
+ emitExpressionFacts(within, statement);
1003
1324
  continue;
1004
1325
  }
1005
- const funcKey = emitMethodFacts(within, statement);
1326
+ const stored = /* @__PURE__ */ new Map();
1327
+ const receiverKey = receiverKeyOf(emitter.filePath, statement, classKey);
1328
+ if (receiverKey !== classKey) {
1329
+ add(emitter, "instanceOf", receiverKey, classKey);
1330
+ }
1331
+ const funcKey = emitMethodFacts(
1332
+ {
1333
+ ...within,
1334
+ selfKey: receiverKey,
1335
+ insideMethod: receiverKey !== classKey,
1336
+ instanceWrites: stored
1337
+ },
1338
+ statement
1339
+ );
1006
1340
  const name = field(statement, "name");
1007
1341
  if (name !== null) {
1008
1342
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1343
+ if (name.text === INITIALIZE_METHOD) {
1344
+ add(emitter, "initializes", classKey, funcKey);
1345
+ }
1346
+ }
1347
+ const methodBody = field(statement, "body");
1348
+ if (methodBody !== null) {
1349
+ emitInstanceStores(within, funcKey, methodBody, stored);
1009
1350
  }
1010
1351
  }
1011
1352
  emitInstanceWrites(within, classKey, collected);
1012
1353
  return classKey;
1013
1354
  }
1014
- function emitValueFacts(db, filePath, root) {
1355
+ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
1015
1356
  const emitter = {
1016
1357
  db,
1017
1358
  filePath,
1018
1359
  enclosing: null,
1019
1360
  selfKey: null,
1020
- instanceWrites: null
1361
+ insideMethod: false,
1362
+ instanceWrites: null,
1363
+ bodyBlocks
1021
1364
  };
1022
1365
  const declaresName = (child, key) => {
1023
1366
  const name = field(child, "name");
@@ -1057,7 +1400,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
1057
1400
  "module",
1058
1401
  "singleton_class"
1059
1402
  ]);
1060
- var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
1403
+ var BLOCK_TYPES4 = /* @__PURE__ */ new Set(["block", "do_block"]);
1061
1404
  var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1062
1405
  var NAME_TYPES = /* @__PURE__ */ new Set([
1063
1406
  "identifier",
@@ -1096,7 +1439,7 @@ function rubyLowering(options) {
1096
1439
  statement: statementOf,
1097
1440
  siteOf,
1098
1441
  functionOf,
1099
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
1442
+ writtenTo: (node, site) => context === null ? null : context.writtenTo(node, site),
1100
1443
  callable: (node) => {
1101
1444
  if (!isInlinableCall(node)) {
1102
1445
  return null;
@@ -1566,7 +1909,7 @@ function isTailExpression(node) {
1566
1909
  if (owner.type === "block" && owner.parent?.type === "lambda") {
1567
1910
  return true;
1568
1911
  }
1569
- if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES2.has(owner.type)) {
1912
+ if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES4.has(owner.type)) {
1570
1913
  return true;
1571
1914
  }
1572
1915
  if (owner.type === "when") {
@@ -1585,7 +1928,7 @@ function isRoot(node) {
1585
1928
  if (OWN_ROOT_TYPES.has(node.type)) {
1586
1929
  return true;
1587
1930
  }
1588
- return BLOCK_TYPES2.has(node.type) && node.parent?.type === "call";
1931
+ return BLOCK_TYPES4.has(node.type) && node.parent?.type === "call";
1589
1932
  }
1590
1933
  function isPathStatement(node, parent) {
1591
1934
  if (node.type === "elsif") {
@@ -1613,17 +1956,32 @@ function siteOf(node) {
1613
1956
  current = parent;
1614
1957
  }
1615
1958
  }
1959
+ var BARE_SPLAT_TYPES = /* @__PURE__ */ new Set(["splat_parameter", "hash_splat_parameter"]);
1616
1960
  function parametersOf2(parameters) {
1617
1961
  if (parameters === null) {
1618
1962
  return [];
1619
1963
  }
1620
- return named(parameters).flatMap((parameter) => {
1964
+ const declared = [];
1965
+ let position = 0;
1966
+ for (const parameter of named(parameters)) {
1967
+ if (BARE_SPLAT_TYPES.has(parameter.type) && field(parameter, "name") === null) {
1968
+ continue;
1969
+ }
1621
1970
  if (parameter.type === "identifier") {
1622
- return [{ name: parameter.text, default: null }];
1971
+ declared.push({ name: parameter.text, default: null, position });
1972
+ } else {
1973
+ const name = field(parameter, "name");
1974
+ if (name !== null) {
1975
+ declared.push({
1976
+ name: name.text,
1977
+ default: field(parameter, "value"),
1978
+ position
1979
+ });
1980
+ }
1623
1981
  }
1624
- const name = field(parameter, "name");
1625
- return name === null ? [] : [{ name: name.text, default: field(parameter, "value") }];
1626
- });
1982
+ position += 1;
1983
+ }
1984
+ return declared;
1627
1985
  }
1628
1986
  function functionOf(node) {
1629
1987
  if (node.type === "program") {
@@ -1639,7 +1997,7 @@ function functionOf(node) {
1639
1997
  body: bodyOf(block)
1640
1998
  };
1641
1999
  }
1642
- if (BLOCK_TYPES2.has(node.type)) {
2000
+ if (BLOCK_TYPES4.has(node.type)) {
1643
2001
  return {
1644
2002
  parameters: parametersOf2(field(node, "parameters")),
1645
2003
  body: bodyOf(node)
@@ -2031,25 +2389,56 @@ var rubyRows = [
2031
2389
 
2032
2390
  // src/values/evaluator.ts
2033
2391
  var evaluators = /* @__PURE__ */ new WeakMap();
2392
+ var contexts = /* @__PURE__ */ new WeakMap();
2034
2393
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2394
+ var filesByTree = /* @__PURE__ */ new WeakMap();
2035
2395
  var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2036
2396
  function bindEvaluator(db, nodes) {
2037
- evaluators.set(
2038
- db,
2039
- new Evaluator(
2040
- rubyLowering({ context: contextOver(db, nodes), rows: rubyRows })
2041
- )
2042
- );
2397
+ for (const entry of nodes.files) {
2398
+ filesByTree.set(entry.root.tree, entry.file);
2399
+ }
2400
+ const context = contextOver(db, nodes);
2401
+ contexts.set(db, context);
2402
+ evaluators.set(db, new Evaluator(rubyLowering({ context, rows: rubyRows })));
2403
+ }
2404
+ function writtenNodeOf(node, db, site) {
2405
+ const context = db === void 0 ? void 0 : contexts.get(db);
2406
+ return context === void 0 ? null : context.writtenTo(node, site);
2043
2407
  }
2044
- function evaluatedValue(node, db, bindings) {
2408
+ function evaluatedValue(node, db, bindings, site) {
2045
2409
  const evaluator = evaluatorFor(node, db);
2046
2410
  if (bindings === void 0) {
2047
- return force(evaluator.evaluate(node));
2411
+ return force(
2412
+ site === void 0 ? evaluator.evaluate(node) : evaluator.evaluate(node, { site })
2413
+ );
2048
2414
  }
2049
2415
  const supplied = new Map(
2050
2416
  [...bindings].map(([name, value]) => [name, text(value)])
2051
2417
  );
2052
- return force(evaluator.evaluate(node, { bindings: supplied }));
2418
+ return force(
2419
+ evaluator.evaluate(node, {
2420
+ bindings: supplied,
2421
+ ...site === void 0 ? {} : { site }
2422
+ })
2423
+ );
2424
+ }
2425
+ function constructionSitesOf(node, db) {
2426
+ const cls = enclosingClass(node);
2427
+ const file = cls === null ? void 0 : filesByTree.get(cls.tree);
2428
+ if (db === void 0 || cls === null || file === void 0) {
2429
+ return [];
2430
+ }
2431
+ return constructionSites(db, nodeId(file, cls));
2432
+ }
2433
+ function enclosingClass(node) {
2434
+ let current = node.parent;
2435
+ while (current !== null) {
2436
+ if (current.type === "class") {
2437
+ return current;
2438
+ }
2439
+ current = current.parent;
2440
+ }
2441
+ return null;
2053
2442
  }
2054
2443
  function stringValueOf(node, db, bindings) {
2055
2444
  return literalOf(evaluatedValue(node, db, bindings));
@@ -2101,15 +2490,13 @@ function contextOver(db, nodes) {
2101
2490
  }
2102
2491
  const fileOf = (node) => filesByRoot.get(node.tree.rootNode.id)?.file ?? null;
2103
2492
  return {
2104
- writtenTo: (node) => {
2493
+ writtenTo: (node, site) => {
2105
2494
  const file = fileOf(node);
2106
2495
  if (file === null) {
2107
2496
  return null;
2108
2497
  }
2109
- const answer = writtenValueOf(
2110
- db,
2111
- readKey(file, node, enclosingMethod(node))
2112
- );
2498
+ const key = readKey(file, node, enclosingMethod(node));
2499
+ const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
2113
2500
  return answer === null ? null : nodeOfKey(rootsByFile, answer);
2114
2501
  },
2115
2502
  callable: (call) => {
@@ -2127,125 +2514,141 @@ function contextOver(db, nodes) {
2127
2514
  };
2128
2515
  }
2129
2516
 
2130
- // src/defineMethod.ts
2131
- var DEFINE_METHOD_CALL = "define_method";
2132
- var LOOP_METHODS = /* @__PURE__ */ new Set(["each", "each_with_index", "map"]);
2133
- var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
2134
- var NOTHING = { names: /* @__PURE__ */ new Set(), unreadable: false };
2135
- var byTree = /* @__PURE__ */ new WeakMap();
2136
- function defineMethodNames(body) {
2137
- let perTree = byTree.get(body.tree);
2138
- if (perTree === void 0) {
2139
- perTree = /* @__PURE__ */ new Map();
2140
- byTree.set(body.tree, perTree);
2141
- }
2142
- const cached = perTree.get(body.id);
2143
- if (cached !== void 0) {
2144
- return cached;
2145
- }
2146
- const read = readNames(body);
2147
- perTree.set(body.id, read);
2148
- return read;
2149
- }
2150
- function readNames(body) {
2151
- const calls = bareCalls(body, DEFINE_METHOD_CALL);
2152
- if (calls.length === 0) {
2153
- return NOTHING;
2517
+ // src/values/literals.ts
2518
+ import { force as force2, literalOf as literalOf2 } from "@suss/values";
2519
+ function literalElementsIn(value) {
2520
+ if (value.kind !== "sequence") {
2521
+ return null;
2154
2522
  }
2155
- const names = /* @__PURE__ */ new Set();
2156
- let unreadable = false;
2157
- for (const call of calls) {
2158
- const defined = namesDefinedBy(call);
2159
- if (defined === null) {
2160
- unreadable = true;
2161
- continue;
2523
+ const elements = [];
2524
+ for (const item of value.items) {
2525
+ const literal = literalOf2(force2(item.value));
2526
+ if (literal === null) {
2527
+ return null;
2162
2528
  }
2163
- for (const name of defined) {
2164
- names.add(name);
2529
+ elements.push(literal);
2530
+ }
2531
+ return elements;
2532
+ }
2533
+ function literalElementsOf(node, db) {
2534
+ return literalElementsIn(evaluatedValue(node, db));
2535
+ }
2536
+ function namesOf(node, db) {
2537
+ const value = evaluatedValue(node, db);
2538
+ const single = literalOf2(value);
2539
+ return single === null ? literalElementsIn(value) : [single];
2540
+ }
2541
+
2542
+ // src/defineMethod.ts
2543
+ var NOTHING = {
2544
+ names: /* @__PURE__ */ new Set(),
2545
+ patterns: [],
2546
+ unreadable: false
2547
+ };
2548
+ function couldBeDefined(defined, name) {
2549
+ return defined.unreadable || defined.patterns.some((pattern) => pattern.test(name));
2550
+ }
2551
+ function definedNamesOf(dynamic, classKey) {
2552
+ return dynamic?.get(classKey) ?? NOTHING;
2553
+ }
2554
+ function readDynamicNames(db, rootsByFile) {
2555
+ const byClass = /* @__PURE__ */ new Map();
2556
+ for (const [classKey, nameKey2] of db.facts("definesMethodFrom")) {
2557
+ const key = String(classKey);
2558
+ byClass.set(
2559
+ key,
2560
+ foldReading(
2561
+ byClass.get(key) ?? NOTHING,
2562
+ readOne(db, rootsByFile, String(nameKey2))
2563
+ )
2564
+ );
2565
+ }
2566
+ for (const [classKey, reading] of byClass) {
2567
+ for (const name of reading.names) {
2568
+ db.add("declaresName", [classKey, name]);
2165
2569
  }
2166
2570
  }
2167
- return { names, unreadable };
2571
+ return byClass;
2168
2572
  }
2169
- function namesDefinedBy(call) {
2170
- const args = field(call, "arguments");
2171
- const first = args === null ? void 0 : bodyStatements(args)[0];
2172
- if (first === void 0) {
2173
- return null;
2573
+ function foldReading(soFar, reading) {
2574
+ if (reading === null) {
2575
+ return { ...soFar, unreadable: true };
2174
2576
  }
2577
+ return {
2578
+ names: /* @__PURE__ */ new Set([...soFar.names, ...reading.names]),
2579
+ patterns: [...soFar.patterns, ...reading.patterns],
2580
+ unreadable: soFar.unreadable
2581
+ };
2582
+ }
2583
+ function readOne(db, rootsByFile, nameKey2) {
2584
+ const node = nodeOfKey2(rootsByFile, nameKey2);
2175
2585
  const names = [];
2176
- for (const bindings of loopTurns(call)) {
2177
- const name = stringValueOf(
2178
- first,
2179
- void 0,
2180
- bindings.size === 0 ? void 0 : bindings
2181
- );
2182
- if (name === null) {
2586
+ const patterns = [];
2587
+ for (const bindings of loopTurns(db, rootsByFile, nameKey2)) {
2588
+ const value = node === null ? hole2(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2589
+ const name = literalOf3(value);
2590
+ if (name !== null) {
2591
+ names.push(name);
2592
+ continue;
2593
+ }
2594
+ const pattern = patternOf(value);
2595
+ if (pattern === null) {
2183
2596
  return null;
2184
2597
  }
2185
- names.push(name);
2598
+ patterns.push(pattern);
2186
2599
  }
2187
- return names;
2600
+ return { names, patterns };
2188
2601
  }
2189
- function loopTurns(call) {
2190
- let turns = [/* @__PURE__ */ new Map()];
2191
- for (const taken of blocksAround(call)) {
2192
- const perElement = elementBindings(taken);
2193
- if (perElement === null) {
2602
+ function patternOf(value) {
2603
+ const pieces = piecesOf(value);
2604
+ let source = "^";
2605
+ let readSomething = false;
2606
+ for (const piece of pieces) {
2607
+ if (piece.kind === "hole") {
2608
+ source += "[\\s\\S]*";
2194
2609
  continue;
2195
2610
  }
2196
- turns = turns.flatMap(
2197
- (base) => perElement.map((element) => new Map([...base, ...element]))
2198
- );
2199
- }
2200
- return turns;
2201
- }
2202
- function blocksAround(call) {
2203
- const blocks = [];
2204
- let current = call.parent;
2205
- while (current !== null && !OWN_BODY_TYPES.has(current.type)) {
2206
- const parent = current.parent;
2207
- if (BLOCK_TYPES3.has(current.type) && parent?.type === "call") {
2208
- blocks.unshift({ block: current, call: parent });
2611
+ const options = piece.options.filter((option) => option.length > 0);
2612
+ if (options.length === 0) {
2613
+ continue;
2209
2614
  }
2210
- current = parent;
2615
+ readSomething = true;
2616
+ source += `(?:${options.map(escapeForPattern).join("|")})`;
2211
2617
  }
2212
- return blocks;
2618
+ return readSomething ? new RegExp(`${source}$`) : null;
2213
2619
  }
2214
- function elementBindings(taken) {
2215
- const { block, call } = taken;
2216
- const receiver = field(call, "receiver");
2217
- const method = field(call, "method")?.text;
2218
- if (receiver === null || method === void 0 || !LOOP_METHODS.has(method)) {
2219
- return null;
2220
- }
2221
- const parameters = field(block, "parameters");
2222
- const [element, index] = parameters === null ? [] : bodyStatements(parameters).map((parameter) => parameter.text);
2223
- const elements = element === void 0 ? null : literalElementsOf(receiver);
2224
- if (element === void 0 || elements === null) {
2225
- return null;
2226
- }
2227
- return elements.map((value, position) => {
2228
- const binding = /* @__PURE__ */ new Map([[element, value]]);
2229
- if (index !== void 0) {
2230
- binding.set(index, String(position));
2231
- }
2232
- return binding;
2233
- });
2620
+ function escapeForPattern(text2) {
2621
+ return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2234
2622
  }
2235
- function literalElementsOf(node) {
2236
- const value = evaluatedValue(node);
2237
- if (value.kind !== "sequence") {
2238
- return null;
2623
+ function loopTurns(db, rootsByFile, nameKey2) {
2624
+ let turns = [/* @__PURE__ */ new Map()];
2625
+ for (const over of listsBehind(db, rootsByFile, nameKey2)) {
2626
+ turns = turns.flatMap(
2627
+ (base) => over.elements.map((value, position) => {
2628
+ const binding = new Map([...base, [over.element, value]]);
2629
+ if (over.index !== "") {
2630
+ binding.set(over.index, String(position));
2631
+ }
2632
+ return binding;
2633
+ })
2634
+ );
2239
2635
  }
2240
- const elements = [];
2241
- for (const item of value.items) {
2242
- const literal = literalOf2(force2(item.value));
2243
- if (literal === null) {
2244
- return null;
2636
+ return turns;
2637
+ }
2638
+ function listsBehind(db, rootsByFile, nameKey2) {
2639
+ const found = [];
2640
+ for (const [, element, index, overKey] of db.lookup(
2641
+ "nameTurnsOn",
2642
+ 0,
2643
+ nameKey2
2644
+ )) {
2645
+ const node = nodeOfKey2(rootsByFile, String(overKey));
2646
+ const elements = node === null ? null : literalElementsOf(node, db);
2647
+ if (elements !== null) {
2648
+ found.push({ element: String(element), index: String(index), elements });
2245
2649
  }
2246
- elements.push(literal);
2247
2650
  }
2248
- return elements;
2651
+ return found;
2249
2652
  }
2250
2653
 
2251
2654
  // src/scope.ts
@@ -2374,8 +2777,6 @@ function graphqlTypeNameFromQualified(qualifiedName, convention) {
2374
2777
  }
2375
2778
 
2376
2779
  // src/ancestry.ts
2377
- var INCLUDE_CALL = "include";
2378
- var PREPEND_CALL = "prepend";
2379
2780
  async function reachDefinition(qualifiedName, lookup) {
2380
2781
  const filePath = resolveConstantFile(
2381
2782
  lookup.root,
@@ -2502,7 +2903,7 @@ function superclassCandidatesOf(blocks) {
2502
2903
  }
2503
2904
  return [];
2504
2905
  }
2505
- function methodInAncestry(ancestry, name) {
2906
+ function methodInAncestry(ancestry, name, read = {}) {
2506
2907
  for (const entry of ancestry) {
2507
2908
  if (entry.type === "root") {
2508
2909
  return { type: "none" };
@@ -2514,7 +2915,7 @@ function methodInAncestry(ancestry, name) {
2514
2915
  cause: "unreadAncestor"
2515
2916
  };
2516
2917
  }
2517
- const found = definitionIn(entry.blocks, name);
2918
+ const found = definitionIn(entry.blocks, name, read);
2518
2919
  if (found.method !== null && found.block !== null) {
2519
2920
  return { type: "found", method: found.method, block: found.block };
2520
2921
  }
@@ -2528,7 +2929,7 @@ function methodInAncestry(ancestry, name) {
2528
2929
  }
2529
2930
  return { type: "none" };
2530
2931
  }
2531
- function definitionIn(blocks, name) {
2932
+ function definitionIn(blocks, name, read) {
2532
2933
  let method = null;
2533
2934
  let block = null;
2534
2935
  let definedDynamically = false;
@@ -2538,14 +2939,17 @@ function definitionIn(blocks, name) {
2538
2939
  if (body === null) {
2539
2940
  continue;
2540
2941
  }
2541
- const found = instanceMethodsByName(body).get(name);
2942
+ const found = instanceMethodsByName(body, read.bodyBlocks).get(name);
2542
2943
  if (found !== void 0) {
2543
2944
  method = found;
2544
2945
  block = candidate;
2545
2946
  }
2546
- const defined = defineMethodNames(body);
2947
+ const defined = definedNamesOf(
2948
+ read.dynamicNames,
2949
+ nodeId(candidate.file, candidate.info.node)
2950
+ );
2547
2951
  definedDynamically ||= defined.names.has(name);
2548
- unreadableDefine ||= defined.unreadable;
2952
+ unreadableDefine ||= couldBeDefined(defined, name);
2549
2953
  }
2550
2954
  return { method, block, definedDynamically, unreadableDefine };
2551
2955
  }
@@ -2577,108 +2981,12 @@ import {
2577
2981
  import { absentReading as absentReading2, unreadableReading as unreadableReading2 } from "@suss/extractor";
2578
2982
 
2579
2983
  // src/clientCalls.ts
2580
- import { namesNothing, restBinding } from "@suss/behavioral-ir";
2984
+ import { hasNameHole, namesNothing, restBinding } from "@suss/behavioral-ir";
2581
2985
  import { pathOf } from "@suss/values";
2582
2986
 
2583
2987
  // src/paths/effects.ts
2584
2988
  import { enumerateOrDegrade, sharedGatingConditions } from "@suss/extractor";
2585
-
2586
- // src/paths/bareCalls.ts
2587
- var PARAMETER_LIST_TYPES = [
2588
- "method_parameters",
2589
- "block_parameters",
2590
- "lambda_parameters",
2591
- "parameters"
2592
- ];
2593
- function isFieldOf(parent, node, name) {
2594
- return field(parent, name)?.id === node.id;
2595
- }
2596
- function namedAt(parent, node) {
2597
- return isFieldOf(parent, node, "name");
2598
- }
2599
- function always() {
2600
- return true;
2601
- }
2602
- var SPELLS_A_NAME = {
2603
- call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
2604
- method: namedAt,
2605
- singleton_method: namedAt,
2606
- assignment: (parent, node) => isFieldOf(parent, node, "left"),
2607
- operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
2608
- for: (parent, node) => isFieldOf(parent, node, "pattern"),
2609
- optional_parameter: namedAt,
2610
- splat_parameter: namedAt,
2611
- hash_splat_parameter: namedAt,
2612
- keyword_parameter: namedAt,
2613
- block_parameter: namedAt,
2614
- destructured_parameter: always,
2615
- left_assignment_list: always,
2616
- rest_assignment: always,
2617
- destructured_left_assignment: always,
2618
- exception_variable: always,
2619
- undef: always,
2620
- alias: always,
2621
- ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
2622
- };
2623
- function addNamesUnder(node, names) {
2624
- if (node === null) {
2625
- return;
2626
- }
2627
- if (node.type === "identifier") {
2628
- names.add(node.text);
2629
- return;
2630
- }
2631
- for (const child of bodyStatements(node)) {
2632
- addNamesUnder(child, names);
2633
- }
2634
- }
2635
- function parameterNames2(node, names) {
2636
- for (const parameter of bodyStatements(node)) {
2637
- if (parameter.type === "identifier") {
2638
- names.add(parameter.text);
2639
- continue;
2640
- }
2641
- const declared = field(parameter, "name");
2642
- if (declared !== null && declared.type === "identifier") {
2643
- names.add(declared.text);
2644
- continue;
2645
- }
2646
- addNamesUnder(parameter, names);
2647
- }
2648
- }
2649
- function assignedNames(node, names) {
2650
- addNamesUnder(field(node, "left"), names);
2651
- }
2652
- var BINDS_A_NAME = {
2653
- assignment: assignedNames,
2654
- operator_assignment: assignedNames,
2655
- for: (node, names) => addNamesUnder(field(node, "pattern"), names),
2656
- exception_variable: (node, names) => addNamesUnder(node, names),
2657
- ...Object.fromEntries(
2658
- PARAMETER_LIST_TYPES.map((type) => [type, parameterNames2])
2659
- )
2660
- };
2661
- function localNamesIn(definitionNode) {
2662
- const names = /* @__PURE__ */ new Set();
2663
- const visit = (node) => {
2664
- BINDS_A_NAME[node.type]?.(node, names);
2665
- for (const child of bodyStatements(node)) {
2666
- visit(child);
2667
- }
2668
- };
2669
- visit(definitionNode);
2670
- return names;
2671
- }
2672
- function isBareMethodCall(node, locals) {
2673
- if (node.type !== "identifier" || locals.has(node.text)) {
2674
- return false;
2675
- }
2676
- const parent = node.parent;
2677
- if (parent === null) {
2678
- return false;
2679
- }
2680
- return SPELLS_A_NAME[parent.type]?.(parent, node) !== true;
2681
- }
2989
+ import { constantOf, literalOf as literalOf4 } from "@suss/values";
2682
2990
 
2683
2991
  // src/paths/lowering.ts
2684
2992
  var RAISE_NAMES = /* @__PURE__ */ new Set(["raise", "fail"]);
@@ -2959,30 +3267,28 @@ function lowerRubyBody(body, terminals, responseCalls = []) {
2959
3267
 
2960
3268
  // src/paths/effects.ts
2961
3269
  var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
2962
- var BLOCK_TYPES4 = /* @__PURE__ */ new Set([
3270
+ var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
2963
3271
  "body_statement",
2964
3272
  "then",
2965
- "else",
2966
- "do_block",
2967
- "block"
2968
- ]);
2969
- var LITERAL_ARGS = {
2970
- string: (node) => ({
2971
- kind: "string",
2972
- value: node.text.replace(/^["']|["']$/g, "")
2973
- }),
2974
- integer: (node) => {
2975
- const value = Number.parseInt(node.text, 10);
2976
- return Number.isNaN(value) ? null : { kind: "number", value };
2977
- },
2978
- float: (node) => {
2979
- const value = Number.parseFloat(node.text);
2980
- return Number.isNaN(value) ? null : { kind: "number", value };
2981
- },
2982
- true: () => ({ kind: "boolean", value: true }),
2983
- false: () => ({ kind: "boolean", value: false }),
2984
- simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
2985
- };
3273
+ "else",
3274
+ "do_block",
3275
+ "block"
3276
+ ]);
3277
+ function literalArgOf(node, facts) {
3278
+ const value = evaluatedValue(node, facts);
3279
+ const text2 = literalOf4(value);
3280
+ if (text2 !== null) {
3281
+ return { kind: "string", value: text2 };
3282
+ }
3283
+ const constant2 = constantOf(value);
3284
+ if (typeof constant2 === "number") {
3285
+ return { kind: "number", value: constant2 };
3286
+ }
3287
+ if (typeof constant2 === "boolean") {
3288
+ return { kind: "boolean", value: constant2 };
3289
+ }
3290
+ return null;
3291
+ }
2986
3292
  function children2(node) {
2987
3293
  return node.namedChildren.filter((child) => child !== null);
2988
3294
  }
@@ -3066,36 +3372,40 @@ function calleeText(call) {
3066
3372
  }
3067
3373
  return receiver === null ? method : `${receiver.text}.${method}`;
3068
3374
  }
3069
- function argOf(node) {
3070
- const literal = LITERAL_ARGS[node.type]?.(node);
3071
- if (literal !== null && literal !== void 0) {
3375
+ function argOf(node, facts) {
3376
+ const literal = literalArgOf(node, facts);
3377
+ if (literal !== null) {
3072
3378
  return literal;
3073
3379
  }
3074
3380
  if (node.type === "call" && !isArglessReceiverCall(node)) {
3075
- return { kind: "call", callee: calleeText(node), args: argsOf(node) };
3381
+ return {
3382
+ kind: "call",
3383
+ callee: calleeText(node),
3384
+ args: argsOf(node, facts)
3385
+ };
3076
3386
  }
3077
3387
  return { kind: "identifier", name: node.text };
3078
3388
  }
3079
- function argsOf(call) {
3389
+ function argsOf(call, facts) {
3080
3390
  const args = field(call, "arguments");
3081
3391
  if (args === null) {
3082
3392
  return [];
3083
3393
  }
3084
3394
  return children2(args).map(
3085
- (child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
3395
+ (child) => child.type === "pair" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3086
3396
  );
3087
3397
  }
3088
3398
  function enclosingStatement(call, body) {
3089
3399
  let current = call;
3090
3400
  while (current.parent !== null) {
3091
- if (BLOCK_TYPES4.has(current.parent.type) || current.parent.id === body.id) {
3401
+ if (BLOCK_TYPES5.has(current.parent.type) || current.parent.id === body.id) {
3092
3402
  return current;
3093
3403
  }
3094
3404
  current = current.parent;
3095
3405
  }
3096
3406
  return current;
3097
3407
  }
3098
- function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS) {
3408
+ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
3099
3409
  const body = field(definitionNode, "body");
3100
3410
  if (body === null) {
3101
3411
  return [];
@@ -3135,7 +3445,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3135
3445
  return {
3136
3446
  type: "invocation",
3137
3447
  callee: calleeText(call),
3138
- args: argsOf(call),
3448
+ args: argsOf(call, facts),
3139
3449
  async: false,
3140
3450
  ...conditions.length > 0 ? { preconditions: conditions } : {}
3141
3451
  };
@@ -3150,7 +3460,7 @@ import {
3150
3460
  unreadableReading,
3151
3461
  writtenReading
3152
3462
  } from "@suss/extractor";
3153
- import { constantOf, literalOf as literalOf4 } from "@suss/values";
3463
+ import { constantOf as constantOf2, literalOf as literalOf6 } from "@suss/values";
3154
3464
 
3155
3465
  // src/paths/predicates.ts
3156
3466
  var OPERATORS = {
@@ -3219,7 +3529,7 @@ function binaryOf(node) {
3219
3529
  return opaqueOf(node);
3220
3530
  }
3221
3531
  function valueRefOf(node) {
3222
- const literal = literalOf3(node);
3532
+ const literal = literalOf5(node);
3223
3533
  if (literal !== null) {
3224
3534
  return literal;
3225
3535
  }
@@ -3232,7 +3542,7 @@ function valueRefOf(node) {
3232
3542
  }
3233
3543
  return { type: "unresolved", sourceText: node.text };
3234
3544
  }
3235
- function literalOf3(node) {
3545
+ function literalOf5(node) {
3236
3546
  if (node.type === "integer") {
3237
3547
  const value = Number.parseInt(node.text, 10);
3238
3548
  return Number.isNaN(value) ? null : { type: "literal", value };
@@ -3275,13 +3585,13 @@ function statusArgumentOf(call, declaration) {
3275
3585
  }
3276
3586
  return args.positional[declaration.statusArgument] ?? null;
3277
3587
  }
3278
- function statusNumberOf(node, names) {
3279
- const value = evaluatedValue(node);
3280
- const constant2 = constantOf(value);
3588
+ function statusNumberOf(node, names, facts) {
3589
+ const value = evaluatedValue(node, facts);
3590
+ const constant2 = constantOf2(value);
3281
3591
  if (typeof constant2 === "number") {
3282
3592
  return constant2;
3283
3593
  }
3284
- const name = literalOf4(value);
3594
+ const name = literalOf6(value);
3285
3595
  return name === null ? null : names[name] ?? null;
3286
3596
  }
3287
3597
  function declarationsByName(declarations) {
@@ -3325,12 +3635,12 @@ function collectReturns(node, found) {
3325
3635
  }
3326
3636
  return found;
3327
3637
  }
3328
- function readingOfCall(call, declaration, names) {
3638
+ function readingOfCall(call, declaration, names, facts) {
3329
3639
  const argument = statusArgumentOf(call, declaration);
3330
3640
  if (argument === null) {
3331
3641
  return declaration.defaultStatusCode === void 0 ? absentReading : writtenReading(declaration.defaultStatusCode, rangeOf(call));
3332
3642
  }
3333
- const status = statusNumberOf(argument, names);
3643
+ const status = statusNumberOf(argument, names, facts);
3334
3644
  if (status === null) {
3335
3645
  return unreadableReading(
3336
3646
  "This response writes a status that does not settle on a number here, so this outcome claims none",
@@ -3474,7 +3784,7 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
3474
3784
  const outcomes = [];
3475
3785
  for (const terminal of terminals) {
3476
3786
  const declaration = declarationOf(terminal, byName, locals);
3477
- const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames);
3787
+ const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
3478
3788
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
3479
3789
  outcomes.push({
3480
3790
  conditions: path7.map(conditionOf),
@@ -3508,11 +3818,9 @@ function clientCallUnits(root, pack, pattern, options) {
3508
3818
  continue;
3509
3819
  }
3510
3820
  for (const call of callsUnder(method)) {
3511
- const request = requestCall(call, method, root, pattern, options);
3512
- if (request === null) {
3513
- continue;
3821
+ for (const request of requestCalls(call, pattern, options)) {
3822
+ units.push(clientUnit(method, name, request, pattern, pack, options));
3514
3823
  }
3515
- units.push(clientUnit(method, name, request, pattern, pack, options));
3516
3824
  }
3517
3825
  }
3518
3826
  return units;
@@ -3541,36 +3849,60 @@ function callsUnder(node, found = []) {
3541
3849
  }
3542
3850
  return found;
3543
3851
  }
3544
- function requestCall(call, method, root, pattern, options) {
3852
+ function requestCalls(call, pattern, options) {
3545
3853
  const receiver = field(call, "receiver");
3546
3854
  const called = field(call, "method")?.text;
3547
- if (receiver === null || called === void 0) {
3548
- return null;
3855
+ if (receiver === null || called === void 0 || !isLibraryReceiver(receiver, pattern, options)) {
3856
+ return [];
3549
3857
  }
3550
- const prefix = receiverPrefix(receiver, method, root, pattern, options);
3551
- if (prefix === null) {
3552
- return null;
3858
+ const read = (site) => requestCall(
3859
+ call,
3860
+ called,
3861
+ receiverPrefix(receiver, pattern, options, site),
3862
+ pattern,
3863
+ options,
3864
+ site
3865
+ );
3866
+ const plain = read();
3867
+ if (plain !== null && !hasNameHole(plain.path)) {
3868
+ return [plain];
3869
+ }
3870
+ const byBoundary = /* @__PURE__ */ new Map();
3871
+ for (const site of constructionSitesOf(call, options.facts)) {
3872
+ const stated = read(site);
3873
+ if (stated !== null) {
3874
+ byBoundary.set(`${stated.method} ${stated.path}`, stated);
3875
+ }
3553
3876
  }
3877
+ if (byBoundary.size > 0) {
3878
+ return [...byBoundary.values()];
3879
+ }
3880
+ return plain === null ? [] : [plain];
3881
+ }
3882
+ function requestCall(call, called, prefix, pattern, options, site) {
3554
3883
  const args = readCallArgs(field(call, "arguments"));
3555
3884
  const verb = pattern.verbMethodNames[called];
3556
3885
  if (verb !== void 0) {
3557
- const path8 = urlIn(args, pattern, options);
3886
+ const path8 = urlIn(args, pattern, options, site);
3558
3887
  return path8 === null ? null : { method: verb, path: prefix + path8 };
3559
3888
  }
3560
3889
  const sent = pattern.requestObject;
3561
3890
  if (sent === void 0 || sent.attribute !== called) {
3562
3891
  return null;
3563
3892
  }
3564
- const built = requestBuilt(args.positional[0], method, sent);
3893
+ const built = requestBuilt(args.positional[0], sent, options, site);
3565
3894
  if (built === null) {
3566
3895
  return null;
3567
3896
  }
3568
- const path7 = pathAt(built.url, options);
3897
+ const path7 = pathAt(built.url, options, site);
3569
3898
  return path7 === null ? null : { method: built.method, path: prefix + path7 };
3570
3899
  }
3571
- function requestBuilt(argument, method, sent) {
3572
- const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
3573
- if (written === null || written.type !== "call") {
3900
+ function requestBuilt(argument, sent, options, site) {
3901
+ if (argument === void 0) {
3902
+ return null;
3903
+ }
3904
+ const written = writtenNodeOf(argument, options.facts, site) ?? argument;
3905
+ if (written.type !== "call") {
3574
3906
  return null;
3575
3907
  }
3576
3908
  const requestClass = field(written, "receiver")?.text;
@@ -3581,28 +3913,18 @@ function requestBuilt(argument, method, sent) {
3581
3913
  const args = readCallArgs(field(written, "arguments"));
3582
3914
  return { method: verb, url: args.positional[sent.urlPosition] };
3583
3915
  }
3584
- function assignedCall(node, method) {
3585
- if (node.type !== "identifier") {
3586
- return null;
3587
- }
3588
- const value = assignedValue(node.text, method);
3589
- return value !== null && value.type === "call" ? value : null;
3916
+ function isLibraryReceiver(receiver, pattern, options) {
3917
+ return namesConstant(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
3590
3918
  }
3591
- function receiverPrefix(receiver, method, root, pattern, options) {
3592
- if (namesConstant(receiver, pattern.constantName)) {
3593
- return "";
3594
- }
3595
- if (receiver.type !== "identifier") {
3596
- return null;
3597
- }
3598
- const built = builtBy(receiver.text, method, root, pattern);
3919
+ function receiverPrefix(receiver, pattern, options, site) {
3920
+ const built = builderCallBehind(receiver, pattern, options.facts);
3599
3921
  if (built === null) {
3600
- return null;
3922
+ return "";
3601
3923
  }
3602
3924
  const args = readCallArgs(field(built, "arguments"));
3603
3925
  const keyword = pattern.builderUrlKeyword;
3604
3926
  const base = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[0];
3605
- const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts));
3927
+ const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts, void 0, site));
3606
3928
  return path7 === void 0 || path7 === null ? "" : trimmed(path7);
3607
3929
  }
3608
3930
  function trimmed(path7) {
@@ -3614,61 +3936,28 @@ function namesConstant(receiver, constantName2) {
3614
3936
  }
3615
3937
  return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
3616
3938
  }
3617
- function builtBy(name, method, root, pattern) {
3939
+ function builderCallBehind(receiver, pattern, facts) {
3618
3940
  const builders = pattern.receiverBuilders ?? [];
3619
3941
  if (builders.length === 0) {
3620
3942
  return null;
3621
3943
  }
3622
- const value = assignedValue(name, method) ?? valueMethodNamedReturns(name, root);
3623
- if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
3624
- return null;
3625
- }
3626
- const receiver = field(value, "receiver");
3627
- return receiver !== null && namesConstant(receiver, pattern.constantName) ? value : null;
3628
- }
3629
- function valueMethodNamedReturns(name, root) {
3630
- const defined = methodDefinitions(root).find(
3631
- (candidate) => field(candidate, "name")?.text === name
3632
- );
3633
- if (defined === void 0) {
3634
- return null;
3635
- }
3636
- const body = field(defined, "body");
3637
- const statements = body === null ? [] : body.namedChildren;
3638
- const last = statements[statements.length - 1];
3639
- if (last === void 0) {
3640
- return null;
3641
- }
3642
- if (last.type === "operator_assignment" || last.type === "assignment") {
3643
- return field(last, "right");
3644
- }
3645
- return last.type === "return" ? last.namedChildren[0] ?? null : last;
3646
- }
3647
- function assignedValue(name, method) {
3648
- const body = field(method, "body");
3649
- if (body === null) {
3944
+ const written = writtenNodeOf(receiver, facts);
3945
+ if (written === null || written.type !== "call" || !builders.includes(field(written, "method")?.text ?? "")) {
3650
3946
  return null;
3651
3947
  }
3652
- for (const statement of runStatements(body)) {
3653
- if (statement.type !== "assignment") {
3654
- continue;
3655
- }
3656
- if (field(statement, "left")?.text === name) {
3657
- return field(statement, "right");
3658
- }
3659
- }
3660
- return null;
3948
+ const constant2 = field(written, "receiver");
3949
+ return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
3661
3950
  }
3662
- function urlIn(args, pattern, options) {
3951
+ function urlIn(args, pattern, options, site) {
3663
3952
  const keyword = pattern.url.keyword;
3664
3953
  const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
3665
- return pathAt(written, options);
3954
+ return pathAt(written, options, site);
3666
3955
  }
3667
- function pathAt(written, options) {
3956
+ function pathAt(written, options, site) {
3668
3957
  if (written === void 0) {
3669
3958
  return null;
3670
3959
  }
3671
- const path7 = pathOf(evaluatedValue(written, options.facts));
3960
+ const path7 = pathOf(evaluatedValue(written, options.facts, void 0, site));
3672
3961
  return path7 === void 0 || namesNothing(path7) ? null : path7;
3673
3962
  }
3674
3963
  function clientUnit(method, name, request, pattern, pack, options) {
@@ -3691,15 +3980,15 @@ function clientUnit(method, name, request, pattern, pack, options) {
3691
3980
  recognition: pack.name
3692
3981
  }),
3693
3982
  parameters: [],
3694
- branches: callerBranches(method, range),
3983
+ branches: callerBranches(method, range, options.facts),
3695
3984
  ...responseAccessors(pattern),
3696
3985
  bodyContent: "statements",
3697
3986
  dependencyCalls: [],
3698
3987
  declaredContract: null
3699
3988
  };
3700
3989
  }
3701
- function callerBranches(method, range) {
3702
- const effects = invocationEffects(method);
3990
+ function callerBranches(method, range, facts) {
3991
+ const effects = invocationEffects(method, void 0, void 0, facts);
3703
3992
  return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
3704
3993
  }
3705
3994
  function responseAccessors(pattern) {
@@ -3837,7 +4126,7 @@ function isDefaultedAt(node) {
3837
4126
  }
3838
4127
 
3839
4128
  // src/filters.ts
3840
- function controllerFilters(pattern, ancestry) {
4129
+ function controllerFilters(pattern, ancestry, read = {}) {
3841
4130
  const forms = pattern.filters ?? [];
3842
4131
  if (forms.length === 0) {
3843
4132
  return [];
@@ -3857,7 +4146,7 @@ function controllerFilters(pattern, ancestry) {
3857
4146
  }
3858
4147
  const form = byName.get(called);
3859
4148
  if (form !== void 0) {
3860
- for (const declaration of declarationsOf(statement, form)) {
4149
+ for (const declaration of declarationsOf(statement, form, read.facts)) {
3861
4150
  declared = [
3862
4151
  ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
3863
4152
  declaration
@@ -3867,12 +4156,12 @@ function controllerFilters(pattern, ancestry) {
3867
4156
  }
3868
4157
  const skipped = skipNames.get(called);
3869
4158
  if (skipped !== void 0) {
3870
- declared = applySkips(declared, skipsOf(statement, skipped));
4159
+ declared = applySkips(declared, skipsOf(statement, skipped, read.facts));
3871
4160
  }
3872
4161
  }
3873
4162
  const resolved = [];
3874
4163
  for (const declaration of declared) {
3875
- const found = methodInAncestry(ancestry, declaration.methodName);
4164
+ const found = methodInAncestry(ancestry, declaration.methodName, read);
3876
4165
  if (found.type !== "found") {
3877
4166
  continue;
3878
4167
  }
@@ -3904,14 +4193,14 @@ function filterReference(filter, displayPath) {
3904
4193
  ...filter.filter.onThrow === true ? { onThrow: true } : {}
3905
4194
  };
3906
4195
  }
3907
- function filterUnit(filter, pattern, displayPath, body) {
4196
+ function filterUnit(filter, pattern, displayPath, body, facts) {
3908
4197
  const range = rangeOf(filter.method);
3909
4198
  const branches = responseBranches(
3910
4199
  filter.method,
3911
4200
  pattern,
3912
4201
  body.effects ?? [],
3913
4202
  body.extraEffects,
3914
- { fallthrough: "handOn" }
4203
+ { fallthrough: "handOn", facts }
3915
4204
  );
3916
4205
  return {
3917
4206
  identity: {
@@ -3959,30 +4248,30 @@ function calledName(statement) {
3959
4248
  }
3960
4249
  return field(statement, "method")?.text ?? null;
3961
4250
  }
3962
- function declarationsOf(statement, filter) {
4251
+ function declarationsOf(statement, filter, facts) {
3963
4252
  const args = readCallArgs(field(statement, "arguments"));
3964
4253
  const keywords = filter.actionKeywords;
3965
- const only = keywords === void 0 ? null : namesUnder(args.keyword[keywords.include]);
3966
- const except = keywords === void 0 ? null : namesUnder(args.keyword[keywords.exclude]);
3967
- return methodNamesOf(args, filter).map((methodName) => ({
4254
+ const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
4255
+ const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
4256
+ return methodNamesOf(args, filter, facts).map((methodName) => ({
3968
4257
  filter,
3969
4258
  methodName,
3970
4259
  only,
3971
4260
  except: except ?? /* @__PURE__ */ new Set()
3972
4261
  }));
3973
4262
  }
3974
- function methodNamesOf(args, filter) {
4263
+ function methodNamesOf(args, filter, facts) {
3975
4264
  if (filter.methodFrom === "withKeyword") {
3976
4265
  const named2 = args.keyword.with;
3977
- const value = named2 === void 0 ? null : symbolValue(named2);
4266
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
3978
4267
  return value === null ? [] : [value];
3979
4268
  }
3980
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
4269
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
3981
4270
  }
3982
- function skipsOf(statement, filterName) {
4271
+ function skipsOf(statement, filterName, facts) {
3983
4272
  const args = readCallArgs(field(statement, "arguments"));
3984
- const actions = namesUnder(args.keyword.only);
3985
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4273
+ const actions = actionsUnder(args.keyword.only, facts);
4274
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
3986
4275
  }
3987
4276
  function sameFilter(one, other) {
3988
4277
  return one.filter.name === other.filter.name && one.methodName === other.methodName;
@@ -4006,26 +4295,12 @@ function applySkips(declared, skips) {
4006
4295
  }
4007
4296
  return left;
4008
4297
  }
4009
- function namesUnder(node) {
4298
+ function actionsUnder(node, facts) {
4010
4299
  if (node === void 0) {
4011
4300
  return null;
4012
4301
  }
4013
- const single = symbolValue(node);
4014
- if (single !== null) {
4015
- return /* @__PURE__ */ new Set([single]);
4016
- }
4017
- if (node.type !== "array" && node.type !== "symbol_array") {
4018
- return null;
4019
- }
4020
- const names = /* @__PURE__ */ new Set();
4021
- for (const child of bodyStatements(node)) {
4022
- const bare = child.type === "bare_symbol" ? child.text : null;
4023
- const name = symbolValue(child) ?? bare;
4024
- if (name !== null) {
4025
- names.add(name);
4026
- }
4027
- }
4028
- return names;
4302
+ const found = namesOf(node, facts);
4303
+ return found === null ? null : new Set(found);
4029
4304
  }
4030
4305
 
4031
4306
  // src/storage.ts
@@ -4076,28 +4351,95 @@ function modelPattern(constant2, file, options) {
4076
4351
  (candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
4077
4352
  );
4078
4353
  }
4079
- function selectorOf(node) {
4080
- const picked = [];
4081
- const walk = (current) => {
4082
- const args = field(current, "arguments");
4083
- for (const argument of args === null ? [] : children3(args)) {
4084
- if (argument.type !== "pair") {
4085
- continue;
4086
- }
4087
- const key = field(argument, "key");
4088
- if (key !== null) {
4089
- picked.push(key.text.replace(/^:/, "").replace(/:$/, ""));
4090
- }
4354
+ function chainLinks(call) {
4355
+ const links = [];
4356
+ let current = call;
4357
+ while (current !== null && current.type === "call") {
4358
+ links.push(current);
4359
+ current = receiverOf(current);
4360
+ }
4361
+ return links;
4362
+ }
4363
+ function kindOfCall(pattern, method) {
4364
+ if (pattern.writes.includes(method)) {
4365
+ return "write";
4366
+ }
4367
+ if (pattern.reads.includes(method)) {
4368
+ return "read";
4369
+ }
4370
+ return void 0;
4371
+ }
4372
+ function someLibraryDefines(options, method) {
4373
+ return options.patterns.some(
4374
+ (pattern) => kindOfCall(pattern, method) !== void 0
4375
+ );
4376
+ }
4377
+ function argumentsOf2(call) {
4378
+ const args = field(call, "arguments");
4379
+ return args === null ? [] : children3(args);
4380
+ }
4381
+ function recordFieldsOf(argument, facts) {
4382
+ const value = evaluatedValue(argument, facts);
4383
+ return value.kind === "record" ? [...value.fields.keys()] : [];
4384
+ }
4385
+ function keywordKeys(call, facts) {
4386
+ const keys = [];
4387
+ for (const argument of argumentsOf2(call)) {
4388
+ if (argument.type !== "pair") {
4389
+ keys.push(...recordFieldsOf(argument, facts));
4390
+ continue;
4091
4391
  }
4092
- const receiver = receiverOf(current);
4093
- if (receiver !== null) {
4094
- walk(receiver);
4392
+ const key = field(argument, "key");
4393
+ const name = key === null ? null : keyNameOf(key);
4394
+ if (name !== null) {
4395
+ keys.push(name);
4095
4396
  }
4096
- };
4097
- walk(node);
4397
+ }
4398
+ return keys;
4399
+ }
4400
+ function keyNameOf(key) {
4401
+ return hashKeySymbolName(key) ?? symbolValue(key) ?? stringLiteralValue(key);
4402
+ }
4403
+ function hasPositionalArgument(call, facts) {
4404
+ return argumentsOf2(call).some(
4405
+ (argument) => argument.type !== "pair" && recordFieldsOf(argument, facts).length === 0
4406
+ );
4407
+ }
4408
+ function columnsAskedFor(call, pattern, facts) {
4409
+ if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
4410
+ return [];
4411
+ }
4412
+ return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
4413
+ }
4414
+ function primaryKeySelector(call, pattern, facts) {
4415
+ const byPrimaryKey = pattern.byPrimaryKey;
4416
+ if (byPrimaryKey === void 0) {
4417
+ return [];
4418
+ }
4419
+ const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call, facts);
4420
+ return picks ? [byPrimaryKey.column] : [];
4421
+ }
4422
+ function selectorOf(call, pattern, facts) {
4423
+ const picked = [];
4424
+ for (const link of chainLinks(call)) {
4425
+ picked.push(...primaryKeySelector(link, pattern, facts));
4426
+ if (pattern.reads.includes(methodOf(link))) {
4427
+ picked.push(...keywordKeys(link, facts));
4428
+ }
4429
+ }
4098
4430
  return [...new Set(picked)];
4099
4431
  }
4100
- function storageEffect(call, container, pattern, kind, selector) {
4432
+ function fieldsOf(call, pattern, kind, facts) {
4433
+ if (kind === "write") {
4434
+ return [...new Set(keywordKeys(call, facts))];
4435
+ }
4436
+ return [
4437
+ ...new Set(
4438
+ chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern, facts))
4439
+ )
4440
+ ];
4441
+ }
4442
+ function storageEffect(call, container, pattern, kind, selector, fields) {
4101
4443
  const operation = methodOf(call);
4102
4444
  return {
4103
4445
  type: "interaction",
@@ -4111,35 +4453,12 @@ function storageEffect(call, container, pattern, kind, selector) {
4111
4453
  interaction: {
4112
4454
  class: "storage-access",
4113
4455
  kind,
4114
- fields: [],
4456
+ fields,
4115
4457
  operation,
4116
4458
  ...selector.length > 0 ? { selector } : {}
4117
4459
  }
4118
4460
  };
4119
4461
  }
4120
- function modelCallEffects(call, file, options) {
4121
- const constant2 = rootConstant(call);
4122
- if (constant2 === null) {
4123
- return [];
4124
- }
4125
- const pattern = modelPattern(constant2, file, options);
4126
- if (pattern === void 0) {
4127
- return [];
4128
- }
4129
- const kind = pattern.writes.includes(methodOf(call)) ? "write" : "read";
4130
- return [
4131
- storageEffect(
4132
- call,
4133
- constantName(constant2),
4134
- pattern,
4135
- kind,
4136
- selectorOf(call)
4137
- )
4138
- ];
4139
- }
4140
- function writeMethods(patterns) {
4141
- return new Set(patterns.flatMap((pattern) => pattern.writes));
4142
- }
4143
4462
  function classSettledOn(facts, key) {
4144
4463
  askResolution2(facts, [key], "wanted", RUBY_PROGRAM);
4145
4464
  const objects = new Set(
@@ -4156,27 +4475,64 @@ function declaredName(facts, classKey) {
4156
4475
  function projectDeclares(facts, classKey, method) {
4157
4476
  return facts.lookup("wantedDeclaredName", 0, classKey).some((row) => String(row[1]) === method);
4158
4477
  }
4159
- function instanceWriteEffects(call, file, options, enclosing) {
4478
+ function constantReceiver(constant2, file, facts) {
4479
+ const classKey = classBehind(facts, file, constant2);
4480
+ return classKey === void 0 ? void 0 : { classKey, container: constantName(constant2) };
4481
+ }
4482
+ function settledReceiver(receiver, file, facts, enclosing) {
4483
+ const classKey = classSettledOn(facts, readKey(file, receiver, enclosing));
4484
+ if (classKey === void 0) {
4485
+ return void 0;
4486
+ }
4487
+ const container = declaredName(facts, classKey);
4488
+ return container === void 0 ? void 0 : { classKey, container };
4489
+ }
4490
+ function receiverClass(call, file, options, enclosing) {
4491
+ const constant2 = rootConstant(call);
4492
+ if (constant2 !== null) {
4493
+ return constantReceiver(constant2, file, options.facts);
4494
+ }
4160
4495
  const receiver = receiverOf(call);
4161
- const method = methodOf(call);
4162
- if (receiver === null || !writeMethods(options.patterns).has(method)) {
4163
- return [];
4496
+ if (receiver === null) {
4497
+ return void 0;
4164
4498
  }
4165
- const classKey = classSettledOn(
4166
- options.facts,
4167
- readKey(file, receiver, enclosing)
4168
- );
4169
- if (classKey === void 0) {
4499
+ return settledReceiver(receiver, file, options.facts, enclosing);
4500
+ }
4501
+ function libraryCallIn(call, options) {
4502
+ return chainLinks(call).find(
4503
+ (link) => someLibraryDefines(options, methodOf(link))
4504
+ ) ?? null;
4505
+ }
4506
+ function modelCallEffects(call, file, options, enclosing) {
4507
+ const worked = libraryCallIn(call, options);
4508
+ if (worked === null) {
4170
4509
  return [];
4171
4510
  }
4172
- const pattern = options.patterns.find(
4173
- (candidate) => candidate.writes.includes(method) && reachesBase(options.facts, classKey, candidate.baseClasses)
4174
- );
4175
- if (pattern === void 0 || projectDeclares(options.facts, classKey, method)) {
4511
+ const method = methodOf(worked);
4512
+ const target = receiverClass(worked, file, options, enclosing);
4513
+ if (target === void 0) {
4176
4514
  return [];
4177
4515
  }
4178
- const container = declaredName(options.facts, classKey);
4179
- return container === void 0 ? [] : [storageEffect(call, container, pattern, "write", [])];
4516
+ for (const pattern of options.patterns) {
4517
+ const kind = kindOfCall(pattern, method);
4518
+ if (kind === void 0 || !reachesBase(options.facts, target.classKey, pattern.baseClasses)) {
4519
+ continue;
4520
+ }
4521
+ if (projectDeclares(options.facts, target.classKey, method)) {
4522
+ return [];
4523
+ }
4524
+ return [
4525
+ storageEffect(
4526
+ worked,
4527
+ target.container,
4528
+ pattern,
4529
+ kind,
4530
+ selectorOf(worked, pattern, options.facts),
4531
+ fieldsOf(worked, pattern, kind, options.facts)
4532
+ )
4533
+ ];
4534
+ }
4535
+ return [];
4180
4536
  }
4181
4537
  function isLoader(node, loader) {
4182
4538
  if (node === null) {
@@ -4214,7 +4570,7 @@ function loaderCallEffects(call, file, options) {
4214
4570
  const pattern = modelPattern(argument, file, options);
4215
4571
  if (pattern !== void 0) {
4216
4572
  effects.push(
4217
- storageEffect(call, constantName(argument), pattern, "read", [])
4573
+ storageEffect(call, constantName(argument), pattern, "read", [], [])
4218
4574
  );
4219
4575
  }
4220
4576
  }
@@ -4222,22 +4578,21 @@ function loaderCallEffects(call, file, options) {
4222
4578
  return effects;
4223
4579
  }
4224
4580
  function effectsOfCall(call, file, options, enclosing) {
4225
- const onModel = modelCallEffects(call, file, options);
4226
- if (onModel.length > 0) {
4227
- return onModel;
4228
- }
4229
- const onInstance = rootConstant(call) === null ? instanceWriteEffects(call, file, options, enclosing) : [];
4230
- return onInstance.length > 0 ? onInstance : loaderCallEffects(call, file, options);
4581
+ const onModel = modelCallEffects(call, file, options, enclosing);
4582
+ return onModel.length > 0 ? onModel : loaderCallEffects(call, file, options);
4231
4583
  }
4232
4584
  function storageClaims(call, file, options, enclosing = null) {
4233
4585
  return options.patterns.length > 0 && effectsOfCall(call, file, options, enclosing).length > 0;
4234
4586
  }
4235
4587
  function receiverKeysToAsk(chains, file, options, enclosing) {
4236
- const writes = writeMethods(options.patterns);
4237
4588
  const keys = [];
4238
4589
  for (const call of chains) {
4239
- const receiver = receiverOf(call);
4240
- if (receiver !== null && writes.has(methodOf(call)) && rootConstant(call) === null) {
4590
+ const worked = libraryCallIn(call, options);
4591
+ if (worked === null || rootConstant(worked) !== null) {
4592
+ continue;
4593
+ }
4594
+ const receiver = receiverOf(worked);
4595
+ if (receiver !== null) {
4241
4596
  keys.push(readKey(file, receiver, enclosing));
4242
4597
  }
4243
4598
  }
@@ -4334,11 +4689,17 @@ function createFileCache(parse, readFile) {
4334
4689
  }
4335
4690
  };
4336
4691
  }
4337
- function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
4692
+ function fieldReadContext(pattern, cache, fileBlocks, bodyRead, facts) {
4338
4693
  return {
4339
4694
  pattern,
4340
4695
  cache,
4341
4696
  bodyRead,
4697
+ facts,
4698
+ read: {
4699
+ facts,
4700
+ bodyBlocks: bodyRead.bodyBlocks,
4701
+ dynamicNames: bodyRead.dynamicNames
4702
+ },
4342
4703
  lookup: {
4343
4704
  root: pattern.root,
4344
4705
  pathConvention: pattern.pathConvention,
@@ -4421,7 +4782,13 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
4421
4782
  if (info.bodyNode === null || pattern.baseClassNames.includes(info.qualifiedName)) {
4422
4783
  return [];
4423
4784
  }
4424
- const ctx = fieldReadContext(pattern, options.cache, fileBlocks, options);
4785
+ const ctx = fieldReadContext(
4786
+ pattern,
4787
+ options.cache,
4788
+ fileBlocks,
4789
+ options,
4790
+ options.facts
4791
+ );
4425
4792
  const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, ctx.lookup);
4426
4793
  if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
4427
4794
  return [];
@@ -4469,7 +4836,11 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4469
4836
  ])) {
4470
4837
  return [];
4471
4838
  }
4472
- const filters = controllerFilters(pattern, ancestry);
4839
+ const filters = controllerFilters(pattern, ancestry, {
4840
+ facts: options.facts,
4841
+ bodyBlocks: options.bodyBlocks,
4842
+ dynamicNames: options.dynamicNames
4843
+ });
4473
4844
  const units = [];
4474
4845
  for (const filter of filters) {
4475
4846
  const displayPath = options.displayPathOf?.(filter.file) ?? filter.file;
@@ -4477,7 +4848,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4477
4848
  filter,
4478
4849
  pattern,
4479
4850
  displayPath,
4480
- bodyOfMethod(filter.method, filter.file, options)
4851
+ bodyOfMethod(filter.method, filter.file, options),
4852
+ options.facts
4481
4853
  );
4482
4854
  units.push(raw);
4483
4855
  options.onReachSeed?.(raw, {
@@ -4514,7 +4886,10 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4514
4886
  });
4515
4887
  };
4516
4888
  const own = /* @__PURE__ */ new Set();
4517
- for (const [actionName, method, block] of publicInstanceMethods(ownBlocks)) {
4889
+ for (const [actionName, method, block] of publicInstanceMethods(
4890
+ ownBlocks,
4891
+ options.bodyBlocks
4892
+ )) {
4518
4893
  own.add(actionName);
4519
4894
  emitAction(actionName, method, block);
4520
4895
  }
@@ -4524,7 +4899,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4524
4899
  continue;
4525
4900
  }
4526
4901
  for (const [actionName, method, block] of publicInstanceMethods(
4527
- entry.blocks
4902
+ entry.blocks,
4903
+ options.bodyBlocks
4528
4904
  )) {
4529
4905
  if (seen.has(actionName)) {
4530
4906
  continue;
@@ -4538,14 +4914,17 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4538
4914
  }
4539
4915
  return units;
4540
4916
  }
4541
- function publicInstanceMethods(blocks) {
4917
+ function publicInstanceMethods(blocks, bodyBlocks) {
4542
4918
  const found = [];
4543
4919
  for (const block of blocks) {
4544
4920
  if (block.info.bodyNode === null) {
4545
4921
  continue;
4546
4922
  }
4547
4923
  const visibility = instanceMethodVisibility(block.info.bodyNode);
4548
- for (const [name, method] of instanceMethodsByName(block.info.bodyNode)) {
4924
+ for (const [name, method] of instanceMethodsByName(
4925
+ block.info.bodyNode,
4926
+ bodyBlocks
4927
+ )) {
4549
4928
  if ((visibility.get(name) ?? "public") === "public") {
4550
4929
  found.push([name, method, block]);
4551
4930
  }
@@ -4561,7 +4940,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
4561
4940
  method,
4562
4941
  pattern,
4563
4942
  body.effects ?? [],
4564
- body.extraEffects
4943
+ body.extraEffects,
4944
+ { facts: bodyRead.facts }
4565
4945
  );
4566
4946
  return {
4567
4947
  identity: {
@@ -4691,7 +5071,8 @@ function bodyOfMethod(method, file, bodyRead = {}) {
4691
5071
  const effects = invocationEffects(
4692
5072
  method,
4693
5073
  bodyRead.inheritedMethods,
4694
- EVERY_ARGLESS_CALL
5074
+ EVERY_ARGLESS_CALL,
5075
+ bodyRead.facts
4695
5076
  );
4696
5077
  const storage = bodyRead.storage;
4697
5078
  const extra = [
@@ -4808,7 +5189,7 @@ async function readFieldShape(symbol, callArgs, scope, ctx, ancestry, range) {
4808
5189
  return {
4809
5190
  contract: literalContract(callArgs, scope, ctx),
4810
5191
  body: bodyFromLookup(
4811
- methodInAncestry(ancestry, symbol),
5192
+ methodInAncestry(ancestry, symbol, ctx.read),
4812
5193
  range,
4813
5194
  "This field",
4814
5195
  NO_METHOD_BEHIND_IT,
@@ -4853,7 +5234,7 @@ async function readWiredClass(ref, scope, ctx) {
4853
5234
  return {
4854
5235
  contract: readClassContract(ancestry, ctx.pattern),
4855
5236
  body: bodyFromLookup(
4856
- methodInAncestry(ancestry, ctx.pattern.resolverMethodName),
5237
+ methodInAncestry(ancestry, ctx.pattern.resolverMethodName, ctx.read),
4857
5238
  range,
4858
5239
  `This field's ${targetQualifiedName}`,
4859
5240
  methodNotSettled(
@@ -5025,6 +5406,202 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
5025
5406
  };
5026
5407
  }
5027
5408
 
5409
+ // src/inflect.ts
5410
+ var SINGULAR_RULES = [
5411
+ [/s$/i, ""],
5412
+ [/(ss)$/i, "$1"],
5413
+ [/(n)ews$/i, "$1ews"],
5414
+ [/([ti])a$/i, "$1um"],
5415
+ [
5416
+ /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i,
5417
+ "$1sis"
5418
+ ],
5419
+ [/(^analy)(sis|ses)$/i, "$1sis"],
5420
+ [/([^f])ves$/i, "$1fe"],
5421
+ [/(hive)s$/i, "$1"],
5422
+ [/(tive)s$/i, "$1"],
5423
+ [/([lr])ves$/i, "$1f"],
5424
+ [/([^aeiouy]|qu)ies$/i, "$1y"],
5425
+ [/(s)eries$/i, "$1eries"],
5426
+ [/(m)ovies$/i, "$1ovie"],
5427
+ [/(x|ch|ss|sh)es$/i, "$1"],
5428
+ [/^(m|l)ice$/i, "$1ouse"],
5429
+ [/(bus)(es)?$/i, "$1"],
5430
+ [/(o)es$/i, "$1"],
5431
+ [/(shoe)s$/i, "$1"],
5432
+ [/(cris|test)(is|es)$/i, "$1is"],
5433
+ [/^(a)x[ie]s$/i, "$1xis"],
5434
+ [/(octop|vir)(us|i)$/i, "$1us"],
5435
+ [/(alias|status)(es)?$/i, "$1"],
5436
+ [/^(ox)en/i, "$1"],
5437
+ [/(vert|ind)ices$/i, "$1ex"],
5438
+ [/(matr)ices$/i, "$1ix"],
5439
+ [/(quiz)zes$/i, "$1"],
5440
+ [/(database)s$/i, "$1"],
5441
+ // The words ActiveSupport calls irregular. It declares them after the
5442
+ // rules above, which is what puts them ahead in the search.
5443
+ [/(p)erson$/i, "$1erson"],
5444
+ [/(p)eople$/i, "$1erson"],
5445
+ [/(m)an$/i, "$1an"],
5446
+ [/(m)en$/i, "$1an"],
5447
+ [/(c)hild$/i, "$1hild"],
5448
+ [/(c)hildren$/i, "$1hild"],
5449
+ [/(s)ex$/i, "$1ex"],
5450
+ [/(s)exes$/i, "$1ex"],
5451
+ [/(m)ove$/i, "$1ove"],
5452
+ [/(m)oves$/i, "$1ove"],
5453
+ [/(z)ombie$/i, "$1ombie"],
5454
+ [/(z)ombies$/i, "$1ombie"]
5455
+ ];
5456
+ var UNCOUNTABLE = /* @__PURE__ */ new Set([
5457
+ "equipment",
5458
+ "information",
5459
+ "rice",
5460
+ "money",
5461
+ "species",
5462
+ "series",
5463
+ "fish",
5464
+ "sheep",
5465
+ "jeans",
5466
+ "police"
5467
+ ]);
5468
+ function singularize(word, project) {
5469
+ if (UNCOUNTABLE.has(word.toLowerCase()) || (project?.uncountable ?? []).some(
5470
+ (uncountable) => uncountable.toLowerCase() === word.toLowerCase()
5471
+ )) {
5472
+ return word;
5473
+ }
5474
+ const own = projectSingular(word, project);
5475
+ if (own !== null) {
5476
+ return own;
5477
+ }
5478
+ for (let at = SINGULAR_RULES.length - 1; at >= 0; at--) {
5479
+ const [pattern, replacement] = SINGULAR_RULES[at];
5480
+ if (pattern.test(word)) {
5481
+ return word.replace(pattern, replacement);
5482
+ }
5483
+ }
5484
+ return word;
5485
+ }
5486
+ function projectSingular(word, project) {
5487
+ const irregular = [...project?.irregular ?? []].reverse();
5488
+ for (const [plural, singular] of irregular) {
5489
+ if (plural.toLowerCase() === word.toLowerCase()) {
5490
+ return matchingCase(word, singular);
5491
+ }
5492
+ }
5493
+ const rules = [...project?.singular ?? []].reverse();
5494
+ for (const [rule3, replacement] of rules) {
5495
+ const applied = applyRule(word, rule3, replacement);
5496
+ if (applied !== null) {
5497
+ return applied;
5498
+ }
5499
+ }
5500
+ return null;
5501
+ }
5502
+ function applyRule(word, rule3, replacement) {
5503
+ const written = /^\/(.*)\/([a-z]*)$/s.exec(rule3);
5504
+ if (written === null) {
5505
+ return word.includes(rule3) ? word.replace(rule3, replacement) : null;
5506
+ }
5507
+ const flags = (written[2] ?? "").replace("m", "s").replace(/[^is]/g, "");
5508
+ const pattern = new RegExp(written[1] ?? "", flags);
5509
+ return pattern.test(word) ? word.replace(pattern, replacement.replace(/\\(\d)/g, "$$$1")) : null;
5510
+ }
5511
+ function matchingCase(word, replacement) {
5512
+ const first = word[0];
5513
+ if (first === void 0 || first !== first.toUpperCase()) {
5514
+ return replacement;
5515
+ }
5516
+ return replacement.charAt(0).toUpperCase() + replacement.slice(1);
5517
+ }
5518
+ function camelize(word, acronyms = []) {
5519
+ return word.split("_").map((part) => capitalize(part, acronyms)).join("");
5520
+ }
5521
+ function capitalize(part, acronyms) {
5522
+ if (part === "") {
5523
+ return "";
5524
+ }
5525
+ const acronym = acronyms.find(
5526
+ (known) => known.toLowerCase() === part.toLowerCase()
5527
+ );
5528
+ return acronym ?? part[0]?.toUpperCase() + part.slice(1);
5529
+ }
5530
+ function associationTargetName(name, plural, project) {
5531
+ return camelize(
5532
+ plural ? singularize(name, project) : name,
5533
+ project?.acronyms ?? []
5534
+ );
5535
+ }
5536
+
5537
+ // src/facts/associations.ts
5538
+ function declaredName2(argument) {
5539
+ if (argument === void 0) {
5540
+ return null;
5541
+ }
5542
+ if (argument.type === "simple_symbol") {
5543
+ return argument.text.slice(1);
5544
+ }
5545
+ return stringLiteralValue(argument);
5546
+ }
5547
+ function targetReference(key, written, nesting) {
5548
+ if (written.startsWith("::")) {
5549
+ return { key, written: written.slice(2), nesting: [] };
5550
+ }
5551
+ return { key, written, nesting };
5552
+ }
5553
+ function targetName(call, calls, name, plural, inflections) {
5554
+ const override = readCallArgs(field(call, "arguments")).keyword[calls.classNameKeyword];
5555
+ if (override === void 0) {
5556
+ return associationTargetName(name, plural, inflections);
5557
+ }
5558
+ return stringLiteralValue(override);
5559
+ }
5560
+ function associationsDeclaredIn(cls, classKey, nesting, calls, inflections) {
5561
+ const body = field(cls, "body");
5562
+ if (body === null || calls.length === 0) {
5563
+ return [];
5564
+ }
5565
+ const found = [];
5566
+ for (const statement of runStatements(body)) {
5567
+ if (statement.type !== "call" || field(statement, "receiver") !== null) {
5568
+ continue;
5569
+ }
5570
+ const method = field(statement, "method")?.text ?? "";
5571
+ for (const declaration of calls) {
5572
+ const plural = declaration.plural.includes(method);
5573
+ if (!plural && !declaration.singular.includes(method)) {
5574
+ continue;
5575
+ }
5576
+ const positional = readCallArgs(field(statement, "arguments")).positional;
5577
+ const name = declaredName2(positional[0]);
5578
+ if (name === null) {
5579
+ continue;
5580
+ }
5581
+ const written = targetName(
5582
+ statement,
5583
+ declaration,
5584
+ name,
5585
+ plural,
5586
+ inflections
5587
+ );
5588
+ if (written === null) {
5589
+ continue;
5590
+ }
5591
+ found.push({
5592
+ classKey,
5593
+ name,
5594
+ target: targetReference(
5595
+ `${classKey}#association:${name}`,
5596
+ written,
5597
+ nesting
5598
+ )
5599
+ });
5600
+ }
5601
+ }
5602
+ return found;
5603
+ }
5604
+
5028
5605
  // src/facts/constants.ts
5029
5606
  function children4(node) {
5030
5607
  return node.namedChildren.filter((child) => child !== null);
@@ -5059,20 +5636,32 @@ function isDeclaration(node, parent) {
5059
5636
  }
5060
5637
  return parent.type === "scope_resolution";
5061
5638
  }
5062
- function collectFileConstants(filePath, root) {
5639
+ function collectFileConstants(filePath, root, associationCalls = [], inflections) {
5063
5640
  const definitions = [];
5064
5641
  const references = [];
5642
+ const associations = [];
5065
5643
  const walk = (node, parent, nesting) => {
5066
5644
  if (NESTING_TYPES.has(node.type)) {
5067
5645
  const name = field(node, "name");
5068
5646
  const written = name === null ? null : writtenName(name);
5069
5647
  if (written !== null) {
5648
+ const classKey = nodeId(filePath, node);
5070
5649
  definitions.push({
5071
5650
  qualifiedName: qualify(nesting, written),
5072
- key: nodeId(filePath, node),
5651
+ key: classKey,
5073
5652
  kind: "declaration"
5074
5653
  });
5075
5654
  const inside = [...nesting, ...written.split("::")];
5655
+ for (const declared of associationsDeclaredIn(
5656
+ node,
5657
+ classKey,
5658
+ inside,
5659
+ associationCalls,
5660
+ inflections
5661
+ )) {
5662
+ associations.push(declared);
5663
+ references.push(declared.target);
5664
+ }
5076
5665
  for (const child of children4(node)) {
5077
5666
  walk(child, node, inside);
5078
5667
  }
@@ -5104,7 +5693,7 @@ function collectFileConstants(filePath, root) {
5104
5693
  }
5105
5694
  };
5106
5695
  walk(root, null, []);
5107
- return { filePath, definitions, references };
5696
+ return { filePath, definitions, references, associations };
5108
5697
  }
5109
5698
  function emitConstantBindings(db, perFile) {
5110
5699
  const byName = /* @__PURE__ */ new Map();
@@ -5128,6 +5717,13 @@ function emitConstantBindings(db, perFile) {
5128
5717
  return found.length === 1 ? found[0].key : null;
5129
5718
  };
5130
5719
  for (const file of files) {
5720
+ for (const declared of file.associations) {
5721
+ db.add("declaresAssociation", [
5722
+ declared.classKey,
5723
+ declared.name,
5724
+ declared.target.key
5725
+ ]);
5726
+ }
5131
5727
  for (const reference of file.references) {
5132
5728
  for (let depth = reference.nesting.length; depth >= 0; depth--) {
5133
5729
  const candidate = qualify(
@@ -5214,6 +5810,7 @@ function parseWith(language, source) {
5214
5810
  import fs3 from "fs";
5215
5811
  import path4 from "path";
5216
5812
  import {
5813
+ boundaryKey,
5217
5814
  disambiguateSummaryIds,
5218
5815
  linkCallsToSummaries,
5219
5816
  placeArgTargets as placeArgTargets2,
@@ -5234,6 +5831,7 @@ import {
5234
5831
  runDigest,
5235
5832
  stampModuleImports
5236
5833
  } from "@suss/extractor";
5834
+ import { addPackWords } from "@suss/resolution";
5237
5835
 
5238
5836
  // src/diagnostics.ts
5239
5837
  import {
@@ -5289,6 +5887,35 @@ function requiredRelativePath(node) {
5289
5887
  return positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
5290
5888
  }
5291
5889
 
5890
+ // src/pack.ts
5891
+ function inflectionsIn(packs) {
5892
+ const pooled = {
5893
+ acronyms: [],
5894
+ irregular: [],
5895
+ uncountable: [],
5896
+ singular: []
5897
+ };
5898
+ for (const pack of packs) {
5899
+ pooled.acronyms?.push(...pack.inflections?.acronyms ?? []);
5900
+ pooled.irregular?.push(...pack.inflections?.irregular ?? []);
5901
+ pooled.uncountable?.push(...pack.inflections?.uncountable ?? []);
5902
+ pooled.singular?.push(...pack.inflections?.singular ?? []);
5903
+ }
5904
+ return pooled;
5905
+ }
5906
+ function bodyBlocksIn(packs) {
5907
+ const pooled = /* @__PURE__ */ new Map();
5908
+ for (const pack of packs) {
5909
+ for (const declared of pack.bodyBlocks ?? []) {
5910
+ pooled.set(declared.name, {
5911
+ moduleOnly: declared.moduleOnly ?? false,
5912
+ definesClassMethods: declared.definesClassMethods ?? false
5913
+ });
5914
+ }
5915
+ }
5916
+ return pooled;
5917
+ }
5918
+
5292
5919
  // src/reach/closure.ts
5293
5920
  import {
5294
5921
  functionCallBinding,
@@ -5466,7 +6093,11 @@ function methodOnAncestryOf(qualifiedName, methodName, ctx) {
5466
6093
  if (ancestry === void 0) {
5467
6094
  return stop("outsideRun");
5468
6095
  }
5469
- const found = methodInAncestry(ancestry, methodName);
6096
+ const found = methodInAncestry(ancestry, methodName, {
6097
+ facts: ctx.facts,
6098
+ bodyBlocks: ctx.bodyBlocks,
6099
+ dynamicNames: ctx.dynamicNames
6100
+ });
5470
6101
  if (found.type === "found") {
5471
6102
  return followed(reachedMethod(found.method, found.block, methodName));
5472
6103
  }
@@ -5495,7 +6126,10 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
5495
6126
  if (block.info.bodyNode === null) {
5496
6127
  continue;
5497
6128
  }
5498
- const found = singletonMethodsByName(block.info.bodyNode).get(methodName);
6129
+ const found = singletonMethodsByName(
6130
+ block.info.bodyNode,
6131
+ ctx.bodyBlocks
6132
+ ).get(methodName);
5499
6133
  if (found !== void 0) {
5500
6134
  return followed(reachedMethod(found, block, methodName));
5501
6135
  }
@@ -5849,7 +6483,7 @@ function parameterName(param) {
5849
6483
 
5850
6484
  // src/reach/context.ts
5851
6485
  var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
5852
- async function buildReachContext(files, facts) {
6486
+ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
5853
6487
  const blocksByQualifiedName = /* @__PURE__ */ new Map();
5854
6488
  const classes = [];
5855
6489
  const classNames = /* @__PURE__ */ new Map();
@@ -5923,7 +6557,9 @@ async function buildReachContext(files, facts) {
5923
6557
  topLevelMethods,
5924
6558
  facts,
5925
6559
  classNames,
5926
- definitions
6560
+ definitions,
6561
+ bodyBlocks,
6562
+ dynamicNames
5927
6563
  };
5928
6564
  }
5929
6565
  function namedChildren(node) {
@@ -5969,20 +6605,48 @@ function inheritedMethodsIn(packs) {
5969
6605
  }
5970
6606
  return found;
5971
6607
  }
5972
- function emitStorageFacts(db, packs) {
5973
- for (const pack of packs) {
5974
- for (const pattern of pack.storage ?? []) {
5975
- for (const base of pattern.baseClasses) {
5976
- for (const method of pattern.givesBack) {
5977
- db.add("givesBackOne", [base, method]);
5978
- }
5979
- }
5980
- }
5981
- }
6608
+ function associationCallsIn(packs) {
6609
+ return packs.flatMap(
6610
+ (pack) => (pack.storage ?? []).flatMap(
6611
+ (pattern) => pattern.associations === void 0 ? [] : [pattern.associations]
6612
+ )
6613
+ );
6614
+ }
6615
+ function packWordsOf(packs) {
6616
+ return {
6617
+ givesBackOne: packs.flatMap(
6618
+ (pack) => (pack.storage ?? []).flatMap(
6619
+ (pattern) => pattern.baseClasses.flatMap(
6620
+ (base) => pattern.givesBack.map((method) => ({ base, method }))
6621
+ )
6622
+ )
6623
+ )
6624
+ };
6625
+ }
6626
+ function factsForFile(options) {
6627
+ const db = new Database2();
6628
+ const bodyBlocks = bodyBlocksIn(options.packs);
6629
+ emitValueFacts(db, options.file, options.root, bodyBlocks);
6630
+ emitConstantBindings(db, [
6631
+ collectFileConstants(
6632
+ options.file,
6633
+ options.root,
6634
+ associationCallsIn(options.packs),
6635
+ inflectionsIn(options.packs)
6636
+ )
6637
+ ]);
6638
+ emitRequireFacts(db, options.file, options.root, /* @__PURE__ */ new Set([options.file]));
6639
+ bindEvaluator(db, {
6640
+ files: [{ file: options.file, root: options.root }],
6641
+ definitions: new Map(methodDefinitionsIn(options.file, options.root))
6642
+ });
6643
+ addPackWords(db, packWordsOf(options.packs));
6644
+ return db;
5982
6645
  }
5983
6646
  function alreadyDiscovered(seen, raw) {
5984
6647
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
5985
- const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
6648
+ const boundary = raw.boundaryBinding === null ? "" : boundaryKey(raw.boundaryBinding) ?? "";
6649
+ const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
5986
6650
  if (seen.has(key)) {
5987
6651
  return true;
5988
6652
  }
@@ -6023,6 +6687,9 @@ async function extractRubyProject(options) {
6023
6687
  );
6024
6688
  const parsed = [];
6025
6689
  const definitions = /* @__PURE__ */ new Map();
6690
+ const associationCalls = associationCallsIn(options.packs);
6691
+ const bodyBlocks = bodyBlocksIn(options.packs);
6692
+ const inflections = inflectionsIn(options.packs);
6026
6693
  for (const file of options.files) {
6027
6694
  await timer.timeAsync("parse", async () => {
6028
6695
  const root = await cache.get(file);
@@ -6030,21 +6697,27 @@ async function extractRubyProject(options) {
6030
6697
  return;
6031
6698
  }
6032
6699
  parsed.push({ file, root });
6033
- emitValueFacts(db, file, root);
6700
+ emitValueFacts(db, file, root, bodyBlocks);
6034
6701
  for (const [key, method] of methodDefinitionsIn(file, root)) {
6035
6702
  definitions.set(key, method);
6036
6703
  }
6037
- constants.push(collectFileConstants(file, root));
6704
+ constants.push(
6705
+ collectFileConstants(file, root, associationCalls, inflections)
6706
+ );
6038
6707
  });
6039
6708
  }
6040
- timer.time("discover", () => {
6709
+ const dynamicNames = timer.time("discover", () => {
6041
6710
  emitConstantBindings(db, constants);
6042
6711
  const known = new Set(parsed.map(({ file }) => file));
6043
6712
  for (const { file, root } of parsed) {
6044
6713
  emitRequireFacts(db, file, root, known);
6045
6714
  }
6046
6715
  bindEvaluator(db, { files: parsed, definitions });
6047
- emitStorageFacts(db, options.packs);
6716
+ addPackWords(db, packWordsOf(options.packs));
6717
+ return readDynamicNames(
6718
+ db,
6719
+ new Map(parsed.map(({ file, root }) => [file, root]))
6720
+ );
6048
6721
  });
6049
6722
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
6050
6723
  const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
@@ -6052,7 +6725,7 @@ async function extractRubyProject(options) {
6052
6725
  const inheritedMethods = inheritedMethodsIn(options.packs);
6053
6726
  const reachContext = await timer.timeAsync(
6054
6727
  "discover",
6055
- () => buildReachContext(parsed, db)
6728
+ () => buildReachContext(parsed, db, bodyBlocks, dynamicNames)
6056
6729
  );
6057
6730
  const displayPathOf = (file) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file) : file;
6058
6731
  const seeds = [];
@@ -6072,6 +6745,8 @@ async function extractRubyProject(options) {
6072
6745
  cache,
6073
6746
  ...storage === void 0 ? {} : { storage },
6074
6747
  inheritedMethods,
6748
+ bodyBlocks,
6749
+ dynamicNames,
6075
6750
  displayPathOf,
6076
6751
  facts: db,
6077
6752
  onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
@@ -6143,7 +6818,9 @@ async function extractRubyProject(options) {
6143
6818
  context: reachContext,
6144
6819
  displayPathOf,
6145
6820
  ...storage === void 0 ? {} : { storage },
6146
- inheritedMethods
6821
+ inheritedMethods,
6822
+ bodyBlocks,
6823
+ dynamicNames
6147
6824
  })
6148
6825
  );
6149
6826
  for (const entry of found) {
@@ -6309,6 +6986,10 @@ function displayNameOf(node) {
6309
6986
  function width(node) {
6310
6987
  return node.endIndex - node.startIndex;
6311
6988
  }
6989
+ function askedKey(value) {
6990
+ const enclosing = enclosingMethod2(value.node);
6991
+ return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
6992
+ }
6312
6993
  var RubyWhySession = class {
6313
6994
  root;
6314
6995
  db = new Database3();
@@ -6384,7 +7065,7 @@ var RubyWhySession = class {
6384
7065
  return explainResolvedKey({
6385
7066
  db: this.db,
6386
7067
  rules: WITNESS_RULES,
6387
- key: readKey(value.file, value.node, enclosingMethod2(value.node)),
7068
+ key: askedKey(value),
6388
7069
  locate: (key) => this.locate(key),
6389
7070
  displayPath: (key) => this.displayPath(key),
6390
7071
  ...options
@@ -6501,6 +7182,7 @@ export {
6501
7182
  emitValueFacts,
6502
7183
  evaluatedValue,
6503
7184
  extractRubyProject,
7185
+ factsForFile,
6504
7186
  field,
6505
7187
  findRubyFiles,
6506
7188
  graphqlTypeNameFromQualified,