@suss/adapter-ruby 0.31.2 → 0.32.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.
Files changed (3) hide show
  1. package/dist/index.cjs +188 -45
  2. package/dist/index.js +188 -45
  3. package/package.json +7 -7
package/dist/index.cjs CHANGED
@@ -144,6 +144,17 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
144
144
  "singleton_class"
145
145
  ]);
146
146
  var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
147
+ var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
148
+ function enclosingMethod(node) {
149
+ let current = node.parent;
150
+ while (current !== null) {
151
+ if (METHOD_TYPES.has(current.type)) {
152
+ return current;
153
+ }
154
+ current = current.parent;
155
+ }
156
+ return null;
157
+ }
147
158
  var INCLUDE_CALL = "include";
148
159
  var PREPEND_CALL = "prepend";
149
160
  var ARGUMENT_LIST_TYPE = "argument_list";
@@ -460,8 +471,11 @@ var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
460
471
  function resolveValues(db, keys) {
461
472
  (0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
462
473
  }
474
+ function resolveEnvSites(db, sites) {
475
+ (0, import_resolution.askResolution)(db, sites, "wantedEnvSite", RUBY_PROGRAM);
476
+ }
463
477
  function resolvedFunctions(db, key) {
464
- return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
478
+ return db.lookup("wantedResolves", 0, key).map((row) => String(row[1]));
465
479
  }
466
480
  function writtenValueOf(db, key) {
467
481
  resolveValues(db, [key]);
@@ -1340,7 +1354,6 @@ function emitCandidates(emitter, key, group) {
1340
1354
  add(emitter, "writesAllStated", key);
1341
1355
  }
1342
1356
  }
1343
- var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1344
1357
  var INITIALIZE_METHOD = "initialize";
1345
1358
  function receiverKeyOf(filePath, method, classKey) {
1346
1359
  return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
@@ -1388,7 +1401,7 @@ function emitClassFacts(emitter, cls) {
1388
1401
  left.text,
1389
1402
  valueKey(emitter, right)
1390
1403
  );
1391
- emitExpressionFacts(emitter, statement);
1404
+ emitExpressionFacts(within, statement);
1392
1405
  }
1393
1406
  continue;
1394
1407
  }
@@ -2451,7 +2464,6 @@ var evaluators = /* @__PURE__ */ new WeakMap();
2451
2464
  var contexts = /* @__PURE__ */ new WeakMap();
2452
2465
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2453
2466
  var filesByTree = /* @__PURE__ */ new WeakMap();
