@suss/adapter-ruby 0.31.2 → 0.33.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/README.md +5 -4
- package/dist/index.cjs +1050 -187
- package/dist/index.d.cts +127 -5
- package/dist/index.d.ts +127 -5
- package/dist/index.js +1039 -178
- package/package.json +8 -7
package/dist/index.cjs
CHANGED
|
@@ -64,6 +64,8 @@ __export(index_exports, {
|
|
|
64
64
|
resolveConstantFile: () => resolveConstantFile,
|
|
65
65
|
rubyStubEvidence: () => rubyStubEvidence,
|
|
66
66
|
runStatements: () => runStatements,
|
|
67
|
+
storageClaims: () => storageClaims,
|
|
68
|
+
storageEffects: () => storageEffects,
|
|
67
69
|
stringLiteralValue: () => stringLiteralValue,
|
|
68
70
|
stringValueOf: () => stringValueOf,
|
|
69
71
|
symbolValue: () => symbolValue,
|
|
@@ -143,7 +145,25 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
|
143
145
|
"module",
|
|
144
146
|
"singleton_class"
|
|
145
147
|
]);
|
|
148
|
+
var PROGRAM_TYPE = "program";
|
|
149
|
+
var MODULE_SCOPE_STOPS = /* @__PURE__ */ new Set([
|
|
150
|
+
...OWN_BODY_TYPES,
|
|
151
|
+
"block",
|
|
152
|
+
"do_block"
|
|
153
|
+
]);
|
|
146
154
|
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
155
|
+
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
156
|
+
var LAMBDA_TYPE = "lambda";
|
|
157
|
+
function enclosingDefinition(node) {
|
|
158
|
+
let current = node.parent;
|
|
159
|
+
while (current !== null) {
|
|
160
|
+
if (METHOD_TYPES.has(current.type) || current.type === LAMBDA_TYPE) {
|
|
161
|
+
return current;
|
|
162
|
+
}
|
|
163
|
+
current = current.parent;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
147
167
|
var INCLUDE_CALL = "include";
|
|
148
168
|
var PREPEND_CALL = "prepend";
|
|
149
169
|
var ARGUMENT_LIST_TYPE = "argument_list";
|
|
@@ -460,8 +480,14 @@ var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
|
|
|
460
480
|
function resolveValues(db, keys) {
|
|
461
481
|
(0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
|
|
462
482
|
}
|
|
483
|
+
function resolveEnvObjects(db, objects) {
|
|
484
|
+
(0, import_resolution.askResolution)(db, objects, "wantedEnvObject", RUBY_PROGRAM);
|
|
485
|
+
}
|
|
463
486
|
function resolvedFunctions(db, key) {
|
|
464
|
-
return
|
|
487
|
+
return [
|
|
488
|
+
...db.lookup("wantedResolves", 0, key),
|
|
489
|
+
...db.lookup("wantedGivesBack", 0, key)
|
|
490
|
+
].map((row) => String(row[1]));
|
|
465
491
|
}
|
|
466
492
|
function writtenValueOf(db, key) {
|
|
467
493
|
resolveValues(db, [key]);
|
|
@@ -561,12 +587,15 @@ var BINDS_A_NAME = {
|
|
|
561
587
|
PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
|
|
562
588
|
)
|
|
563
589
|
};
|
|
564
|
-
|
|
590
|
+
var DESCENDS_EVERYWHERE = /* @__PURE__ */ new Set();
|
|
591
|
+
function localNamesIn(definitionNode, stops = DESCENDS_EVERYWHERE) {
|
|
565
592
|
const names = /* @__PURE__ */ new Set();
|
|
566
593
|
const visit = (node) => {
|
|
567
594
|
BINDS_A_NAME[node.type]?.(node, names);
|
|
568
595
|
for (const child of bodyStatements(node)) {
|
|
569
|
-
|
|
596
|
+
if (!stops.has(child.type)) {
|
|
597
|
+
visit(child);
|
|
598
|
+
}
|
|
570
599
|
}
|
|
571
600
|
};
|
|
572
601
|
visit(definitionNode);
|
|
@@ -579,11 +608,12 @@ function spellsAName(node) {
|
|
|
579
608
|
}
|
|
580
609
|
return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
|
|
581
610
|
}
|
|
611
|
+
var KEYWORD_LITERALS = /* @__PURE__ */ new Set(["__FILE__", "__LINE__", "__ENCODING__"]);
|
|
582
612
|
function isBareMethodCall(node, locals) {
|
|
583
613
|
if (node.type !== "identifier" || locals.has(node.text)) {
|
|
584
614
|
return false;
|
|
585
615
|
}
|
|
586
|
-
return !spellsAName(node);
|
|
616
|
+
return !KEYWORD_LITERALS.has(node.text) && !spellsAName(node);
|
|
587
617
|
}
|
|
588
618
|
|
|
589
619
|
// src/facts/locals.ts
|
|
@@ -734,25 +764,46 @@ function namesOfFile(tree) {
|
|
|
734
764
|
cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
|
|
735
765
|
return cache.file;
|
|
736
766
|
}
|
|
767
|
+
function scopesAround(enclosing) {
|
|
768
|
+
const chain = [];
|
|
769
|
+
let current = enclosing;
|
|
770
|
+
while (current !== null && current.type === LAMBDA_TYPE) {
|
|
771
|
+
chain.push(current);
|
|
772
|
+
current = enclosingDefinition(current);
|
|
773
|
+
}
|
|
774
|
+
chain.push(current);
|
|
775
|
+
return chain;
|
|
776
|
+
}
|
|
777
|
+
var namesOfScope = (scope, node) => scope === null ? namesOfFile(node.tree) : namesOfMethod(scope);
|
|
737
778
|
function ownerOfName(node, name, enclosing) {
|
|
738
779
|
if (node.type === "constant") {
|
|
739
780
|
return null;
|
|
740
781
|
}
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
782
|
+
for (const scope of scopesAround(enclosing)) {
|
|
783
|
+
const names = namesOfScope(scope, node);
|
|
784
|
+
if (names.blockParams.has(name)) {
|
|
785
|
+
const block = blockDeclaring(node, name);
|
|
786
|
+
if (block !== null) {
|
|
787
|
+
return block;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (scope !== null && names.locals.has(name)) {
|
|
791
|
+
return scope;
|
|
746
792
|
}
|
|
747
793
|
}
|
|
748
|
-
return
|
|
794
|
+
return null;
|
|
749
795
|
}
|
|
750
796
|
function isLocalName(node, name, enclosing) {
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
797
|
+
for (const scope of scopesAround(enclosing)) {
|
|
798
|
+
const names = namesOfScope(scope, node);
|
|
799
|
+
if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
|
|
800
|
+
return true;
|
|
801
|
+
}
|
|
802
|
+
if (names.locals.has(name)) {
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
754
805
|
}
|
|
755
|
-
return
|
|
806
|
+
return false;
|
|
756
807
|
}
|
|
757
808
|
function blockDeclaring(node, name) {
|
|
758
809
|
let current = node.parent;
|
|
@@ -943,19 +994,33 @@ function calleeKeyOf(filePath, node, enclosing) {
|
|
|
943
994
|
}
|
|
944
995
|
return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
|
|
945
996
|
}
|
|
997
|
+
function invokedKeyOf(filePath, node, enclosing) {
|
|
998
|
+
const invoked = invokedValueOf(node);
|
|
999
|
+
return invoked === null ? null : readKey(filePath, invoked, enclosing);
|
|
1000
|
+
}
|
|
946
1001
|
function emitCall(emitter, call) {
|
|
947
|
-
|
|
1002
|
+
const callKey = nodeId(emitter.filePath, call);
|
|
1003
|
+
add(emitter, "writtenValue", callKey);
|
|
1004
|
+
const invoked = invokedValueOf(call);
|
|
1005
|
+
if (invoked !== null) {
|
|
1006
|
+
add(emitter, "call", callKey, valueKey(emitter, invoked));
|
|
1007
|
+
}
|
|
1008
|
+
if (!emitMessageSent(emitter, call, callKey) && invoked === null) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
emitCallArguments(emitter, call, callKey);
|
|
1012
|
+
}
|
|
1013
|
+
function emitMessageSent(emitter, call, callKey) {
|
|
948
1014
|
const method = call.type === "identifier" ? call : field(call, "method");
|
|
949
1015
|
const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
|
|
950
1016
|
if (method === null || calleeKey === null) {
|
|
951
|
-
return;
|
|
1017
|
+
return false;
|
|
952
1018
|
}
|
|
953
|
-
const receiver = field(call, "receiver");
|
|
954
|
-
const callKey = nodeId(emitter.filePath, call);
|
|
955
1019
|
add(emitter, "call", callKey, calleeKey);
|
|
956
1020
|
if (!emitter.insideMethod) {
|
|
957
1021
|
add(emitter, "callOutsideMethod", callKey);
|
|
958
1022
|
}
|
|
1023
|
+
const receiver = field(call, "receiver");
|
|
959
1024
|
if (receiver !== null) {
|
|
960
1025
|
add(
|
|
961
1026
|
emitter,
|
|
@@ -967,6 +1032,21 @@ function emitCall(emitter, call) {
|
|
|
967
1032
|
} else if (emitter.selfKey !== null) {
|
|
968
1033
|
add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
|
|
969
1034
|
}
|
|
1035
|
+
return true;
|
|
1036
|
+
}
|
|
1037
|
+
var INVOKE_METHOD = "call";
|
|
1038
|
+
function invokedValueOf(call) {
|
|
1039
|
+
if (call.type !== "call") {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
const receiver = field(call, "receiver");
|
|
1043
|
+
if (receiver === null) {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
const method = field(call, "method");
|
|
1047
|
+
return method === null || method.text === INVOKE_METHOD ? receiver : null;
|
|
1048
|
+
}
|
|
1049
|
+
function emitCallArguments(emitter, call, callKey) {
|
|
970
1050
|
const args = field(call, "arguments");
|
|
971
1051
|
let position = 0;
|
|
972
1052
|
for (const argument of args === null ? [] : children(args)) {
|
|
@@ -1086,12 +1166,58 @@ function walkExpressions(node, emitter, visit, turns = []) {
|
|
|
1086
1166
|
);
|
|
1087
1167
|
}
|
|
1088
1168
|
}
|
|
1169
|
+
var WRITTEN_KEY_TYPES = /* @__PURE__ */ new Set([
|
|
1170
|
+
"string",
|
|
1171
|
+
"integer",
|
|
1172
|
+
"simple_symbol",
|
|
1173
|
+
"bare_string",
|
|
1174
|
+
"bare_symbol"
|
|
1175
|
+
]);
|
|
1176
|
+
function emitKeyedRead(emitter, site, container, key) {
|
|
1177
|
+
if (WRITTEN_KEY_TYPES.has(key.type)) {
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
add(
|
|
1181
|
+
emitter,
|
|
1182
|
+
"readsKeyed",
|
|
1183
|
+
nodeId(emitter.filePath, site),
|
|
1184
|
+
valueKey(emitter, container),
|
|
1185
|
+
valueKey(emitter, key)
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
function emitKeyedElement(emitter, node) {
|
|
1189
|
+
const object = field(node, "object");
|
|
1190
|
+
if (object === null || isWriteTarget(node)) {
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const index = node.namedChildren.find(
|
|
1194
|
+
(child) => child !== null && child.id !== object.id
|
|
1195
|
+
);
|
|
1196
|
+
if (index !== void 0) {
|
|
1197
|
+
emitKeyedRead(emitter, node, object, index);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
function emitKeyedFetch(emitter, call) {
|
|
1201
|
+
const receiver = field(call, "receiver");
|
|
1202
|
+
if (receiver === null || field(call, "method")?.text !== "fetch") {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const { positional } = readCallArgs(field(call, "arguments"));
|
|
1206
|
+
const key = positional[0];
|
|
1207
|
+
if (key !== void 0) {
|
|
1208
|
+
emitKeyedRead(emitter, call, receiver, key);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1089
1211
|
function emitExpressionFacts(emitter, node) {
|
|
1090
1212
|
emitDynamicDefinition(emitter, node, []);
|
|
1091
1213
|
walkExpressions(node, emitter, (child, turns) => {
|
|
1092
1214
|
emitDynamicDefinition(emitter, child, turns);
|
|
1093
1215
|
if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
|
|
1094
1216
|
emitCall(emitter, child);
|
|
1217
|
+
emitKeyedFetch(emitter, child);
|
|
1218
|
+
}
|
|
1219
|
+
if (child.type === "element_reference") {
|
|
1220
|
+
emitKeyedElement(emitter, child);
|
|
1095
1221
|
}
|
|
1096
1222
|
if (ARRAY_TYPES.has(child.type)) {
|
|
1097
1223
|
emitArray(emitter, child);
|
|
@@ -1215,6 +1341,25 @@ function assignedValueOf(node) {
|
|
|
1215
1341
|
}
|
|
1216
1342
|
return field(node, "right");
|
|
1217
1343
|
}
|
|
1344
|
+
function emitLambdasIn(emitter, body) {
|
|
1345
|
+
for (const child of children(body)) {
|
|
1346
|
+
if (child.type === LAMBDA_TYPE) {
|
|
1347
|
+
emitMethodFacts(emitter, child);
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
1353
|
+
emitLambdasIn(emitter, child);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
function definitionBody(definition) {
|
|
1357
|
+
const body = field(definition, "body");
|
|
1358
|
+
if (body === null || definition.type !== LAMBDA_TYPE) {
|
|
1359
|
+
return body;
|
|
1360
|
+
}
|
|
1361
|
+
return field(body, "body");
|
|
1362
|
+
}
|
|
1218
1363
|
function emitMethodFacts(emitter, method) {
|
|
1219
1364
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1220
1365
|
add(emitter, "func", funcKey);
|
|
@@ -1229,7 +1374,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1229
1374
|
position += 1;
|
|
1230
1375
|
}
|
|
1231
1376
|
const inside = { ...emitter, enclosing: method };
|
|
1232
|
-
const body =
|
|
1377
|
+
const body = definitionBody(method);
|
|
1233
1378
|
if (body === null) {
|
|
1234
1379
|
return funcKey;
|
|
1235
1380
|
}
|
|
@@ -1261,6 +1406,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1261
1406
|
}
|
|
1262
1407
|
emitExpressionFacts(inside, body);
|
|
1263
1408
|
emitScopeWrites(inside, method, body);
|
|
1409
|
+
emitLambdasIn(inside, body);
|
|
1264
1410
|
return funcKey;
|
|
1265
1411
|
}
|
|
1266
1412
|
function isConstruction(value) {
|
|
@@ -1340,7 +1486,6 @@ function emitCandidates(emitter, key, group) {
|
|
|
1340
1486
|
add(emitter, "writesAllStated", key);
|
|
1341
1487
|
}
|
|
1342
1488
|
}
|
|
1343
|
-
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1344
1489
|
var INITIALIZE_METHOD = "initialize";
|
|
1345
1490
|
function receiverKeyOf(filePath, method, classKey) {
|
|
1346
1491
|
return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
|
|
@@ -1388,7 +1533,7 @@ function emitClassFacts(emitter, cls) {
|
|
|
1388
1533
|
left.text,
|
|
1389
1534
|
valueKey(emitter, right)
|
|
1390
1535
|
);
|
|
1391
|
-
emitExpressionFacts(
|
|
1536
|
+
emitExpressionFacts(within, statement);
|
|
1392
1537
|
}
|
|
1393
1538
|
continue;
|
|
1394
1539
|
}
|
|
@@ -1421,12 +1566,15 @@ function emitClassFacts(emitter, cls) {
|
|
|
1421
1566
|
add(emitter, "initializes", classKey, funcKey);
|
|
1422
1567
|
}
|
|
1423
1568
|
}
|
|
1424
|
-
const
|
|
1425
|
-
if (
|
|
1426
|
-
emitInstanceStores(within, funcKey,
|
|
1569
|
+
const methodBody2 = field(statement, "body");
|
|
1570
|
+
if (methodBody2 !== null) {
|
|
1571
|
+
emitInstanceStores(within, funcKey, methodBody2, stored);
|
|
1427
1572
|
}
|
|
1428
1573
|
}
|
|
1429
1574
|
emitInstanceWrites(within, classKey, collected);
|
|
1575
|
+
if (body !== null) {
|
|
1576
|
+
emitLambdasIn(within, body);
|
|
1577
|
+
}
|
|
1430
1578
|
return classKey;
|
|
1431
1579
|
}
|
|
1432
1580
|
function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
@@ -1462,6 +1610,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
|
1462
1610
|
walk(root);
|
|
1463
1611
|
emitExpressionFacts(emitter, root);
|
|
1464
1612
|
emitScopeWrites(emitter, null, root);
|
|
1613
|
+
emitLambdasIn(emitter, root);
|
|
1465
1614
|
}
|
|
1466
1615
|
|
|
1467
1616
|
// src/values/lowering.ts
|
|
@@ -2451,7 +2600,6 @@ var evaluators = /* @__PURE__ */ new WeakMap();
|
|
|
2451
2600
|
var contexts = /* @__PURE__ */ new WeakMap();
|
|
2452
2601
|
var withoutFacts = /* @__PURE__ */ new WeakMap();
|
|
2453
2602
|
var filesByTree = /* @__PURE__ */ new WeakMap();
|
|
2454
|
-
var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
2455
2603
|
function bindEvaluator(db, nodes) {
|
|
2456
2604
|
for (const entry of nodes.files) {
|
|
2457
2605
|
filesByTree.set(entry.root.tree, entry.file);
|
|
@@ -2464,6 +2612,24 @@ function writtenNodeOf(node, db, site) {
|
|
|
2464
2612
|
const context = db === void 0 ? void 0 : contexts.get(db);
|
|
2465
2613
|
return context === void 0 ? null : context.writtenTo(node, site);
|
|
2466
2614
|
}
|
|
2615
|
+
function askWrittenValues(nodes, db) {
|
|
2616
|
+
if (db === void 0 || !contexts.has(db)) {
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2620
|
+
for (const node of nodes) {
|
|
2621
|
+
const file = filesByTree.get(node.tree);
|
|
2622
|
+
if (file !== void 0) {
|
|
2623
|
+
keys.add(readKey(file, node, enclosingDefinition(node)));
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
if (keys.size === 0) {
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
const asked = [...keys];
|
|
2630
|
+
resolveValues(db, asked);
|
|
2631
|
+
(0, import_resolution4.writtenValuesByKey)(db, asked, (more) => resolveValues(db, more));
|
|
2632
|
+
}
|
|
2467
2633
|
function evaluatedValue(node, db, bindings, site) {
|
|
2468
2634
|
const evaluator = evaluatorFor(node, db);
|
|
2469
2635
|
if (bindings === void 0) {
|
|
@@ -2505,7 +2671,7 @@ function stringValueOf(node, db, bindings) {
|
|
|
2505
2671
|
function methodDefinitionsIn(file, root) {
|
|
2506
2672
|
const found = /* @__PURE__ */ new Map();
|
|
2507
2673
|
const visit = (node) => {
|
|
2508
|
-
if (
|
|
2674
|
+
if (METHOD_TYPES.has(node.type)) {
|
|
2509
2675
|
found.set(nodeId(file, node), node);
|
|
2510
2676
|
}
|
|
2511
2677
|
for (const child of node.namedChildren) {
|
|
@@ -2530,16 +2696,6 @@ function evaluatorFor(node, db) {
|
|
|
2530
2696
|
}
|
|
2531
2697
|
return local;
|
|
2532
2698
|
}
|
|
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
2699
|
function contextOver(db, nodes) {
|
|
2544
2700
|
const filesByRoot = /* @__PURE__ */ new Map();
|
|
2545
2701
|
const rootsByFile = /* @__PURE__ */ new Map();
|
|
@@ -2554,7 +2710,7 @@ function contextOver(db, nodes) {
|
|
|
2554
2710
|
if (file === null) {
|
|
2555
2711
|
return null;
|
|
2556
2712
|
}
|
|
2557
|
-
const key = readKey(file, node,
|
|
2713
|
+
const key = readKey(file, node, enclosingDefinition(node));
|
|
2558
2714
|
const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
|
|
2559
2715
|
return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
|
|
2560
2716
|
},
|
|
@@ -2564,7 +2720,7 @@ function contextOver(db, nodes) {
|
|
|
2564
2720
|
if (file === null || method === null) {
|
|
2565
2721
|
return null;
|
|
2566
2722
|
}
|
|
2567
|
-
const key = field(call, "receiver") === null ? readKey(file, method,
|
|
2723
|
+
const key = field(call, "receiver") === null ? readKey(file, method, enclosingDefinition(call)) : nodeId(file, method);
|
|
2568
2724
|
resolveValues(db, [key]);
|
|
2569
2725
|
const resolved = resolvedFunctions(db, key);
|
|
2570
2726
|
const settled = resolved.length === 1 ? resolved[0] : void 0;
|
|
@@ -2716,8 +2872,8 @@ function qualifyConstantRef(node, nesting) {
|
|
|
2716
2872
|
return compoundName(node);
|
|
2717
2873
|
}
|
|
2718
2874
|
if (node.type === "constant") {
|
|
2719
|
-
const
|
|
2720
|
-
return
|
|
2875
|
+
const innermost2 = nesting[0] ?? null;
|
|
2876
|
+
return innermost2 === null ? node.text : `${innermost2}::${node.text}`;
|
|
2721
2877
|
}
|
|
2722
2878
|
return null;
|
|
2723
2879
|
}
|
|
@@ -2774,9 +2930,9 @@ function ownIdentity(nameNode, outerNesting) {
|
|
|
2774
2930
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
2775
2931
|
}
|
|
2776
2932
|
function definedName(nameNode, outerNesting) {
|
|
2777
|
-
const
|
|
2778
|
-
if (nameNode.type === "scope_resolution" &&
|
|
2779
|
-
return `${
|
|
2933
|
+
const innermost2 = outerNesting[0] ?? null;
|
|
2934
|
+
if (nameNode.type === "scope_resolution" && innermost2 !== null && !nameNode.text.startsWith("::")) {
|
|
2935
|
+
return `${innermost2}::${nameNode.text}`;
|
|
2780
2936
|
}
|
|
2781
2937
|
return qualifyConstantRef(nameNode, outerNesting);
|
|
2782
2938
|
}
|
|
@@ -3400,24 +3556,38 @@ function isCall(node, locals, inherited) {
|
|
|
3400
3556
|
}
|
|
3401
3557
|
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
3402
3558
|
}
|
|
3403
|
-
function collectCalls(node,
|
|
3559
|
+
function collectCalls(node, read, inherited, found) {
|
|
3404
3560
|
for (const child of children2(node)) {
|
|
3405
|
-
if (
|
|
3561
|
+
if (read.stops.has(child.type)) {
|
|
3406
3562
|
continue;
|
|
3407
3563
|
}
|
|
3408
|
-
if (isCall(child, locals, inherited)) {
|
|
3564
|
+
if (isCall(child, read.locals, inherited)) {
|
|
3409
3565
|
found.push(child);
|
|
3410
3566
|
}
|
|
3411
|
-
collectCalls(child,
|
|
3567
|
+
collectCalls(child, read, inherited, found);
|
|
3412
3568
|
}
|
|
3413
3569
|
return found;
|
|
3414
3570
|
}
|
|
3415
|
-
function
|
|
3571
|
+
function methodBody(definitionNode) {
|
|
3416
3572
|
const body = field(definitionNode, "body");
|
|
3417
3573
|
if (body === null) {
|
|
3418
|
-
return
|
|
3574
|
+
return null;
|
|
3419
3575
|
}
|
|
3420
|
-
return
|
|
3576
|
+
return {
|
|
3577
|
+
body,
|
|
3578
|
+
locals: localNamesIn(definitionNode),
|
|
3579
|
+
stops: OWN_BODY_TYPES
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
function moduleScopeBody(root) {
|
|
3583
|
+
return {
|
|
3584
|
+
body: root,
|
|
3585
|
+
locals: localNamesIn(root, MODULE_SCOPE_STOPS),
|
|
3586
|
+
stops: MODULE_SCOPE_STOPS
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3589
|
+
function bodyCalls(read, inherited = NO_INHERITED_METHODS) {
|
|
3590
|
+
return collectCalls(read.body, read, inherited, []);
|
|
3421
3591
|
}
|
|
3422
3592
|
function calleeText(call) {
|
|
3423
3593
|
const receiver = field(call, "receiver");
|
|
@@ -3461,14 +3631,23 @@ function enclosingStatement(call, body) {
|
|
|
3461
3631
|
return current;
|
|
3462
3632
|
}
|
|
3463
3633
|
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
|
|
3464
|
-
const
|
|
3465
|
-
if (
|
|
3634
|
+
const read = methodBody(definitionNode);
|
|
3635
|
+
if (read === null) {
|
|
3466
3636
|
return [];
|
|
3467
3637
|
}
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3638
|
+
return effectsOfBody(read, inherited, keepsArglessCall, facts);
|
|
3639
|
+
}
|
|
3640
|
+
function moduleScopeInvocationEffects(root, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
|
|
3641
|
+
return effectsOfBody(
|
|
3642
|
+
moduleScopeBody(root),
|
|
3643
|
+
inherited,
|
|
3644
|
+
keepsArglessCall,
|
|
3645
|
+
facts
|
|
3471
3646
|
);
|
|
3647
|
+
}
|
|
3648
|
+
function effectsOfBody(read, inherited, keepsArglessCall, facts) {
|
|
3649
|
+
const body = read.body;
|
|
3650
|
+
const calls = callsReported(bodyCalls(read, inherited), keepsArglessCall);
|
|
3472
3651
|
if (calls.length === 0) {
|
|
3473
3652
|
return [];
|
|
3474
3653
|
}
|
|
@@ -3874,6 +4053,57 @@ function clientCallUnits(root, pack, pattern, options) {
|
|
|
3874
4053
|
}
|
|
3875
4054
|
return units;
|
|
3876
4055
|
}
|
|
4056
|
+
function askClientCallReads(root, patterns, options) {
|
|
4057
|
+
const facts = options.facts;
|
|
4058
|
+
if (facts === void 0 || patterns.length === 0) {
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
const calls = [];
|
|
4062
|
+
for (const method of methodDefinitions(root)) {
|
|
4063
|
+
calls.push(...callsUnder(method));
|
|
4064
|
+
}
|
|
4065
|
+
if (patterns.some((pattern) => (pattern.receiverBuilders ?? []).length > 0)) {
|
|
4066
|
+
askWrittenValues(receiversOf(calls), facts);
|
|
4067
|
+
}
|
|
4068
|
+
askWrittenValues(requestArguments(calls, patterns, options), facts);
|
|
4069
|
+
}
|
|
4070
|
+
function receiversOf(calls) {
|
|
4071
|
+
const found = [];
|
|
4072
|
+
for (const call of calls) {
|
|
4073
|
+
const receiver = field(call, "receiver");
|
|
4074
|
+
if (receiver !== null) {
|
|
4075
|
+
found.push(receiver);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
return found;
|
|
4079
|
+
}
|
|
4080
|
+
function requestArguments(calls, patterns, options) {
|
|
4081
|
+
const found = [];
|
|
4082
|
+
for (const call of calls) {
|
|
4083
|
+
const receiver = field(call, "receiver");
|
|
4084
|
+
const called = field(call, "method")?.text;
|
|
4085
|
+
if (receiver === null || called === void 0) {
|
|
4086
|
+
continue;
|
|
4087
|
+
}
|
|
4088
|
+
for (const pattern of patterns) {
|
|
4089
|
+
if (!isLibraryReceiver(receiver, pattern, options)) {
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
const argument = requestArgument(call, called, pattern);
|
|
4093
|
+
if (argument !== void 0) {
|
|
4094
|
+
found.push(argument);
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
return found;
|
|
4099
|
+
}
|
|
4100
|
+
function requestArgument(call, called, pattern) {
|
|
4101
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
4102
|
+
if (pattern.verbMethodNames[called] !== void 0) {
|
|
4103
|
+
return urlNodeIn(args, pattern);
|
|
4104
|
+
}
|
|
4105
|
+
return pattern.requestObject?.attribute === called ? args.positional[0] : void 0;
|
|
4106
|
+
}
|
|
3877
4107
|
function methodDefinitions(node, found = []) {
|
|
3878
4108
|
for (const child of node.namedChildren) {
|
|
3879
4109
|
if (child === null) {
|
|
@@ -3998,9 +4228,11 @@ function builderCallBehind(receiver, pattern, facts) {
|
|
|
3998
4228
|
return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
|
|
3999
4229
|
}
|
|
4000
4230
|
function urlIn(args, pattern, options, site) {
|
|
4231
|
+
return pathAt(urlNodeIn(args, pattern), options, site);
|
|
4232
|
+
}
|
|
4233
|
+
function urlNodeIn(args, pattern) {
|
|
4001
4234
|
const keyword = pattern.url.keyword;
|
|
4002
|
-
|
|
4003
|
-
return pathAt(written, options, site);
|
|
4235
|
+
return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
|
|
4004
4236
|
}
|
|
4005
4237
|
function pathAt(written, options, site) {
|
|
4006
4238
|
if (written === void 0) {
|
|
@@ -4077,19 +4309,161 @@ function returnBranch(range, effects) {
|
|
|
4077
4309
|
var import_behavioral_ir2 = require("@suss/behavioral-ir");
|
|
4078
4310
|
var import_extractor4 = require("@suss/extractor");
|
|
4079
4311
|
var RUBY_ENV_RECOGNITION = "ruby-env";
|
|
4312
|
+
var ENV_DEFAULTED = "rbEnvDefaulted";
|
|
4080
4313
|
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
|
|
4081
|
-
function envReadEffects(root) {
|
|
4082
|
-
const
|
|
4314
|
+
function envReadEffects(root, facts) {
|
|
4315
|
+
const helpers = helperFactsOf(facts);
|
|
4316
|
+
const slots = [];
|
|
4083
4317
|
(0, import_extractor4.walkDescendants)(root, null, {
|
|
4084
4318
|
at: (node) => {
|
|
4085
4319
|
const read = envReadAt(node);
|
|
4086
4320
|
if (read !== null) {
|
|
4087
|
-
|
|
4321
|
+
slots.push({ read });
|
|
4322
|
+
return;
|
|
4323
|
+
}
|
|
4324
|
+
if (helpers !== null && node.type === "call") {
|
|
4325
|
+
slots.push({ call: node });
|
|
4088
4326
|
}
|
|
4089
4327
|
},
|
|
4090
4328
|
into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? import_extractor4.SKIP_CHILDREN : null
|
|
4091
4329
|
});
|
|
4092
|
-
|
|
4330
|
+
const calls = slots.flatMap((slot) => "call" in slot ? [slot.call] : []);
|
|
4331
|
+
const throughHelpers = helpers === null ? new NodeMap() : helperReads(calls, helpers);
|
|
4332
|
+
return slots.flatMap((slot) => {
|
|
4333
|
+
if ("read" in slot) {
|
|
4334
|
+
return [configReadEffect(slot.read)];
|
|
4335
|
+
}
|
|
4336
|
+
return (throughHelpers.get(slot.call) ?? []).map(configReadEffect);
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
function helperFactsOf(facts) {
|
|
4340
|
+
if (facts === void 0) {
|
|
4341
|
+
return null;
|
|
4342
|
+
}
|
|
4343
|
+
const named2 = namedParameters(facts.db);
|
|
4344
|
+
if (named2.size === 0) {
|
|
4345
|
+
return null;
|
|
4346
|
+
}
|
|
4347
|
+
return { ...facts, named: named2 };
|
|
4348
|
+
}
|
|
4349
|
+
var namedByDb = /* @__PURE__ */ new WeakMap();
|
|
4350
|
+
function namedParameters(db) {
|
|
4351
|
+
const memo = namedByDb.get(db);
|
|
4352
|
+
if (memo !== void 0) {
|
|
4353
|
+
return memo;
|
|
4354
|
+
}
|
|
4355
|
+
const named2 = /* @__PURE__ */ new Map();
|
|
4356
|
+
namedByDb.set(db, named2);
|
|
4357
|
+
const objects = db.facts("environmentObject").map((row) => String(row[0]));
|
|
4358
|
+
if (objects.length === 0) {
|
|
4359
|
+
return named2;
|
|
4360
|
+
}
|
|
4361
|
+
resolveEnvObjects(db, objects);
|
|
4362
|
+
for (const row of db.facts("wantedParamNamesEnv")) {
|
|
4363
|
+
const found = named2.get(String(row[0]));
|
|
4364
|
+
if (found === void 0) {
|
|
4365
|
+
named2.set(String(row[0]), [String(row[1])]);
|
|
4366
|
+
continue;
|
|
4367
|
+
}
|
|
4368
|
+
found.push(String(row[1]));
|
|
4369
|
+
}
|
|
4370
|
+
return named2;
|
|
4371
|
+
}
|
|
4372
|
+
function emitEnvFacts(db, file, root) {
|
|
4373
|
+
(0, import_extractor4.walkDescendants)(root, null, {
|
|
4374
|
+
at: (node) => {
|
|
4375
|
+
if (isEnv(node) && handsOnward(node)) {
|
|
4376
|
+
db.add("environmentObject", [
|
|
4377
|
+
readKey(file, node, enclosingDefinition(node))
|
|
4378
|
+
]);
|
|
4379
|
+
}
|
|
4380
|
+
const site = envSiteAt(node);
|
|
4381
|
+
if (site !== null && site.defaulted && stringLiteralValue(site.name) === null) {
|
|
4382
|
+
db.add(ENV_DEFAULTED, [nodeId(file, site.read)]);
|
|
4383
|
+
}
|
|
4384
|
+
},
|
|
4385
|
+
into: () => null
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
function handsOnward(node) {
|
|
4389
|
+
const parent = node.parent;
|
|
4390
|
+
if (parent === null) {
|
|
4391
|
+
return true;
|
|
4392
|
+
}
|
|
4393
|
+
if (parent.type === "element_reference" || parent.type === "call") {
|
|
4394
|
+
const site = envSiteAt(parent);
|
|
4395
|
+
return site === null || stringLiteralValue(site.name) === null;
|
|
4396
|
+
}
|
|
4397
|
+
return true;
|
|
4398
|
+
}
|
|
4399
|
+
function helperReads(calls, facts) {
|
|
4400
|
+
const { db, file, named: named2 } = facts;
|
|
4401
|
+
const callees = new NodeMap();
|
|
4402
|
+
for (const call of calls) {
|
|
4403
|
+
const enclosing = enclosingDefinition(call);
|
|
4404
|
+
const keys = [
|
|
4405
|
+
calleeKeyOf(file, call, enclosing),
|
|
4406
|
+
invokedKeyOf(file, call, enclosing)
|
|
4407
|
+
].filter((key) => key !== null);
|
|
4408
|
+
if (keys.length > 0) {
|
|
4409
|
+
callees.set(call, keys);
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
const reads = new NodeMap();
|
|
4413
|
+
if (callees.size === 0) {
|
|
4414
|
+
return reads;
|
|
4415
|
+
}
|
|
4416
|
+
resolveValues(
|
|
4417
|
+
db,
|
|
4418
|
+
[...callees].flatMap(([, keys]) => keys)
|
|
4419
|
+
);
|
|
4420
|
+
for (const [call, callee] of callees) {
|
|
4421
|
+
const args = namingArguments(db, named2, call, callee);
|
|
4422
|
+
if (args.length === 0) {
|
|
4423
|
+
continue;
|
|
4424
|
+
}
|
|
4425
|
+
const found = readsAtCall(db, call, args);
|
|
4426
|
+
if (found.length > 0) {
|
|
4427
|
+
reads.set(call, found);
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
return reads;
|
|
4431
|
+
}
|
|
4432
|
+
function namingArguments(db, named2, call, callees) {
|
|
4433
|
+
const { positional, keyword } = readCallArgs(field(call, "arguments"));
|
|
4434
|
+
const found = [];
|
|
4435
|
+
for (const func of callees.flatMap((key) => resolvedFunctions(db, key))) {
|
|
4436
|
+
for (const row of db.lookup("paramOf", 0, func)) {
|
|
4437
|
+
const sites = named2.get(String(row[2]));
|
|
4438
|
+
const argument = positional[Number(row[1])];
|
|
4439
|
+
if (sites !== void 0 && argument !== void 0) {
|
|
4440
|
+
found.push({ sites, argument });
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
for (const row of db.lookup("paramNamed", 0, func)) {
|
|
4444
|
+
const sites = named2.get(String(row[2]));
|
|
4445
|
+
const argument = keyword[String(row[1])];
|
|
4446
|
+
if (sites !== void 0 && argument !== void 0) {
|
|
4447
|
+
found.push({ sites, argument });
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
return found;
|
|
4452
|
+
}
|
|
4453
|
+
function readsAtCall(db, call, args) {
|
|
4454
|
+
const defaultedAtCall = isDefaultedAt(call);
|
|
4455
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4456
|
+
for (const { sites, argument } of args) {
|
|
4457
|
+
const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
|
|
4458
|
+
if (name === null) {
|
|
4459
|
+
continue;
|
|
4460
|
+
}
|
|
4461
|
+
for (const site of sites) {
|
|
4462
|
+
const defaulted = defaultedAtCall || db.lookup(ENV_DEFAULTED, 0, site).length > 0;
|
|
4463
|
+
byName.set(name, (byName.get(name) ?? true) && defaulted);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
return [...byName].map(([name, defaulted]) => ({ name, defaulted }));
|
|
4093
4467
|
}
|
|
4094
4468
|
function configReadEffect(read) {
|
|
4095
4469
|
return {
|
|
@@ -4108,15 +4482,23 @@ function configReadEffect(read) {
|
|
|
4108
4482
|
};
|
|
4109
4483
|
}
|
|
4110
4484
|
function envReadAt(node) {
|
|
4485
|
+
const site = envSiteAt(node);
|
|
4486
|
+
if (site === null) {
|
|
4487
|
+
return null;
|
|
4488
|
+
}
|
|
4489
|
+
const name = stringLiteralValue(site.name);
|
|
4490
|
+
return name === null ? null : { name, defaulted: site.defaulted };
|
|
4491
|
+
}
|
|
4492
|
+
function envSiteAt(node) {
|
|
4111
4493
|
if (node.type === "element_reference") {
|
|
4112
|
-
return
|
|
4494
|
+
return elementSite(node);
|
|
4113
4495
|
}
|
|
4114
4496
|
if (node.type === "call") {
|
|
4115
|
-
return
|
|
4497
|
+
return fetchSite(node);
|
|
4116
4498
|
}
|
|
4117
4499
|
return null;
|
|
4118
4500
|
}
|
|
4119
|
-
function
|
|
4501
|
+
function elementSite(node) {
|
|
4120
4502
|
const object = field(node, "object");
|
|
4121
4503
|
if (object === null || !isEnv(object) || isAssignedTo(node)) {
|
|
4122
4504
|
return null;
|
|
@@ -4124,24 +4506,23 @@ function elementRead(node) {
|
|
|
4124
4506
|
const index = node.namedChildren.find(
|
|
4125
4507
|
(child) => child !== null && child.id !== object.id
|
|
4126
4508
|
);
|
|
4127
|
-
|
|
4128
|
-
if (name === null) {
|
|
4509
|
+
if (index === void 0) {
|
|
4129
4510
|
return null;
|
|
4130
4511
|
}
|
|
4131
|
-
return { name, defaulted: isDefaultedAt(node) };
|
|
4512
|
+
return { read: node, name: index, defaulted: isDefaultedAt(node) };
|
|
4132
4513
|
}
|
|
4133
|
-
function
|
|
4514
|
+
function fetchSite(node) {
|
|
4134
4515
|
const receiver = field(node, "receiver");
|
|
4135
4516
|
if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
|
|
4136
4517
|
return null;
|
|
4137
4518
|
}
|
|
4138
4519
|
const { positional } = readCallArgs(field(node, "arguments"));
|
|
4139
|
-
const name = positional[0]
|
|
4140
|
-
if (name ===
|
|
4520
|
+
const name = positional[0];
|
|
4521
|
+
if (name === void 0) {
|
|
4141
4522
|
return null;
|
|
4142
4523
|
}
|
|
4143
4524
|
const hasDefault = positional.length > 1 || field(node, "block") !== null;
|
|
4144
|
-
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
4525
|
+
return { read: node, name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
4145
4526
|
}
|
|
4146
4527
|
function isEnv(node) {
|
|
4147
4528
|
if (node.type === "constant") {
|
|
@@ -4315,6 +4696,9 @@ function methodNamesOf(args, filter, facts) {
|
|
|
4315
4696
|
const value = named2 === void 0 ? null : stringValueOf(named2, facts);
|
|
4316
4697
|
return value === null ? [] : [value];
|
|
4317
4698
|
}
|
|
4699
|
+
return symbolArgumentNames(args, facts);
|
|
4700
|
+
}
|
|
4701
|
+
function symbolArgumentNames(args, facts) {
|
|
4318
4702
|
return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
|
|
4319
4703
|
}
|
|
4320
4704
|
function skipsOf(statement, filterName, facts) {
|
|
@@ -4353,19 +4737,315 @@ function actionsUnder(node, facts) {
|
|
|
4353
4737
|
}
|
|
4354
4738
|
|
|
4355
4739
|
// src/storage.ts
|
|
4356
|
-
var
|
|
4740
|
+
var import_ir_core2 = require("@suss/ir-core");
|
|
4741
|
+
var import_resolution7 = require("@suss/resolution");
|
|
4742
|
+
|
|
4743
|
+
// src/baseClass.ts
|
|
4357
4744
|
var import_resolution6 = require("@suss/resolution");
|
|
4358
|
-
function
|
|
4359
|
-
|
|
4745
|
+
function classBehind(facts, file, constant2) {
|
|
4746
|
+
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
4747
|
+
const bound = new Set(
|
|
4748
|
+
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
4749
|
+
);
|
|
4750
|
+
return bound.size === 1 ? [...bound][0] : void 0;
|
|
4751
|
+
}
|
|
4752
|
+
function reachesBase(facts, classKey, bases) {
|
|
4753
|
+
(0, import_resolution6.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4754
|
+
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4360
4755
|
}
|
|
4756
|
+
function enclosingClassKey(node, file) {
|
|
4757
|
+
let current = node.parent;
|
|
4758
|
+
while (current !== null) {
|
|
4759
|
+
if (NESTING_TYPES.has(current.type)) {
|
|
4760
|
+
return nodeId(file, current);
|
|
4761
|
+
}
|
|
4762
|
+
current = current.parent;
|
|
4763
|
+
}
|
|
4764
|
+
return null;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
// src/loaders.ts
|
|
4361
4768
|
function receiverOf(node) {
|
|
4362
4769
|
return node.type === "call" ? field(node, "receiver") : null;
|
|
4363
4770
|
}
|
|
4364
4771
|
function methodOf(node) {
|
|
4365
4772
|
return field(node, "method")?.text ?? "";
|
|
4366
4773
|
}
|
|
4774
|
+
function isLoader(node, loader) {
|
|
4775
|
+
if (node === null) {
|
|
4776
|
+
return false;
|
|
4777
|
+
}
|
|
4778
|
+
if (node.type === "identifier") {
|
|
4779
|
+
return node.text === loader.loader;
|
|
4780
|
+
}
|
|
4781
|
+
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
4782
|
+
}
|
|
4783
|
+
function loaderPick(call, loader) {
|
|
4784
|
+
const method = methodOf(call);
|
|
4785
|
+
const receiver = receiverOf(call);
|
|
4786
|
+
if (receiver === null) {
|
|
4787
|
+
return loader.shortcuts.includes(method) ? call : null;
|
|
4788
|
+
}
|
|
4789
|
+
if (!loader.reads.includes(method) || receiver.type !== "call") {
|
|
4790
|
+
return null;
|
|
4791
|
+
}
|
|
4792
|
+
const picks = methodOf(receiver) === loader.pick && isLoader(receiverOf(receiver), loader);
|
|
4793
|
+
return picks ? receiver : null;
|
|
4794
|
+
}
|
|
4795
|
+
function pickedSource(call, loader) {
|
|
4796
|
+
const source = loader.source;
|
|
4797
|
+
const pick = source === void 0 ? null : loaderPick(call, loader);
|
|
4798
|
+
if (source === void 0 || pick === null || methodOf(pick) !== loader.pick) {
|
|
4799
|
+
return null;
|
|
4800
|
+
}
|
|
4801
|
+
const args = field(pick, "arguments");
|
|
4802
|
+
const given = args === null ? void 0 : args.namedChildren[source.at];
|
|
4803
|
+
return given?.type === "constant" || given?.type === "scope_resolution" ? given : null;
|
|
4804
|
+
}
|
|
4805
|
+
|
|
4806
|
+
// src/rawSql.ts
|
|
4807
|
+
var import_ir_core = require("@suss/ir-core");
|
|
4808
|
+
var import_sql = require("@suss/sql");
|
|
4809
|
+
var import_values12 = require("@suss/values");
|
|
4810
|
+
var MOST_HOPS = 8;
|
|
4811
|
+
var NOWHERE = { scope: null, container: null };
|
|
4812
|
+
function rawSqlEffects(call, options) {
|
|
4813
|
+
for (const pattern of options.patterns) {
|
|
4814
|
+
const effects = patternEffects(call, pattern, options);
|
|
4815
|
+
if (effects.length > 0) {
|
|
4816
|
+
return effects;
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
return [];
|
|
4820
|
+
}
|
|
4821
|
+
function patternEffects(call, pattern, options) {
|
|
4822
|
+
const receiver = field(call, "receiver");
|
|
4823
|
+
if (receiver === null) {
|
|
4824
|
+
return [];
|
|
4825
|
+
}
|
|
4826
|
+
const address = addressBehind(receiver, pattern, options, 0);
|
|
4827
|
+
if (address === null) {
|
|
4828
|
+
return [];
|
|
4829
|
+
}
|
|
4830
|
+
const method = field(call, "method")?.text ?? "";
|
|
4831
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
4832
|
+
const place = pattern.statements?.[method];
|
|
4833
|
+
if (place !== void 0) {
|
|
4834
|
+
const statement = statementAt(
|
|
4835
|
+
args,
|
|
4836
|
+
place,
|
|
4837
|
+
options.facts,
|
|
4838
|
+
pattern.bindPlaceholder
|
|
4839
|
+
);
|
|
4840
|
+
return statementEffects(call, statement, address, pattern);
|
|
4841
|
+
}
|
|
4842
|
+
return rowCallEffects(call, args, address, pattern, options);
|
|
4843
|
+
}
|
|
4844
|
+
function statementEffects(call, statement, address, store) {
|
|
4845
|
+
if (statement === null) {
|
|
4846
|
+
return [];
|
|
4847
|
+
}
|
|
4848
|
+
return (0, import_sql.readSqlAccess)(statement, { dialect: store.dialect }).map(
|
|
4849
|
+
(access) => effectOf(
|
|
4850
|
+
call,
|
|
4851
|
+
store,
|
|
4852
|
+
{
|
|
4853
|
+
scope: innermost(access.qualifier) ?? address.scope,
|
|
4854
|
+
container: access.table
|
|
4855
|
+
},
|
|
4856
|
+
{
|
|
4857
|
+
kind: access.kind,
|
|
4858
|
+
fields: access.fields,
|
|
4859
|
+
selector: access.selector
|
|
4860
|
+
}
|
|
4861
|
+
)
|
|
4862
|
+
);
|
|
4863
|
+
}
|
|
4864
|
+
function innermost(qualifier) {
|
|
4865
|
+
return qualifier[qualifier.length - 1] ?? null;
|
|
4866
|
+
}
|
|
4867
|
+
function rowCallEffects(call, args, address, pattern, options) {
|
|
4868
|
+
const method = field(call, "method")?.text ?? "";
|
|
4869
|
+
const rowCall = pattern.rowCalls?.[method];
|
|
4870
|
+
if (rowCall === void 0) {
|
|
4871
|
+
return [];
|
|
4872
|
+
}
|
|
4873
|
+
const given = rowCall.container === void 0 ? null : namedTable(stringAt(args, rowCall.container, options.facts));
|
|
4874
|
+
const reached = {
|
|
4875
|
+
scope: given?.scope ?? address.scope,
|
|
4876
|
+
container: given?.container ?? address.container
|
|
4877
|
+
};
|
|
4878
|
+
if (reached.container === null) {
|
|
4879
|
+
return [];
|
|
4880
|
+
}
|
|
4881
|
+
return [
|
|
4882
|
+
effectOf(call, pattern, reached, {
|
|
4883
|
+
kind: rowCall.kind,
|
|
4884
|
+
fields: [],
|
|
4885
|
+
selector: []
|
|
4886
|
+
})
|
|
4887
|
+
];
|
|
4888
|
+
}
|
|
4889
|
+
function namedTable(name) {
|
|
4890
|
+
const split = name === null ? null : (0, import_sql.splitQualifiedTable)(name);
|
|
4891
|
+
return split === null ? null : { scope: innermost(split.qualifier), container: split.table };
|
|
4892
|
+
}
|
|
4893
|
+
function effectOf(call, store, address, access) {
|
|
4894
|
+
const operation = field(call, "method")?.text ?? "";
|
|
4895
|
+
return {
|
|
4896
|
+
type: "interaction",
|
|
4897
|
+
binding: (0, import_ir_core.storageBinding)({
|
|
4898
|
+
recognition: "ruby-raw-sql",
|
|
4899
|
+
storageSystem: store.storageSystem,
|
|
4900
|
+
scope: address.scope ?? store.scope ?? "default",
|
|
4901
|
+
container: address.container
|
|
4902
|
+
}),
|
|
4903
|
+
callee: call.text,
|
|
4904
|
+
interaction: {
|
|
4905
|
+
class: "storage-access",
|
|
4906
|
+
kind: access.kind,
|
|
4907
|
+
fields: access.fields,
|
|
4908
|
+
operation,
|
|
4909
|
+
...access.selector.length > 0 ? { selector: access.selector } : {}
|
|
4910
|
+
}
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
function addressBehind(receiver, pattern, options, hops) {
|
|
4914
|
+
if (hops > MOST_HOPS) {
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
if (receiver.type !== "call") {
|
|
4918
|
+
const written = writtenNodeOf(receiver, options.facts);
|
|
4919
|
+
if (written !== null && written.id !== receiver.id) {
|
|
4920
|
+
return addressBehind(written, pattern, options, hops + 1);
|
|
4921
|
+
}
|
|
4922
|
+
return receiver.type === "identifier" && buildsOnOwnClass(receiver, receiver.text, pattern, options) ? NOWHERE : null;
|
|
4923
|
+
}
|
|
4924
|
+
const method = field(receiver, "method")?.text ?? "";
|
|
4925
|
+
const inner = field(receiver, "receiver");
|
|
4926
|
+
if (inner === null) {
|
|
4927
|
+
return buildsOnOwnClass(receiver, method, pattern, options) ? NOWHERE : null;
|
|
4928
|
+
}
|
|
4929
|
+
if (namesConstant2(inner, pattern.constantName) || namesSubclass(inner, pattern, options)) {
|
|
4930
|
+
return pattern.clientBuilders.includes(method) ? NOWHERE : null;
|
|
4931
|
+
}
|
|
4932
|
+
const addressing = pattern.addressing?.[method];
|
|
4933
|
+
if (addressing === void 0 || inner === null) {
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4936
|
+
const behind = addressBehind(inner, pattern, options, hops + 1);
|
|
4937
|
+
if (behind === null) {
|
|
4938
|
+
return null;
|
|
4939
|
+
}
|
|
4940
|
+
const named2 = stringAt(
|
|
4941
|
+
readCallArgs(field(receiver, "arguments")),
|
|
4942
|
+
addressing,
|
|
4943
|
+
options.facts
|
|
4944
|
+
);
|
|
4945
|
+
if (addressing.says === "scope") {
|
|
4946
|
+
return { scope: named2 ?? behind.scope, container: behind.container };
|
|
4947
|
+
}
|
|
4948
|
+
const table = namedTable(named2);
|
|
4949
|
+
return {
|
|
4950
|
+
scope: table?.scope ?? behind.scope,
|
|
4951
|
+
container: table?.container ?? behind.container
|
|
4952
|
+
};
|
|
4953
|
+
}
|
|
4954
|
+
function namesConstant2(receiver, constantName2) {
|
|
4955
|
+
if (receiver.type === "constant") {
|
|
4956
|
+
return receiver.text === constantName2;
|
|
4957
|
+
}
|
|
4958
|
+
return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
|
|
4959
|
+
}
|
|
4960
|
+
function namesSubclass(receiver, pattern, options) {
|
|
4961
|
+
const bases = pattern.baseClasses ?? [];
|
|
4962
|
+
const facts = options.facts;
|
|
4963
|
+
const file = options.file;
|
|
4964
|
+
if (bases.length === 0 || facts === void 0 || file === void 0 || receiver.type !== "constant" && receiver.type !== "scope_resolution") {
|
|
4965
|
+
return false;
|
|
4966
|
+
}
|
|
4967
|
+
const classKey = classBehind(facts, file, receiver);
|
|
4968
|
+
return classKey !== void 0 && reachesBase(facts, classKey, bases);
|
|
4969
|
+
}
|
|
4970
|
+
function buildsOnOwnClass(node, method, pattern, options) {
|
|
4971
|
+
const bases = pattern.baseClasses ?? [];
|
|
4972
|
+
const facts = options.facts;
|
|
4973
|
+
const file = options.file;
|
|
4974
|
+
if (bases.length === 0 || facts === void 0 || file === void 0 || !pattern.clientBuilders.includes(method)) {
|
|
4975
|
+
return false;
|
|
4976
|
+
}
|
|
4977
|
+
const classKey = enclosingClassKey(node, file);
|
|
4978
|
+
return classKey !== null && reachesBase(facts, classKey, bases);
|
|
4979
|
+
}
|
|
4980
|
+
function argumentAt(args, place) {
|
|
4981
|
+
const keyword = place.keyword;
|
|
4982
|
+
return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[place.at];
|
|
4983
|
+
}
|
|
4984
|
+
function stringAt(args, place, facts) {
|
|
4985
|
+
const written = argumentAt(args, place);
|
|
4986
|
+
if (written === void 0) {
|
|
4987
|
+
return null;
|
|
4988
|
+
}
|
|
4989
|
+
const pieces = (0, import_values12.piecesOf)(evaluatedValue(written, facts));
|
|
4990
|
+
const only = pieces.length === 1 ? pieces[0] : void 0;
|
|
4991
|
+
if (only === void 0 || only.kind !== "text" || only.options.length !== 1) {
|
|
4992
|
+
return null;
|
|
4993
|
+
}
|
|
4994
|
+
return only.options[0] ?? null;
|
|
4995
|
+
}
|
|
4996
|
+
function statementAt(args, place, facts, bindPlaceholder) {
|
|
4997
|
+
const written = argumentAt(args, place);
|
|
4998
|
+
if (written === void 0) {
|
|
4999
|
+
return null;
|
|
5000
|
+
}
|
|
5001
|
+
const parts = literalParts(statementValue(evaluatedValue(written, facts)));
|
|
5002
|
+
if (parts === null) {
|
|
5003
|
+
return null;
|
|
5004
|
+
}
|
|
5005
|
+
const statement = (0, import_sql.sqlFromParts)(bindsSplit(parts, bindPlaceholder));
|
|
5006
|
+
return statement.trim() === "" ? null : statement;
|
|
5007
|
+
}
|
|
5008
|
+
function bindsSplit(parts, bindPlaceholder) {
|
|
5009
|
+
if (bindPlaceholder === void 0 || bindPlaceholder === "") {
|
|
5010
|
+
return [...parts];
|
|
5011
|
+
}
|
|
5012
|
+
return parts.flatMap((part) => part.split(bindPlaceholder));
|
|
5013
|
+
}
|
|
5014
|
+
function statementValue(value) {
|
|
5015
|
+
if (value.kind !== "sequence") {
|
|
5016
|
+
return value;
|
|
5017
|
+
}
|
|
5018
|
+
const first = value.items[0];
|
|
5019
|
+
return first === void 0 ? value : first.value;
|
|
5020
|
+
}
|
|
5021
|
+
function literalParts(value) {
|
|
5022
|
+
if (value.kind !== "string") {
|
|
5023
|
+
return null;
|
|
5024
|
+
}
|
|
5025
|
+
const parts = [""];
|
|
5026
|
+
for (const piece of value.pieces) {
|
|
5027
|
+
const only = piece.kind === "text" ? piece.options : [];
|
|
5028
|
+
if (only.length === 1) {
|
|
5029
|
+
parts[parts.length - 1] += only[0] ?? "";
|
|
5030
|
+
continue;
|
|
5031
|
+
}
|
|
5032
|
+
parts.push("");
|
|
5033
|
+
}
|
|
5034
|
+
return parts;
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5037
|
+
// src/storage.ts
|
|
5038
|
+
function children3(node) {
|
|
5039
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
5040
|
+
}
|
|
5041
|
+
function receiverOf2(node) {
|
|
5042
|
+
return node.type === "call" ? field(node, "receiver") : null;
|
|
5043
|
+
}
|
|
5044
|
+
function methodOf2(node) {
|
|
5045
|
+
return field(node, "method")?.text ?? "";
|
|
5046
|
+
}
|
|
4367
5047
|
function rootConstant(node) {
|
|
4368
|
-
const receiver =
|
|
5048
|
+
const receiver = receiverOf2(node);
|
|
4369
5049
|
if (receiver === null) {
|
|
4370
5050
|
return null;
|
|
4371
5051
|
}
|
|
@@ -4380,17 +5060,6 @@ function isConstant(node) {
|
|
|
4380
5060
|
function constantName(constant2) {
|
|
4381
5061
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
4382
5062
|
}
|
|
4383
|
-
function reachesBase(facts, classKey, bases) {
|
|
4384
|
-
(0, import_resolution6.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4385
|
-
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4386
|
-
}
|
|
4387
|
-
function classBehind(facts, file, constant2) {
|
|
4388
|
-
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
4389
|
-
const bound = new Set(
|
|
4390
|
-
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
4391
|
-
);
|
|
4392
|
-
return bound.size === 1 ? [...bound][0] : void 0;
|
|
4393
|
-
}
|
|
4394
5063
|
function modelPattern(constant2, file, options) {
|
|
4395
5064
|
const classKey = classBehind(options.facts, file, constant2);
|
|
4396
5065
|
if (classKey === void 0) {
|
|
@@ -4405,7 +5074,7 @@ function chainLinks(call) {
|
|
|
4405
5074
|
let current = call;
|
|
4406
5075
|
while (current !== null && current.type === "call") {
|
|
4407
5076
|
links.push(current);
|
|
4408
|
-
current =
|
|
5077
|
+
current = receiverOf2(current);
|
|
4409
5078
|
}
|
|
4410
5079
|
return links;
|
|
4411
5080
|
}
|
|
@@ -4418,9 +5087,17 @@ function kindOfCall(pattern, method) {
|
|
|
4418
5087
|
}
|
|
4419
5088
|
return void 0;
|
|
4420
5089
|
}
|
|
5090
|
+
function libraryCallOf(pattern, method) {
|
|
5091
|
+
const place = pattern.statements?.[method];
|
|
5092
|
+
if (place !== void 0) {
|
|
5093
|
+
return { how: "statement", place };
|
|
5094
|
+
}
|
|
5095
|
+
const kind = kindOfCall(pattern, method);
|
|
5096
|
+
return kind === void 0 ? void 0 : { how: "rows", kind };
|
|
5097
|
+
}
|
|
4421
5098
|
function someLibraryDefines(options, method) {
|
|
4422
5099
|
return options.patterns.some(
|
|
4423
|
-
(pattern) =>
|
|
5100
|
+
(pattern) => libraryCallOf(pattern, method) !== void 0
|
|
4424
5101
|
);
|
|
4425
5102
|
}
|
|
4426
5103
|
function argumentsOf2(call) {
|
|
@@ -4455,7 +5132,7 @@ function hasPositionalArgument(call, facts) {
|
|
|
4455
5132
|
);
|
|
4456
5133
|
}
|
|
4457
5134
|
function columnsAskedFor(call, pattern, facts) {
|
|
4458
|
-
if (!(pattern.columnArguments ?? []).includes(
|
|
5135
|
+
if (!(pattern.columnArguments ?? []).includes(methodOf2(call))) {
|
|
4459
5136
|
return [];
|
|
4460
5137
|
}
|
|
4461
5138
|
return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
|
|
@@ -4465,14 +5142,14 @@ function primaryKeySelector(call, pattern, facts) {
|
|
|
4465
5142
|
if (byPrimaryKey === void 0) {
|
|
4466
5143
|
return [];
|
|
4467
5144
|
}
|
|
4468
|
-
const picks = byPrimaryKey.methods.includes(
|
|
5145
|
+
const picks = byPrimaryKey.methods.includes(methodOf2(call)) && hasPositionalArgument(call, facts);
|
|
4469
5146
|
return picks ? [byPrimaryKey.column] : [];
|
|
4470
5147
|
}
|
|
4471
5148
|
function selectorOf(call, pattern, facts) {
|
|
4472
5149
|
const picked = [];
|
|
4473
5150
|
for (const link of chainLinks(call)) {
|
|
4474
5151
|
picked.push(...primaryKeySelector(link, pattern, facts));
|
|
4475
|
-
if (pattern.reads.includes(
|
|
5152
|
+
if (pattern.reads.includes(methodOf2(link))) {
|
|
4476
5153
|
picked.push(...keywordKeys(link, facts));
|
|
4477
5154
|
}
|
|
4478
5155
|
}
|
|
@@ -4489,10 +5166,10 @@ function fieldsOf(call, pattern, kind, facts) {
|
|
|
4489
5166
|
];
|
|
4490
5167
|
}
|
|
4491
5168
|
function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
4492
|
-
const operation =
|
|
5169
|
+
const operation = methodOf2(call);
|
|
4493
5170
|
return {
|
|
4494
5171
|
type: "interaction",
|
|
4495
|
-
binding: (0,
|
|
5172
|
+
binding: (0, import_ir_core2.storageBinding)({
|
|
4496
5173
|
recognition: "ruby-storage",
|
|
4497
5174
|
storageSystem: pattern.storageSystem,
|
|
4498
5175
|
scope: "default",
|
|
@@ -4509,7 +5186,7 @@ function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
|
4509
5186
|
};
|
|
4510
5187
|
}
|
|
4511
5188
|
function classSettledOn(facts, key) {
|
|
4512
|
-
(0,
|
|
5189
|
+
(0, import_resolution7.askResolution)(facts, [key], "wanted", RUBY_PROGRAM);
|
|
4513
5190
|
const objects = new Set(
|
|
4514
5191
|
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
4515
5192
|
);
|
|
@@ -4541,7 +5218,7 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4541
5218
|
if (constant2 !== null) {
|
|
4542
5219
|
return constantReceiver(constant2, file, options.facts);
|
|
4543
5220
|
}
|
|
4544
|
-
const receiver =
|
|
5221
|
+
const receiver = receiverOf2(call);
|
|
4545
5222
|
if (receiver === null) {
|
|
4546
5223
|
return void 0;
|
|
4547
5224
|
}
|
|
@@ -4549,60 +5226,91 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4549
5226
|
}
|
|
4550
5227
|
function libraryCallIn(call, options) {
|
|
4551
5228
|
return chainLinks(call).find(
|
|
4552
|
-
(link) => someLibraryDefines(options,
|
|
5229
|
+
(link) => someLibraryDefines(options, methodOf2(link))
|
|
4553
5230
|
) ?? null;
|
|
4554
5231
|
}
|
|
5232
|
+
function modelStatementEffects(call, place, pattern, options) {
|
|
5233
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
5234
|
+
return statementEffects(
|
|
5235
|
+
call,
|
|
5236
|
+
statementAt(args, place, options.facts, pattern.bindPlaceholder),
|
|
5237
|
+
NOWHERE,
|
|
5238
|
+
{ storageSystem: pattern.storageSystem, dialect: pattern.storageSystem }
|
|
5239
|
+
);
|
|
5240
|
+
}
|
|
5241
|
+
function callbacksRunBy(facts, classKey, pattern, writeMethod) {
|
|
5242
|
+
const events = pattern.callbacks?.eventOf[writeMethod] ?? [];
|
|
5243
|
+
if (events.length === 0) {
|
|
5244
|
+
return [];
|
|
5245
|
+
}
|
|
5246
|
+
(0, import_resolution7.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
5247
|
+
const found = /* @__PURE__ */ new Map();
|
|
5248
|
+
for (const row of facts.lookup("wantedCallbackMethod", 0, classKey)) {
|
|
5249
|
+
if (events.includes(String(row[1]))) {
|
|
5250
|
+
const key = String(row[3]);
|
|
5251
|
+
found.set(key, { name: String(row[2]), key });
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
return [...found.values()];
|
|
5255
|
+
}
|
|
5256
|
+
function callbackEffect(callback) {
|
|
5257
|
+
return {
|
|
5258
|
+
type: "invocation",
|
|
5259
|
+
callee: callback.name,
|
|
5260
|
+
args: [],
|
|
5261
|
+
async: false
|
|
5262
|
+
};
|
|
5263
|
+
}
|
|
4555
5264
|
function modelCallEffects(call, file, options, enclosing) {
|
|
4556
5265
|
const worked = libraryCallIn(call, options);
|
|
4557
5266
|
if (worked === null) {
|
|
4558
5267
|
return [];
|
|
4559
5268
|
}
|
|
4560
|
-
const method =
|
|
5269
|
+
const method = methodOf2(worked);
|
|
4561
5270
|
const target = receiverClass(worked, file, options, enclosing);
|
|
4562
5271
|
if (target === void 0) {
|
|
4563
5272
|
return [];
|
|
4564
5273
|
}
|
|
4565
5274
|
for (const pattern of options.patterns) {
|
|
4566
|
-
const
|
|
4567
|
-
if (
|
|
5275
|
+
const library = libraryCallOf(pattern, method);
|
|
5276
|
+
if (library === void 0 || !reachesBase(options.facts, target.classKey, pattern.baseClasses)) {
|
|
4568
5277
|
continue;
|
|
4569
5278
|
}
|
|
4570
5279
|
if (projectDeclares(options.facts, target.classKey, method)) {
|
|
4571
5280
|
return [];
|
|
4572
5281
|
}
|
|
5282
|
+
if (library.how === "statement") {
|
|
5283
|
+
return modelStatementEffects(worked, library.place, pattern, options);
|
|
5284
|
+
}
|
|
5285
|
+
const ran = library.kind === "write" ? callbacksRunBy(options.facts, target.classKey, pattern, method) : [];
|
|
4573
5286
|
return [
|
|
4574
5287
|
storageEffect(
|
|
4575
5288
|
worked,
|
|
4576
5289
|
target.container,
|
|
4577
5290
|
pattern,
|
|
4578
|
-
kind,
|
|
5291
|
+
library.kind,
|
|
4579
5292
|
selectorOf(worked, pattern, options.facts),
|
|
4580
|
-
fieldsOf(worked, pattern, kind, options.facts)
|
|
4581
|
-
)
|
|
5293
|
+
fieldsOf(worked, pattern, library.kind, options.facts)
|
|
5294
|
+
),
|
|
5295
|
+
...ran.map(callbackEffect)
|
|
4582
5296
|
];
|
|
4583
5297
|
}
|
|
4584
5298
|
return [];
|
|
4585
5299
|
}
|
|
4586
|
-
function
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
return node.text === loader.loader;
|
|
4592
|
-
}
|
|
4593
|
-
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
4594
|
-
}
|
|
4595
|
-
function loaderPick(call, loader) {
|
|
4596
|
-
const method = methodOf(call);
|
|
4597
|
-
const receiver = receiverOf(call);
|
|
4598
|
-
if (receiver === null) {
|
|
4599
|
-
return loader.shortcuts.includes(method) ? call : null;
|
|
5300
|
+
function callbacksReached(call, file, options, enclosing = null) {
|
|
5301
|
+
const worked = libraryCallIn(call, options);
|
|
5302
|
+
const target = worked === null ? void 0 : receiverClass(worked, file, options, enclosing);
|
|
5303
|
+
if (worked === null || target === void 0) {
|
|
5304
|
+
return [];
|
|
4600
5305
|
}
|
|
4601
|
-
|
|
4602
|
-
|
|
5306
|
+
const method = methodOf2(worked);
|
|
5307
|
+
for (const pattern of options.patterns) {
|
|
5308
|
+
if (libraryCallOf(pattern, method)?.how !== "rows" || kindOfCall(pattern, method) !== "write" || !reachesBase(options.facts, target.classKey, pattern.baseClasses) || projectDeclares(options.facts, target.classKey, method)) {
|
|
5309
|
+
continue;
|
|
5310
|
+
}
|
|
5311
|
+
return callbacksRunBy(options.facts, target.classKey, pattern, method);
|
|
4603
5312
|
}
|
|
4604
|
-
|
|
4605
|
-
return picks ? receiver : null;
|
|
5313
|
+
return [];
|
|
4606
5314
|
}
|
|
4607
5315
|
function loaderCallEffects(call, file, options) {
|
|
4608
5316
|
const effects = [];
|
|
@@ -4628,10 +5336,24 @@ function loaderCallEffects(call, file, options) {
|
|
|
4628
5336
|
}
|
|
4629
5337
|
function effectsOfCall(call, file, options, enclosing) {
|
|
4630
5338
|
const onModel = modelCallEffects(call, file, options, enclosing);
|
|
4631
|
-
|
|
5339
|
+
if (onModel.length > 0) {
|
|
5340
|
+
return onModel;
|
|
5341
|
+
}
|
|
5342
|
+
const throughLoader = loaderCallEffects(call, file, options);
|
|
5343
|
+
if (throughLoader.length > 0) {
|
|
5344
|
+
return throughLoader;
|
|
5345
|
+
}
|
|
5346
|
+
return rawSqlEffects(call, {
|
|
5347
|
+
facts: options.facts,
|
|
5348
|
+
patterns: options.rawSql ?? [],
|
|
5349
|
+
file
|
|
5350
|
+
});
|
|
4632
5351
|
}
|
|
4633
5352
|
function storageClaims(call, file, options, enclosing = null) {
|
|
4634
|
-
return options
|
|
5353
|
+
return saysAnything(options) && effectsOfCall(call, file, options, enclosing).length > 0;
|
|
5354
|
+
}
|
|
5355
|
+
function saysAnything(options) {
|
|
5356
|
+
return options.patterns.length > 0 || (options.rawSql ?? []).length > 0;
|
|
4635
5357
|
}
|
|
4636
5358
|
function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
4637
5359
|
const keys = [];
|
|
@@ -4640,7 +5362,7 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4640
5362
|
if (worked === null || rootConstant(worked) !== null) {
|
|
4641
5363
|
continue;
|
|
4642
5364
|
}
|
|
4643
|
-
const receiver =
|
|
5365
|
+
const receiver = receiverOf2(worked);
|
|
4644
5366
|
if (receiver !== null) {
|
|
4645
5367
|
keys.push(readKey(file, receiver, enclosing));
|
|
4646
5368
|
}
|
|
@@ -4648,18 +5370,18 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4648
5370
|
return keys;
|
|
4649
5371
|
}
|
|
4650
5372
|
function storageEffects(calls, file, options, enclosing = null) {
|
|
4651
|
-
if (options
|
|
5373
|
+
if (!saysAnything(options)) {
|
|
4652
5374
|
return [];
|
|
4653
5375
|
}
|
|
4654
5376
|
const partOfOne = /* @__PURE__ */ new Set();
|
|
4655
5377
|
for (const call of calls) {
|
|
4656
|
-
const receiver =
|
|
5378
|
+
const receiver = receiverOf2(call);
|
|
4657
5379
|
if (receiver !== null) {
|
|
4658
5380
|
partOfOne.add(receiver.id);
|
|
4659
5381
|
}
|
|
4660
5382
|
}
|
|
4661
5383
|
const chains = calls.filter((call) => !partOfOne.has(call.id));
|
|
4662
|
-
(0,
|
|
5384
|
+
(0, import_resolution7.askResolution)(
|
|
4663
5385
|
options.facts,
|
|
4664
5386
|
receiverKeysToAsk(chains, file, options, enclosing),
|
|
4665
5387
|
"wanted",
|
|
@@ -4784,14 +5506,18 @@ async function discoverUnits(root, options) {
|
|
|
4784
5506
|
file: options.absoluteFile ?? options.filePath
|
|
4785
5507
|
}));
|
|
4786
5508
|
const units = [];
|
|
5509
|
+
const clientOptions = {
|
|
5510
|
+
filePath: options.filePath,
|
|
5511
|
+
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
5512
|
+
};
|
|
5513
|
+
askClientCallReads(
|
|
5514
|
+
root,
|
|
5515
|
+
options.packs.flatMap((pack) => pack.clients ?? []),
|
|
5516
|
+
clientOptions
|
|
5517
|
+
);
|
|
4787
5518
|
for (const pack of options.packs) {
|
|
4788
5519
|
for (const pattern of pack.clients ?? []) {
|
|
4789
|
-
units.push(
|
|
4790
|
-
...clientCallUnits(root, pack, pattern, {
|
|
4791
|
-
filePath: options.filePath,
|
|
4792
|
-
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
4793
|
-
})
|
|
4794
|
-
);
|
|
5520
|
+
units.push(...clientCallUnits(root, pack, pattern, clientOptions));
|
|
4795
5521
|
}
|
|
4796
5522
|
}
|
|
4797
5523
|
for (const info of classes) {
|
|
@@ -5124,8 +5850,12 @@ function bodyOfMethod(method, file, bodyRead = {}) {
|
|
|
5124
5850
|
bodyRead.facts
|
|
5125
5851
|
);
|
|
5126
5852
|
const storage = bodyRead.storage;
|
|
5853
|
+
const facts = bodyRead.facts;
|
|
5127
5854
|
const extra = [
|
|
5128
|
-
...envReadEffects(
|
|
5855
|
+
...envReadEffects(
|
|
5856
|
+
method,
|
|
5857
|
+
facts === void 0 ? void 0 : { db: facts, file }
|
|
5858
|
+
),
|
|
5129
5859
|
...storage === void 0 ? [] : storageEffects(callsUnder2(method), file, storage, method)
|
|
5130
5860
|
];
|
|
5131
5861
|
return {
|
|
@@ -5797,7 +6527,7 @@ function emitConstantBindings(db, perFile) {
|
|
|
5797
6527
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
5798
6528
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
5799
6529
|
var import_datalog4 = require("@suss/datalog");
|
|
5800
|
-
var
|
|
6530
|
+
var import_resolution10 = require("@suss/resolution");
|
|
5801
6531
|
|
|
5802
6532
|
// src/parser.ts
|
|
5803
6533
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -5861,7 +6591,7 @@ var import_node_path4 = __toESM(require("path"), 1);
|
|
|
5861
6591
|
var import_behavioral_ir5 = require("@suss/behavioral-ir");
|
|
5862
6592
|
var import_datalog3 = require("@suss/datalog");
|
|
5863
6593
|
var import_extractor10 = require("@suss/extractor");
|
|
5864
|
-
var
|
|
6594
|
+
var import_resolution9 = require("@suss/resolution");
|
|
5865
6595
|
|
|
5866
6596
|
// src/diagnostics.ts
|
|
5867
6597
|
var import_extractor6 = require("@suss/extractor");
|
|
@@ -5878,6 +6608,47 @@ function buildRubyExtractionReport(args) {
|
|
|
5878
6608
|
});
|
|
5879
6609
|
}
|
|
5880
6610
|
|
|
6611
|
+
// src/facts/callbacks.ts
|
|
6612
|
+
function callbacksIn(patterns) {
|
|
6613
|
+
return patterns.flatMap(
|
|
6614
|
+
(pattern) => pattern.callbacks === void 0 ? [] : [pattern.callbacks]
|
|
6615
|
+
);
|
|
6616
|
+
}
|
|
6617
|
+
function emitClassCallbacks(db, file, classNode, declared) {
|
|
6618
|
+
const body = field(classNode, "body");
|
|
6619
|
+
if (body === null || declared.length === 0) {
|
|
6620
|
+
return;
|
|
6621
|
+
}
|
|
6622
|
+
const classKey = nodeId(file, classNode);
|
|
6623
|
+
for (const statement of runStatements(body)) {
|
|
6624
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
6625
|
+
continue;
|
|
6626
|
+
}
|
|
6627
|
+
const called = field(statement, "method")?.text ?? "";
|
|
6628
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
6629
|
+
for (const callbacks of declared) {
|
|
6630
|
+
const events = eventsOf(args, called, callbacks, db);
|
|
6631
|
+
for (const event of events) {
|
|
6632
|
+
for (const method of symbolArgumentNames(args, db)) {
|
|
6633
|
+
db.add("classCallback", [classKey, event, method]);
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6639
|
+
function eventsOf(args, called, callbacks, db) {
|
|
6640
|
+
const covered = callbacks.registeredBy[called];
|
|
6641
|
+
if (covered === void 0) {
|
|
6642
|
+
return [];
|
|
6643
|
+
}
|
|
6644
|
+
const written = args.keyword[callbacks.eventKeyword];
|
|
6645
|
+
const narrowed = written === void 0 ? null : stringValueOf(written, db);
|
|
6646
|
+
if (narrowed === null) {
|
|
6647
|
+
return covered;
|
|
6648
|
+
}
|
|
6649
|
+
return covered.filter((event) => event === narrowed);
|
|
6650
|
+
}
|
|
6651
|
+
|
|
5881
6652
|
// src/facts.ts
|
|
5882
6653
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
5883
6654
|
var import_extractor7 = require("@suss/extractor");
|
|
@@ -5948,7 +6719,7 @@ var import_datalog2 = require("@suss/datalog");
|
|
|
5948
6719
|
var import_extractor8 = require("@suss/extractor");
|
|
5949
6720
|
|
|
5950
6721
|
// src/reach/resolveCallee.ts
|
|
5951
|
-
var
|
|
6722
|
+
var import_resolution8 = require("@suss/resolution");
|
|
5952
6723
|
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
5953
6724
|
var INVOKES_RECEIVER = "call";
|
|
5954
6725
|
var STOP_FOR = {
|
|
@@ -5977,10 +6748,14 @@ function calleeSpellings(calls, ctx) {
|
|
|
5977
6748
|
}
|
|
5978
6749
|
return {
|
|
5979
6750
|
spellingOf,
|
|
5980
|
-
outcomes: (0,
|
|
6751
|
+
outcomes: (0, import_resolution8.calleeOutcomes)(ctx.facts, [...keys], RUBY_PROGRAM)
|
|
5981
6752
|
};
|
|
5982
6753
|
}
|
|
5983
6754
|
function resolveCallee(call, site, ctx, read) {
|
|
6755
|
+
const throughLoader = loaderSourceCallee(call, site, ctx);
|
|
6756
|
+
if (throughLoader !== null) {
|
|
6757
|
+
return throughLoader;
|
|
6758
|
+
}
|
|
5984
6759
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
5985
6760
|
if (spelling.kind === "stopped") {
|
|
5986
6761
|
return spelling;
|
|
@@ -5990,12 +6765,27 @@ function resolveCallee(call, site, ctx, read) {
|
|
|
5990
6765
|
}
|
|
5991
6766
|
return asCallee(spelling, outcomeFor(spelling.key, ctx, read), site, ctx);
|
|
5992
6767
|
}
|
|
6768
|
+
function loaderSourceCallee(call, site, ctx) {
|
|
6769
|
+
for (const loader of ctx.loaders) {
|
|
6770
|
+
const constant2 = pickedSource(call, loader);
|
|
6771
|
+
const source = loader.source;
|
|
6772
|
+
if (constant2 === null || source === void 0) {
|
|
6773
|
+
continue;
|
|
6774
|
+
}
|
|
6775
|
+
const classKey = classBehind(ctx.facts, site.file, constant2);
|
|
6776
|
+
const qualifiedName = classKey === void 0 ? void 0 : ctx.classNames.get(classKey);
|
|
6777
|
+
if (qualifiedName !== void 0) {
|
|
6778
|
+
return methodOnAncestryOf(qualifiedName, source.method, ctx);
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
return null;
|
|
6782
|
+
}
|
|
5993
6783
|
function outcomeFor(key, ctx, read) {
|
|
5994
|
-
return read?.outcomes.get(key) ?? (0,
|
|
6784
|
+
return read?.outcomes.get(key) ?? (0, import_resolution8.calleeOutcomeOf)(ctx.facts, key, RUBY_PROGRAM);
|
|
5995
6785
|
}
|
|
5996
6786
|
function mightReadAsACall(call, site, ctx) {
|
|
5997
6787
|
const spelling = spellingFor(call, site);
|
|
5998
|
-
return spelling.kind === "receiver" && (0,
|
|
6788
|
+
return spelling.kind === "receiver" && (0, import_resolution8.couldBeSettled)(ctx.facts, spelling.key);
|
|
5999
6789
|
}
|
|
6000
6790
|
function readsAsACall(call, site, ctx, read) {
|
|
6001
6791
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
@@ -6171,6 +6961,7 @@ async function reachedFunctions(seeds, options) {
|
|
|
6171
6961
|
const parameterCallsByKey = /* @__PURE__ */ new Map();
|
|
6172
6962
|
const propertyReadsByKey = /* @__PURE__ */ new Map();
|
|
6173
6963
|
const passedPositions = /* @__PURE__ */ new Set();
|
|
6964
|
+
const followedKeys = /* @__PURE__ */ new Set();
|
|
6174
6965
|
for (const seed of seeds) {
|
|
6175
6966
|
seedKeys.add(seed.key);
|
|
6176
6967
|
functionByKey.set(seed.key, {
|
|
@@ -6218,6 +7009,9 @@ async function reachedFunctions(seeds, options) {
|
|
|
6218
7009
|
for (const position of scan.passedPositions) {
|
|
6219
7010
|
passedPositions.add(position);
|
|
6220
7011
|
}
|
|
7012
|
+
if (scan.followed.length > 0) {
|
|
7013
|
+
followedKeys.add(key);
|
|
7014
|
+
}
|
|
6221
7015
|
for (const target of scan.followed) {
|
|
6222
7016
|
const calleeKey = keyOf(target);
|
|
6223
7017
|
if (!functionByKey.has(calleeKey)) {
|
|
@@ -6254,7 +7048,8 @@ async function reachedFunctions(seeds, options) {
|
|
|
6254
7048
|
argTargetsByKey,
|
|
6255
7049
|
parameterCallsByKey,
|
|
6256
7050
|
propertyReadsByKey,
|
|
6257
|
-
passedPositions
|
|
7051
|
+
passedPositions,
|
|
7052
|
+
followedKeys
|
|
6258
7053
|
};
|
|
6259
7054
|
}
|
|
6260
7055
|
function keyOf(target) {
|
|
@@ -6293,13 +7088,9 @@ function methodReferenceSymbol(node) {
|
|
|
6293
7088
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
6294
7089
|
}
|
|
6295
7090
|
function bodyOf2(source, options) {
|
|
6296
|
-
const site =
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
owner: keyOf(source),
|
|
6300
|
-
enclosingQualifiedName: source.enclosingQualifiedName
|
|
6301
|
-
};
|
|
6302
|
-
const written = field(source.node, "body") === null ? [] : bodyCalls(source.node, options.inheritedMethods);
|
|
7091
|
+
const site = siteOf2(source);
|
|
7092
|
+
const read = readableBodyOf(source.node);
|
|
7093
|
+
const written = read === null ? [] : bodyCalls(read, options.inheritedMethods);
|
|
6303
7094
|
const calls = callsReported(
|
|
6304
7095
|
written,
|
|
6305
7096
|
(call) => mightReadAsACall(call, site, options.context)
|
|
@@ -6311,6 +7102,17 @@ function bodyOf2(source, options) {
|
|
|
6311
7102
|
written: calls.map((call) => ({ call, site }))
|
|
6312
7103
|
};
|
|
6313
7104
|
}
|
|
7105
|
+
function readableBodyOf(node) {
|
|
7106
|
+
return node.type === PROGRAM_TYPE ? moduleScopeBody(node) : methodBody(node);
|
|
7107
|
+
}
|
|
7108
|
+
function siteOf2(source) {
|
|
7109
|
+
return {
|
|
7110
|
+
file: source.file,
|
|
7111
|
+
method: source.node.type === PROGRAM_TYPE ? null : source.node,
|
|
7112
|
+
owner: keyOf(source),
|
|
7113
|
+
enclosingQualifiedName: source.enclosingQualifiedName
|
|
7114
|
+
};
|
|
7115
|
+
}
|
|
6314
7116
|
function callsMade(asked, site, ctx, spellings) {
|
|
6315
7117
|
return asked.filter(
|
|
6316
7118
|
(call) => !isArglessReceiverCall(call) || readsAsACall(call, site, ctx, spellings)
|
|
@@ -6318,7 +7120,7 @@ function callsMade(asked, site, ctx, spellings) {
|
|
|
6318
7120
|
}
|
|
6319
7121
|
function scanBody(source, ctx, options, read) {
|
|
6320
7122
|
const displayPathOf = options.displayPathOf;
|
|
6321
|
-
if (
|
|
7123
|
+
if (readableBodyOf(source.node) === null) {
|
|
6322
7124
|
return EMPTY_SCAN;
|
|
6323
7125
|
}
|
|
6324
7126
|
const site = read.site;
|
|
@@ -6361,9 +7163,34 @@ function scanBody(source, ctx, options, read) {
|
|
|
6361
7163
|
}
|
|
6362
7164
|
}
|
|
6363
7165
|
};
|
|
7166
|
+
const followCallback = (name, key) => {
|
|
7167
|
+
const target = ctx.definitions.get(key);
|
|
7168
|
+
if (target === void 0) {
|
|
7169
|
+
return;
|
|
7170
|
+
}
|
|
7171
|
+
const targetKey = keyOf(target);
|
|
7172
|
+
if (!seen.has(targetKey)) {
|
|
7173
|
+
seen.add(targetKey);
|
|
7174
|
+
followed2.push(target);
|
|
7175
|
+
}
|
|
7176
|
+
placements.place(name, {
|
|
7177
|
+
file: displayPathOf(target.file),
|
|
7178
|
+
span: spanOf(target.node)
|
|
7179
|
+
});
|
|
7180
|
+
};
|
|
6364
7181
|
for (const call of calls) {
|
|
6365
7182
|
const callee = calleeText(call);
|
|
6366
7183
|
const outcome = resolveCallee(call, site, ctx, read.spellings);
|
|
7184
|
+
if (options.storage !== void 0) {
|
|
7185
|
+
for (const callback of callbacksReached(
|
|
7186
|
+
call,
|
|
7187
|
+
source.file,
|
|
7188
|
+
options.storage,
|
|
7189
|
+
site.method
|
|
7190
|
+
)) {
|
|
7191
|
+
followCallback(callback.name, callback.key);
|
|
7192
|
+
}
|
|
7193
|
+
}
|
|
6367
7194
|
const placed = outcome.kind === "followed" ? {
|
|
6368
7195
|
file: displayPathOf(outcome.target.file),
|
|
6369
7196
|
span: spanOf(outcome.target.node)
|
|
@@ -6495,8 +7322,8 @@ function parameterName(param) {
|
|
|
6495
7322
|
}
|
|
6496
7323
|
|
|
6497
7324
|
// src/reach/context.ts
|
|
6498
|
-
var
|
|
6499
|
-
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
|
|
7325
|
+
var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
7326
|
+
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map(), loaders = []) {
|
|
6500
7327
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
6501
7328
|
const classes = [];
|
|
6502
7329
|
const classNames = /* @__PURE__ */ new Map();
|
|
@@ -6506,7 +7333,7 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
|
|
|
6506
7333
|
classes.push({ file, info });
|
|
6507
7334
|
classNames.set(nodeId(file, info.node), info.qualifiedName);
|
|
6508
7335
|
for (const statement of namedChildren(info.bodyNode)) {
|
|
6509
|
-
if (
|
|
7336
|
+
if (METHOD_TYPES3.has(statement.type)) {
|
|
6510
7337
|
classOfMethod.set(nodeId(file, statement), info.qualifiedName);
|
|
6511
7338
|
}
|
|
6512
7339
|
}
|
|
@@ -6572,7 +7399,8 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
|
|
|
6572
7399
|
classNames,
|
|
6573
7400
|
definitions,
|
|
6574
7401
|
bodyBlocks,
|
|
6575
|
-
dynamicNames
|
|
7402
|
+
dynamicNames,
|
|
7403
|
+
loaders
|
|
6576
7404
|
};
|
|
6577
7405
|
}
|
|
6578
7406
|
function namedChildren(node) {
|
|
@@ -6640,6 +7468,7 @@ function factsForFile(options) {
|
|
|
6640
7468
|
const db = new import_datalog3.Database();
|
|
6641
7469
|
const bodyBlocks = bodyBlocksIn(options.packs);
|
|
6642
7470
|
emitValueFacts(db, options.file, options.root, bodyBlocks);
|
|
7471
|
+
emitEnvFacts(db, options.file, options.root);
|
|
6643
7472
|
emitConstantBindings(db, [
|
|
6644
7473
|
collectFileConstants(
|
|
6645
7474
|
options.file,
|
|
@@ -6653,9 +7482,22 @@ function factsForFile(options) {
|
|
|
6653
7482
|
files: [{ file: options.file, root: options.root }],
|
|
6654
7483
|
definitions: new Map(methodDefinitionsIn(options.file, options.root))
|
|
6655
7484
|
});
|
|
6656
|
-
(0,
|
|
7485
|
+
(0, import_resolution9.addPackWords)(db, packWordsOf(options.packs));
|
|
6657
7486
|
return db;
|
|
6658
7487
|
}
|
|
7488
|
+
function moduleInitUnit(options) {
|
|
7489
|
+
const raw = (0, import_extractor10.moduleInitStructure)({
|
|
7490
|
+
name: options.name,
|
|
7491
|
+
file: options.file,
|
|
7492
|
+
range: options.range,
|
|
7493
|
+
effects: options.effects
|
|
7494
|
+
});
|
|
7495
|
+
const branch = raw.branches[0];
|
|
7496
|
+
if (branch !== void 0) {
|
|
7497
|
+
branch.effects = options.calls;
|
|
7498
|
+
}
|
|
7499
|
+
return raw;
|
|
7500
|
+
}
|
|
6659
7501
|
function alreadyDiscovered(seen, raw) {
|
|
6660
7502
|
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
6661
7503
|
const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir5.boundaryKey)(raw.boundaryBinding) ?? "";
|
|
@@ -6711,6 +7553,7 @@ async function extractRubyProject(options) {
|
|
|
6711
7553
|
}
|
|
6712
7554
|
parsed.push({ file, root });
|
|
6713
7555
|
emitValueFacts(db, file, root, bodyBlocks);
|
|
7556
|
+
emitEnvFacts(db, file, root);
|
|
6714
7557
|
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
6715
7558
|
definitions.set(key, method);
|
|
6716
7559
|
}
|
|
@@ -6726,7 +7569,16 @@ async function extractRubyProject(options) {
|
|
|
6726
7569
|
emitRequireFacts(db, file, root, known);
|
|
6727
7570
|
}
|
|
6728
7571
|
bindEvaluator(db, { files: parsed, definitions });
|
|
6729
|
-
(0,
|
|
7572
|
+
(0, import_resolution9.addPackWords)(db, packWordsOf(options.packs));
|
|
7573
|
+
const callbacks = callbacksIn(
|
|
7574
|
+
options.packs.flatMap((pack) => pack.storage ?? [])
|
|
7575
|
+
);
|
|
7576
|
+
for (const { file, root } of parsed) {
|
|
7577
|
+
walkDefinitions(
|
|
7578
|
+
root,
|
|
7579
|
+
(info) => emitClassCallbacks(db, file, info.node, callbacks)
|
|
7580
|
+
);
|
|
7581
|
+
}
|
|
6730
7582
|
return readDynamicNames(
|
|
6731
7583
|
db,
|
|
6732
7584
|
new Map(parsed.map(({ file, root }) => [file, root]))
|
|
@@ -6734,11 +7586,17 @@ async function extractRubyProject(options) {
|
|
|
6734
7586
|
});
|
|
6735
7587
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
6736
7588
|
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
6737
|
-
const
|
|
7589
|
+
const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
|
|
7590
|
+
const storage = storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
|
|
7591
|
+
facts: db,
|
|
7592
|
+
patterns: storagePatterns,
|
|
7593
|
+
loaders: loaderPatterns,
|
|
7594
|
+
rawSql: rawSqlPatterns
|
|
7595
|
+
} : void 0;
|
|
6738
7596
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
6739
7597
|
const reachContext = await timer.timeAsync(
|
|
6740
7598
|
"discover",
|
|
6741
|
-
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames)
|
|
7599
|
+
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames, loaderPatterns)
|
|
6742
7600
|
);
|
|
6743
7601
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? import_node_path4.default.relative(options.workspaceRoot, file) : file;
|
|
6744
7602
|
const seeds = [];
|
|
@@ -6788,23 +7646,34 @@ async function extractRubyProject(options) {
|
|
|
6788
7646
|
});
|
|
6789
7647
|
}
|
|
6790
7648
|
}
|
|
6791
|
-
const
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
}
|
|
7649
|
+
const moduleKey = nodeId(file, root);
|
|
7650
|
+
seedKeys.add(moduleKey);
|
|
7651
|
+
seeds.push({
|
|
7652
|
+
key: moduleKey,
|
|
7653
|
+
file,
|
|
7654
|
+
node: root,
|
|
7655
|
+
enclosingQualifiedName: null
|
|
7656
|
+
});
|
|
7657
|
+
const loadTime = timer.time("discover", () => ({
|
|
7658
|
+
reads: envReadEffects(root, { db, file }),
|
|
7659
|
+
calls: moduleScopeInvocationEffects(
|
|
7660
|
+
root,
|
|
7661
|
+
inheritedMethods,
|
|
7662
|
+
EVERY_ARGLESS_CALL,
|
|
7663
|
+
db
|
|
7664
|
+
)
|
|
7665
|
+
}));
|
|
7666
|
+
found.push({
|
|
7667
|
+
raw: moduleInitUnit({
|
|
7668
|
+
name: import_node_path4.default.basename(displayPath),
|
|
7669
|
+
file: displayPath,
|
|
7670
|
+
range: rangeOf(root),
|
|
7671
|
+
effects: loadTime.reads,
|
|
7672
|
+
calls: loadTime.calls
|
|
7673
|
+
}),
|
|
7674
|
+
seedKey: moduleKey,
|
|
7675
|
+
onlyIfItReaches: loadTime.reads.length === 0
|
|
7676
|
+
});
|
|
6808
7677
|
}
|
|
6809
7678
|
for (const pack of options.packs) {
|
|
6810
7679
|
for (const pattern of pack.discovery) {
|
|
@@ -6842,6 +7711,9 @@ async function extractRubyProject(options) {
|
|
|
6842
7711
|
continue;
|
|
6843
7712
|
}
|
|
6844
7713
|
const { raw, seedKey } = entry;
|
|
7714
|
+
if (entry.onlyIfItReaches === true && (seedKey === null || !reached.followedKeys.has(seedKey))) {
|
|
7715
|
+
continue;
|
|
7716
|
+
}
|
|
6845
7717
|
if (seedKey !== null) {
|
|
6846
7718
|
dropPropertyReads(raw, reached.propertyReadsByKey.get(seedKey));
|
|
6847
7719
|
}
|
|
@@ -6950,21 +7822,10 @@ function findRubyFiles(root) {
|
|
|
6950
7822
|
}
|
|
6951
7823
|
|
|
6952
7824
|
// src/facts/why.ts
|
|
6953
|
-
var WITNESS_RULES = [...
|
|
6954
|
-
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
7825
|
+
var WITNESS_RULES = [...import_resolution10.RESOLUTION_RULES, ...RUBY_RULES];
|
|
6955
7826
|
function namedChildrenOf(node) {
|
|
6956
7827
|
return node.namedChildren.filter((child) => child !== null);
|
|
6957
7828
|
}
|
|
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
7829
|
function indexFile(file, root, locations) {
|
|
6969
7830
|
const walk = (node) => {
|
|
6970
7831
|
locations.set(nodeId(file, node), { file, node });
|
|
@@ -7000,7 +7861,7 @@ function width(node) {
|
|
|
7000
7861
|
return node.endIndex - node.startIndex;
|
|
7001
7862
|
}
|
|
7002
7863
|
function askedKey(value) {
|
|
7003
|
-
const enclosing =
|
|
7864
|
+
const enclosing = enclosingDefinition(value.node);
|
|
7004
7865
|
return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
|
|
7005
7866
|
}
|
|
7006
7867
|
var RubyWhySession = class {
|
|
@@ -7075,7 +7936,7 @@ var RubyWhySession = class {
|
|
|
7075
7936
|
* which the caller says in its own words.
|
|
7076
7937
|
*/
|
|
7077
7938
|
explain(value, options = {}) {
|
|
7078
|
-
return (0,
|
|
7939
|
+
return (0, import_resolution10.explainResolvedKey)({
|
|
7079
7940
|
db: this.db,
|
|
7080
7941
|
rules: WITNESS_RULES,
|
|
7081
7942
|
key: askedKey(value),
|
|
@@ -7216,6 +8077,8 @@ async function rubyStubEvidence(options) {
|
|
|
7216
8077
|
resolveConstantFile,
|
|
7217
8078
|
rubyStubEvidence,
|
|
7218
8079
|
runStatements,
|
|
8080
|
+
storageClaims,
|
|
8081
|
+
storageEffects,
|
|
7219
8082
|
stringLiteralValue,
|
|
7220
8083
|
stringValueOf,
|
|
7221
8084
|
symbolValue,
|