2454
- var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2455
2467
  function bindEvaluator(db, nodes) {
2456
2468
  for (const entry of nodes.files) {
2457
2469
  filesByTree.set(entry.root.tree, entry.file);
@@ -2505,7 +2517,7 @@ function stringValueOf(node, db, bindings) {
2505
2517
  function methodDefinitionsIn(file, root) {
2506
2518
  const found = /* @__PURE__ */ new Map();
2507
2519
  const visit = (node) => {
2508
- if (METHOD_TYPES3.has(node.type)) {
2520
+ if (METHOD_TYPES.has(node.type)) {
2509
2521
  found.set(nodeId(file, node), node);
2510
2522
  }
2511
2523
  for (const child of node.namedChildren) {
@@ -2530,16 +2542,6 @@ function evaluatorFor(node, db) {
2530
2542
  }
2531
2543
  return local;
2532
2544
  }
2533
- function enclosingMethod(node) {
2534
- let current = node.parent;
2535
- while (current !== null) {
2536
- if (METHOD_TYPES3.has(current.type)) {
2537
- return current;
2538
- }
2539
- current = current.parent;
2540
- }
2541
- return null;
2542
- }
2543
2545
  function contextOver(db, nodes) {
2544
2546
  const filesByRoot = /* @__PURE__ */ new Map();
2545
2547
  const rootsByFile = /* @__PURE__ */ new Map();
@@ -4077,19 +4079,155 @@ function returnBranch(range, effects) {
4077
4079
  var import_behavioral_ir2 = require("@suss/behavioral-ir");
4078
4080
  var import_extractor4 = require("@suss/extractor");
4079
4081
  var RUBY_ENV_RECOGNITION = "ruby-env";
4082
+ var ENV_DEFAULTED = "rbEnvDefaulted";
4080
4083
  var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
4081
- function envReadEffects(root) {
4082
- const effects = [];
4084
+ function envReadEffects(root, facts) {
4085
+ const helpers = helperFactsOf(facts);
4086
+ const slots = [];
4083
4087
  (0, import_extractor4.walkDescendants)(root, null, {
4084
4088
  at: (node) => {
4085
4089
  const read = envReadAt(node);
4086
4090
  if (read !== null) {
4087
- effects.push(configReadEffect(read));
4091
+ slots.push({ read });
4092
+ return;
4093
+ }
4094
+ if (helpers !== null && node.type === "call") {
4095
+ slots.push({ call: node });
4088
4096
  }
4089
4097
  },
4090
4098
  into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? import_extractor4.SKIP_CHILDREN : null
4091
4099
  });
4092
- return effects;
4100
+ const calls = slots.flatMap((slot) => "call" in slot ? [slot.call] : []);
4101
+ const throughHelpers = helpers === null ? new NodeMap() : helperReads(calls, helpers);
4102
+ return slots.flatMap((slot) => {
4103
+ if ("read" in slot) {
4104
+ return [configReadEffect(slot.read)];
4105
+ }
4106
+ return (throughHelpers.get(slot.call) ?? []).map(configReadEffect);
4107
+ });
4108
+ }
4109
+ function helperFactsOf(facts) {
4110
+ if (facts === void 0) {
4111
+ return null;
4112
+ }
4113
+ const named2 = namedParameters(facts.db);
4114
+ if (named2.size === 0) {
4115
+ return null;
4116
+ }
4117
+ return { ...facts, named: named2 };
4118
+ }
4119
+ var namedByDb = /* @__PURE__ */ new WeakMap();
4120
+ function namedParameters(db) {
4121
+ const memo = namedByDb.get(db);
4122
+ if (memo !== void 0) {
4123
+ return memo;
4124
+ }
4125
+ const named2 = /* @__PURE__ */ new Map();
4126
+ namedByDb.set(db, named2);
4127
+ const sites = db.facts("readsEnvNamed").map((row) => String(row[0]));
4128
+ if (sites.length === 0) {
4129
+ return named2;
4130
+ }
4131
+ resolveEnvSites(db, sites);
4132
+ for (const row of db.facts("wantedParamNamesEnv")) {
4133
+ const found = named2.get(String(row[0]));
4134
+ if (found === void 0) {
4135
+ named2.set(String(row[0]), [String(row[1])]);
4136
+ continue;
4137
+ }
4138
+ found.push(String(row[1]));
4139
+ }
4140
+ return named2;
4141
+ }
4142
+ function emitEnvNameFacts(db, file, root) {
4143
+ for (const site of envNameSites(root)) {
4144
+ const key = nodeId(file, site.read);
4145
+ db.add("readsEnvNamed", [
4146
+ key,
4147
+ readKey(file, site.name, enclosingMethod(site.name))
4148
+ ]);
4149
+ if (site.defaulted) {
4150
+ db.add(ENV_DEFAULTED, [key]);
4151
+ }
4152
+ }
4153
+ }
4154
+ function envNameSites(root) {
4155
+ const sites = [];
4156
+ (0, import_extractor4.walkDescendants)(root, null, {
4157
+ at: (node) => {
4158
+ const site = envSiteAt(node);
4159
+ if (site !== null && stringLiteralValue(site.name) === null) {
4160
+ sites.push(site);
4161
+ }
4162
+ },
4163
+ into: () => null
4164
+ });
4165
+ return sites;
4166
+ }
4167
+ function helperReads(calls, facts) {
4168
+ const { db, file, named: named2 } = facts;
4169
+ const callees = new NodeMap();
4170
+ for (const call of calls) {
4171
+ const key = calleeKeyOf(file, call, enclosingMethod(call));
4172
+ if (key !== null) {
4173
+ callees.set(call, key);
4174
+ }
4175
+ }
4176
+ const reads = new NodeMap();
4177
+ if (callees.size === 0) {
4178
+ return reads;
4179
+ }
4180
+ resolveValues(
4181
+ db,
4182
+ [...callees].map(([, callee]) => callee)
4183
+ );
4184
+ for (const [call, callee] of callees) {
4185
+ const args = namingArguments(db, named2, call, callee);
4186
+ if (args.length === 0) {
4187
+ continue;
4188
+ }
4189
+ const found = readsAtCall(db, call, args);
4190
+ if (found.length > 0) {
4191
+ reads.set(call, found);
4192
+ }
4193
+ }
4194
+ return reads;
4195
+ }
4196
+ function namingArguments(db, named2, call, callee) {
4197
+ const { positional, keyword } = readCallArgs(field(call, "arguments"));
4198
+ const found = [];
4199
+ for (const func of resolvedFunctions(db, callee)) {
4200
+ for (const row of db.lookup("paramOf", 0, func)) {
4201
+ const sites = named2.get(String(row[2]));
4202
+ const argument = positional[Number(row[1])];
4203
+ if (sites !== void 0 && argument !== void 0) {
4204
+ found.push({ sites, argument });
4205
+ }
4206
+ }
4207
+ for (const row of db.lookup("paramNamed", 0, func)) {
4208
+ const sites = named2.get(String(row[2]));
4209
+ const argument = keyword[String(row[1])];
4210
+ if (sites !== void 0 && argument !== void 0) {
4211
+ found.push({ sites, argument });
4212
+ }
4213
+ }
4214
+ }
4215
+ return found;
4216
+ }
4217
+ function readsAtCall(db, call, args) {
4218
+ const defaultedAtCall = isDefaultedAt(call);
4219
+ const byName = /* @__PURE__ */ new Map();
4220
+ for (const { sites, argument } of args) {
4221
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
4222
+ if (name === null) {
4223
+ continue;
4224
+ }
4225
+ for (const site of sites) {
4226
+ const defaulted = defaultedAtCall || db.lookup(ENV_DEFAULTED, 0, site).length > 0;
4227
+ byName.set(name, (byName.get(name) ?? true) && defaulted);
4228
+ }
4229
+ }
4230
+ return [...byName].map(([name, defaulted]) => ({ name, defaulted }));
4093
4231
  }
4094
4232
  function configReadEffect(read) {
4095
4233
  return {
@@ -4108,15 +4246,23 @@ function configReadEffect(read) {
4108
4246
  };
4109
4247
  }
4110
4248
  function envReadAt(node) {
4249
+ const site = envSiteAt(node);
4250
+ if (site === null) {
4251
+ return null;
4252
+ }
4253
+ const name = stringLiteralValue(site.name);
4254
+ return name === null ? null : { name, defaulted: site.defaulted };
4255
+ }
4256
+ function envSiteAt(node) {
4111
4257
  if (node.type === "element_reference") {
4112
- return elementRead(node);
4258
+ return elementSite(node);
4113
4259
  }
4114
4260
  if (node.type === "call") {
4115
- return fetchRead(node);
4261
+ return fetchSite(node);
4116
4262
  }
4117
4263
  return null;
4118
4264
  }
4119
- function elementRead(node) {
4265
+ function elementSite(node) {
4120
4266
  const object = field(node, "object");
4121
4267
  if (object === null || !isEnv(object) || isAssignedTo(node)) {
4122
4268
  return null;
@@ -4124,24 +4270,23 @@ function elementRead(node) {
4124
4270
  const index = node.namedChildren.find(
4125
4271
  (child) => child !== null && child.id !== object.id
4126
4272
  );
4127
- const name = index === void 0 ? null : stringLiteralValue(index);
4128
- if (name === null) {
4273
+ if (index === void 0) {
4129
4274
  return null;
4130
4275
  }
4131
- return { name, defaulted: isDefaultedAt(node) };
4276
+ return { read: node, name: index, defaulted: isDefaultedAt(node) };
4132
4277
  }
4133
- function fetchRead(node) {
4278
+ function fetchSite(node) {
4134
4279
  const receiver = field(node, "receiver");
4135
4280
  if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
4136
4281
  return null;
4137
4282
  }
4138
4283
  const { positional } = readCallArgs(field(node, "arguments"));
4139
- const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
4140
- if (name === null) {
4284
+ const name = positional[0];
4285
+ if (name === void 0) {
4141
4286
  return null;
4142
4287
  }
4143
4288
  const hasDefault = positional.length > 1 || field(node, "block") !== null;
4144
- return { name, defaulted: hasDefault || isDefaultedAt(node) };
4289
+ return { read: node, name, defaulted: hasDefault || isDefaultedAt(node) };
4145
4290
  }
4146
4291
  function isEnv(node) {
4147
4292
  if (node.type === "constant") {
@@ -5124,8 +5269,12 @@ function bodyOfMethod(method, file, bodyRead = {}) {
5124
5269
  bodyRead.facts
5125
5270
  );
5126
5271
  const storage = bodyRead.storage;
5272
+ const facts = bodyRead.facts;
5127
5273
  const extra = [
5128
- ...envReadEffects(method),
5274
+ ...envReadEffects(
5275
+ method,
5276
+ facts === void 0 ? void 0 : { db: facts, file }
5277
+ ),
5129
5278
  ...storage === void 0 ? [] : storageEffects(callsUnder2(method), file, storage, method)
5130
5279
  ];
5131
5280
  return {
@@ -6495,7 +6644,7 @@ function parameterName(param) {
6495
6644
  }
6496
6645
 
6497
6646
  // src/reach/context.ts
6498
- var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6647
+ var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6499
6648
  async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
6500
6649
  const blocksByQualifiedName = /* @__PURE__ */ new Map();
6501
6650
  const classes = [];
@@ -6506,7 +6655,7 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
6506
6655
  classes.push({ file, info });
6507
6656
  classNames.set(nodeId(file, info.node), info.qualifiedName);
6508
6657
  for (const statement of namedChildren(info.bodyNode)) {
6509
- if (METHOD_TYPES4.has(statement.type)) {
6658
+ if (METHOD_TYPES3.has(statement.type)) {
6510
6659
  classOfMethod.set(nodeId(file, statement), info.qualifiedName);
6511
6660
  }
6512
6661
  }
@@ -6640,6 +6789,7 @@ function factsForFile(options) {
6640
6789
  const db = new import_datalog3.Database();
6641
6790
  const bodyBlocks = bodyBlocksIn(options.packs);
6642
6791
  emitValueFacts(db, options.file, options.root, bodyBlocks);
6792
+ emitEnvNameFacts(db, options.file, options.root);
6643
6793
  emitConstantBindings(db, [
6644
6794
  collectFileConstants(
6645
6795
  options.file,
@@ -6711,6 +6861,7 @@ async function extractRubyProject(options) {
6711
6861
  }
6712
6862
  parsed.push({ file, root });
6713
6863
  emitValueFacts(db, file, root, bodyBlocks);
6864
+ emitEnvNameFacts(db, file, root);
6714
6865
  for (const [key, method] of methodDefinitionsIn(file, root)) {
6715
6866
  definitions.set(key, method);
6716
6867
  }
@@ -6788,7 +6939,10 @@ async function extractRubyProject(options) {
6788
6939
  });
6789
6940
  }
6790
6941
  }
6791
- const loadTimeReads = timer.time("discover", () => envReadEffects(root));
6942
+ const loadTimeReads = timer.time(
6943
+ "discover",
6944
+ () => envReadEffects(root, { db, file })
6945
+ );
6792
6946
  if (loadTimeReads.length > 0) {
6793
6947
  const summary = timer.time(
6794
6948
  "summarize",
@@ -6951,20 +7105,9 @@ function findRubyFiles(root) {
6951
7105
 
6952
7106
  // src/facts/why.ts
6953
7107
  var WITNESS_RULES = [...import_resolution9.RESOLUTION_RULES, ...RUBY_RULES];
6954
- var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6955
7108
  function namedChildrenOf(node) {
6956
7109
  return node.namedChildren.filter((child) => child !== null);
6957
7110
  }
6958
- function enclosingMethod2(node) {
6959
- let current = node.parent;
6960
- while (current !== null) {
6961
- if (METHOD_TYPES5.has(current.type)) {
6962
- return current;
6963
- }
6964
- current = current.parent;
6965
- }
6966
- return null;
6967
- }
6968
7111
  function indexFile(file, root, locations) {
6969
7112
  const walk = (node) => {
6970
7113
  locations.set(nodeId(file, node), { file, node });
@@ -7000,7 +7143,7 @@ function width(node) {
7000
7143
  return node.endIndex - node.startIndex;
7001
7144
  }
7002
7145
  function askedKey(value) {
7003
- const enclosing = enclosingMethod2(value.node);
7146
+ const enclosing = enclosingMethod(value.node);
7004
7147
  return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
7005
7148
  }
7006
7149
  var RubyWhySession = class {
package/dist/index.js CHANGED
@@ -63,6 +63,17 @@ 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 METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
67
+ function enclosingMethod(node) {
68
+ let current = node.parent;
69
+ while (current !== null) {
70
+ if (METHOD_TYPES.has(current.type)) {
71
+ return current;
72
+ }
73
+ current = current.parent;
74
+ }
75
+ return null;
76
+ }
66
77
  var INCLUDE_CALL = "include";
67
78
  var PREPEND_CALL = "prepend";
68
79
  var ARGUMENT_LIST_TYPE = "argument_list";
@@ -390,8 +401,11 @@ var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
390
401
  function resolveValues(db, keys) {
391
402
  askResolution(db, keys, "wanted", RUBY_PROGRAM);
392
403
  }
404
+ function resolveEnvSites(db, sites) {
405
+ askResolution(db, sites, "wantedEnvSite", RUBY_PROGRAM);
406
+ }
393
407
  function resolvedFunctions(db, key) {
394
- return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
408
+ return db.lookup("wantedResolves", 0, key).map((row) => String(row[1]));
395
409
  }
396
410
  function writtenValueOf(db, key) {
397
411
  resolveValues(db, [key]);
@@ -1270,7 +1284,6 @@ function emitCandidates(emitter, key, group) {
1270
1284
  add(emitter, "writesAllStated", key);
1271
1285
  }
1272
1286
  }
1273
- var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1274
1287
  var INITIALIZE_METHOD = "initialize";
1275
1288
  function receiverKeyOf(filePath, method, classKey) {
1276
1289
  return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
@@ -1318,7 +1331,7 @@ function emitClassFacts(emitter, cls) {
1318
1331
  left.text,
1319
1332
  valueKey(emitter, right)
1320
1333
  );
1321
- emitExpressionFacts(emitter, statement);
1334
+ emitExpressionFacts(within, statement);
1322
1335
  }
1323
1336
  continue;
1324
1337
  }
@@ -2399,7 +2412,6 @@ var evaluators = /* @__PURE__ */ new WeakMap();
2399
2412
  var contexts = /* @__PURE__ */ new WeakMap();
2400
2413
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2401
2414
  var filesByTree = /* @__PURE__ */ new WeakMap();
2402
- var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2403
2415
  function bindEvaluator(db, nodes) {
2404
2416
  for (const entry of nodes.files) {
2405
2417
  filesByTree.set(entry.root.tree, entry.file);
@@ -2453,7 +2465,7 @@ function stringValueOf(node, db, bindings) {
2453
2465
  function methodDefinitionsIn(file, root) {
2454
2466
  const found = /* @__PURE__ */ new Map();
2455
2467
  const visit = (node) => {
2456
- if (METHOD_TYPES3.has(node.type)) {
2468
+ if (METHOD_TYPES.has(node.type)) {
2457
2469
  found.set(nodeId(file, node), node);
2458
2470
  }
2459
2471
  for (const child of node.namedChildren) {
@@ -2478,16 +2490,6 @@ function evaluatorFor(node, db) {
2478
2490
  }
2479
2491
  return local;
2480
2492
  }
2481
- function enclosingMethod(node) {
2482
- let current = node.parent;
2483
- while (current !== null) {
2484
- if (METHOD_TYPES3.has(current.type)) {
2485
- return current;
2486
- }
2487
- current = current.parent;
2488
- }
2489
- return null;
2490
- }
2491
2493
  function contextOver(db, nodes) {
2492
2494
  const filesByRoot = /* @__PURE__ */ new Map();
2493
2495
  const rootsByFile = /* @__PURE__ */ new Map();
@@ -4035,19 +4037,155 @@ function returnBranch(range, effects) {
4035
4037
  import { runtimeConfigBinding } from "@suss/behavioral-ir";
4036
4038
  import { SKIP_CHILDREN as SKIP_CHILDREN2, walkDescendants as walkDescendants2 } from "@suss/extractor";
4037
4039
  var RUBY_ENV_RECOGNITION = "ruby-env";
4040
+ var ENV_DEFAULTED = "rbEnvDefaulted";
4038
4041
  var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
4039
- function envReadEffects(root) {
4040
- const effects = [];
4042
+ function envReadEffects(root, facts) {
4043
+ const helpers = helperFactsOf(facts);
4044
+ const slots = [];
4041
4045
  walkDescendants2(root, null, {
4042
4046
  at: (node) => {
4043
4047
  const read = envReadAt(node);
4044
4048
  if (read !== null) {
4045
- effects.push(configReadEffect(read));
4049
+ slots.push({ read });
4050
+ return;
4051
+ }
4052
+ if (helpers !== null && node.type === "call") {
4053
+ slots.push({ call: node });
4046
4054
  }
4047
4055
  },
4048
4056
  into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? SKIP_CHILDREN2 : null
4049
4057
  });
4050
- return effects;
4058
+ const calls = slots.flatMap((slot) => "call" in slot ? [slot.call] : []);
4059
+ const throughHelpers = helpers === null ? new NodeMap() : helperReads(calls, helpers);
4060
+ return slots.flatMap((slot) => {
4061
+ if ("read" in slot) {
4062
+ return [configReadEffect(slot.read)];
4063
+ }
4064
+ return (throughHelpers.get(slot.call) ?? []).map(configReadEffect);
4065
+ });
4066
+ }
4067
+ function helperFactsOf(facts) {
4068
+ if (facts === void 0) {
4069
+ return null;
4070
+ }
4071
+ const named2 = namedParameters(facts.db);
4072
+ if (named2.size === 0) {
4073
+ return null;
4074
+ }
4075
+ return { ...facts, named: named2 };
4076
+ }
4077
+ var namedByDb = /* @__PURE__ */ new WeakMap();
4078
+ function namedParameters(db) {
4079
+ const memo = namedByDb.get(db);
4080
+ if (memo !== void 0) {
4081
+ return memo;
4082
+ }
4083
+ const named2 = /* @__PURE__ */ new Map();
4084
+ namedByDb.set(db, named2);
4085
+ const sites = db.facts("readsEnvNamed").map((row) => String(row[0]));
4086
+ if (sites.length === 0) {
4087
+ return named2;
4088
+ }
4089
+ resolveEnvSites(db, sites);
4090
+ for (const row of db.facts("wantedParamNamesEnv")) {
4091
+ const found = named2.get(String(row[0]));
4092
+ if (found === void 0) {
4093
+ named2.set(String(row[0]), [String(row[1])]);
4094
+ continue;
4095
+ }
4096
+ found.push(String(row[1]));
4097
+ }
4098
+ return named2;
4099
+ }
4100
+ function emitEnvNameFacts(db, file, root) {
4101
+ for (const site of envNameSites(root)) {
4102
+ const key = nodeId(file, site.read);
4103
+ db.add("readsEnvNamed", [
4104
+ key,
4105
+ readKey(file, site.name, enclosingMethod(site.name))
4106
+ ]);
4107
+ if (site.defaulted) {
4108
+ db.add(ENV_DEFAULTED, [key]);
4109
+ }
4110
+ }
4111
+ }
4112
+ function envNameSites(root) {
4113
+ const sites = [];
4114
+ walkDescendants2(root, null, {
4115
+ at: (node) => {
4116
+ const site = envSiteAt(node);
4117
+ if (site !== null && stringLiteralValue(site.name) === null) {
4118
+ sites.push(site);
4119
+ }
4120
+ },
4121
+ into: () => null
4122
+ });
4123
+ return sites;
4124
+ }
4125
+ function helperReads(calls, facts) {
4126
+ const { db, file, named: named2 } = facts;
4127
+ const callees = new NodeMap();
4128
+ for (const call of calls) {
4129
+ const key = calleeKeyOf(file, call, enclosingMethod(call));
4130
+ if (key !== null) {
4131
+ callees.set(call, key);
4132
+ }
4133
+ }
4134
+ const reads = new NodeMap();
4135
+ if (callees.size === 0) {
4136
+ return reads;
4137
+ }
4138
+ resolveValues(
4139
+ db,
4140
+ [...callees].map(([, callee]) => callee)
4141
+ );
4142
+ for (const [call, callee] of callees) {
4143
+ const args = namingArguments(db, named2, call, callee);
4144
+ if (args.length === 0) {
4145
+ continue;
4146
+ }
4147
+ const found = readsAtCall(db, call, args);
4148
+ if (found.length > 0) {
4149
+ reads.set(call, found);
4150
+ }
4151
+ }
4152
+ return reads;
4153
+ }
4154
+ function namingArguments(db, named2, call, callee) {
4155
+ const { positional, keyword } = readCallArgs(field(call, "arguments"));
4156
+ const found = [];
4157
+ for (const func of resolvedFunctions(db, callee)) {
4158
+ for (const row of db.lookup("paramOf", 0, func)) {
4159
+ const sites = named2.get(String(row[2]));
4160
+ const argument = positional[Number(row[1])];
4161
+ if (sites !== void 0 && argument !== void 0) {
4162
+ found.push({ sites, argument });
4163
+ }
4164
+ }
4165
+ for (const row of db.lookup("paramNamed", 0, func)) {
4166
+ const sites = named2.get(String(row[2]));
4167
+ const argument = keyword[String(row[1])];
4168
+ if (sites !== void 0 && argument !== void 0) {
4169
+ found.push({ sites, argument });
4170
+ }
4171
+ }
4172
+ }
4173
+ return found;
4174
+ }
4175
+ function readsAtCall(db, call, args) {
4176
+ const defaultedAtCall = isDefaultedAt(call);
4177
+ const byName = /* @__PURE__ */ new Map();
4178
+ for (const { sites, argument } of args) {
4179
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
4180
+ if (name === null) {
4181
+ continue;
4182
+ }
4183
+ for (const site of sites) {
4184
+ const defaulted = defaultedAtCall || db.lookup(ENV_DEFAULTED, 0, site).length > 0;
4185
+ byName.set(name, (byName.get(name) ?? true) && defaulted);
4186
+ }
4187
+ }
4188
+ return [...byName].map(([name, defaulted]) => ({ name, defaulted }));
4051
4189
  }
4052
4190
  function configReadEffect(read) {
4053
4191
  return {
@@ -4066,15 +4204,23 @@ function configReadEffect(read) {
4066
4204
  };
4067
4205
  }
4068
4206
  function envReadAt(node) {
4207
+ const site = envSiteAt(node);
4208
+ if (site === null) {
4209
+ return null;
4210
+ }
4211
+ const name = stringLiteralValue(site.name);
4212
+ return name === null ? null : { name, defaulted: site.defaulted };
4213
+ }
4214
+ function envSiteAt(node) {
4069
4215
  if (node.type === "element_reference") {
4070
- return elementRead(node);
4216
+ return elementSite(node);
4071
4217
  }
4072
4218
  if (node.type === "call") {
4073
- return fetchRead(node);
4219
+ return fetchSite(node);
4074
4220
  }
4075
4221
  return null;
4076
4222
  }
4077
- function elementRead(node) {
4223
+ function elementSite(node) {
4078
4224
  const object = field(node, "object");
4079
4225
  if (object === null || !isEnv(object) || isAssignedTo(node)) {
4080
4226
  return null;
@@ -4082,24 +4228,23 @@ function elementRead(node) {
4082
4228
  const index = node.namedChildren.find(
4083
4229
  (child) => child !== null && child.id !== object.id
4084
4230
  );
4085
- const name = index === void 0 ? null : stringLiteralValue(index);
4086
- if (name === null) {
4231
+ if (index === void 0) {
4087
4232
  return null;
4088
4233
  }
4089
- return { name, defaulted: isDefaultedAt(node) };
4234
+ return { read: node, name: index, defaulted: isDefaultedAt(node) };
4090
4235
  }
4091
- function fetchRead(node) {
4236
+ function fetchSite(node) {
4092
4237
  const receiver = field(node, "receiver");
4093
4238
  if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
4094
4239
  return null;
4095
4240
  }
4096
4241
  const { positional } = readCallArgs(field(node, "arguments"));
4097
- const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
4098
- if (name === null) {
4242
+ const name = positional[0];
4243
+ if (name === void 0) {
4099
4244
  return null;
4100
4245
  }
4101
4246
  const hasDefault = positional.length > 1 || field(node, "block") !== null;
4102
- return { name, defaulted: hasDefault || isDefaultedAt(node) };
4247
+ return { read: node, name, defaulted: hasDefault || isDefaultedAt(node) };
4103
4248
  }
4104
4249
  function isEnv(node) {
4105
4250
  if (node.type === "constant") {
@@ -5082,8 +5227,12 @@ function bodyOfMethod(method, file, bodyRead = {}) {
5082
5227
  bodyRead.facts
5083
5228
  );
5084
5229
  const storage = bodyRead.storage;
5230
+ const facts = bodyRead.facts;
5085
5231
  const extra = [
5086
- ...envReadEffects(method),
5232
+ ...envReadEffects(
5233
+ method,
5234
+ facts === void 0 ? void 0 : { db: facts, file }
5235
+ ),
5087
5236
  ...storage === void 0 ? [] : storageEffects(callsUnder2(method), file, storage, method)
5088
5237
  ];
5089
5238
  return {
@@ -6489,7 +6638,7 @@ function parameterName(param) {
6489
6638
  }
6490
6639
 
6491
6640
  // src/reach/context.ts
6492
- var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6641
+ var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6493
6642
  async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
6494
6643
  const blocksByQualifiedName = /* @__PURE__ */ new Map();
6495
6644
  const classes = [];
@@ -6500,7 +6649,7 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
6500
6649
  classes.push({ file, info });
6501
6650
  classNames.set(nodeId(file, info.node), info.qualifiedName);
6502
6651
  for (const statement of namedChildren(info.bodyNode)) {
6503
- if (METHOD_TYPES4.has(statement.type)) {
6652
+ if (METHOD_TYPES3.has(statement.type)) {
6504
6653
  classOfMethod.set(nodeId(file, statement), info.qualifiedName);
6505
6654
  }
6506
6655
  }
@@ -6634,6 +6783,7 @@ function factsForFile(options) {
6634
6783
  const db = new Database2();
6635
6784
  const bodyBlocks = bodyBlocksIn(options.packs);
6636
6785
  emitValueFacts(db, options.file, options.root, bodyBlocks);
6786
+ emitEnvNameFacts(db, options.file, options.root);
6637
6787
  emitConstantBindings(db, [
6638
6788
  collectFileConstants(
6639
6789
  options.file,
@@ -6705,6 +6855,7 @@ async function extractRubyProject(options) {
6705
6855
  }
6706
6856
  parsed.push({ file, root });
6707
6857
  emitValueFacts(db, file, root, bodyBlocks);
6858
+ emitEnvNameFacts(db, file, root);
6708
6859
  for (const [key, method] of methodDefinitionsIn(file, root)) {
6709
6860
  definitions.set(key, method);
6710
6861
  }
@@ -6782,7 +6933,10 @@ async function extractRubyProject(options) {
6782
6933
  });
6783
6934
  }
6784
6935
  }
6785
- const loadTimeReads = timer.time("discover", () => envReadEffects(root));
6936
+ const loadTimeReads = timer.time(
6937
+ "discover",
6938
+ () => envReadEffects(root, { db, file })
6939
+ );
6786
6940
  if (loadTimeReads.length > 0) {
6787
6941
  const summary = timer.time(
6788
6942
  "summarize",
@@ -6945,20 +7099,9 @@ function findRubyFiles(root) {
6945
7099
 
6946
7100
  // src/facts/why.ts
6947
7101
  var WITNESS_RULES = [...RESOLUTION_RULES, ...RUBY_RULES];
6948
- var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6949
7102
  function namedChildrenOf(node) {
6950
7103
  return node.namedChildren.filter((child) => child !== null);
6951
7104
  }
6952
- function enclosingMethod2(node) {
6953
- let current = node.parent;
6954
- while (current !== null) {
6955
- if (METHOD_TYPES5.has(current.type)) {
6956
- return current;
6957
- }
6958
- current = current.parent;
6959
- }
6960
- return null;
6961
- }
6962
7105
  function indexFile(file, root, locations) {
6963
7106
  const walk = (node) => {
6964
7107
  locations.set(nodeId(file, node), { file, node });
@@ -6994,7 +7137,7 @@ function width(node) {
6994
7137
  return node.endIndex - node.startIndex;
6995
7138
  }
6996
7139
  function askedKey(value) {
6997
- const enclosing = enclosingMethod2(value.node);
7140
+ const enclosing = enclosingMethod(value.node);
6998
7141
  return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
6999
7142
  }
7000
7143
  var RubyWhySession = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/adapter-ruby",
3
- "version": "0.31.2",
3
+ "version": "0.32.0",
4
4
  "type": "module",
5
5
  "description": "Read a Ruby codebase with suss and check what each Rails action or graphql-ruby resolver returns against the code that calls it.",
6
6
  "keywords": [
@@ -49,12 +49,12 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@suss/behavioral-ir": "0.31.2",
53
- "@suss/datalog": "0.31.2",
54
- "@suss/extractor": "0.31.2",
55
- "@suss/ir-core": "0.31.2",
56
- "@suss/resolution": "0.31.2",
57
- "@suss/values": "0.31.2",
52
+ "@suss/behavioral-ir": "0.32.0",
53
+ "@suss/datalog": "0.32.0",
54
+ "@suss/extractor": "0.32.0",
55
+ "@suss/ir-core": "0.32.0",
56
+ "@suss/resolution": "0.32.0",
57
+ "@suss/values": "0.32.0",
58
58
  "web-tree-sitter": "^0.26.11"
59
59
  },
60
60
  "devDependencies": {