@suss/adapter-ruby 0.32.0 → 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 +900 -180
- package/dist/index.d.cts +127 -5
- package/dist/index.d.ts +127 -5
- package/dist/index.js +889 -171
- 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,12 +145,19 @@ 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"]);
|
|
147
155
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
148
|
-
|
|
156
|
+
var LAMBDA_TYPE = "lambda";
|
|
157
|
+
function enclosingDefinition(node) {
|
|
149
158
|
let current = node.parent;
|
|
150
159
|
while (current !== null) {
|
|
151
|
-
if (METHOD_TYPES.has(current.type)) {
|
|
160
|
+
if (METHOD_TYPES.has(current.type) || current.type === LAMBDA_TYPE) {
|
|
152
161
|
return current;
|
|
153
162
|
}
|
|
154
163
|
current = current.parent;
|
|
@@ -471,11 +480,14 @@ var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
|
|
|
471
480
|
function resolveValues(db, keys) {
|
|
472
481
|
(0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
|
|
473
482
|
}
|
|
474
|
-
function
|
|
475
|
-
(0, import_resolution.askResolution)(db,
|
|
483
|
+
function resolveEnvObjects(db, objects) {
|
|
484
|
+
(0, import_resolution.askResolution)(db, objects, "wantedEnvObject", RUBY_PROGRAM);
|
|
476
485
|
}
|
|
477
486
|
function resolvedFunctions(db, key) {
|
|
478
|
-
return
|
|
487
|
+
return [
|
|
488
|
+
...db.lookup("wantedResolves", 0, key),
|
|
489
|
+
...db.lookup("wantedGivesBack", 0, key)
|
|
490
|
+
].map((row) => String(row[1]));
|
|
479
491
|
}
|
|
480
492
|
function writtenValueOf(db, key) {
|
|
481
493
|
resolveValues(db, [key]);
|
|
@@ -575,12 +587,15 @@ var BINDS_A_NAME = {
|
|
|
575
587
|
PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
|
|
576
588
|
)
|
|
577
589
|
};
|
|
578
|
-
|
|
590
|
+
var DESCENDS_EVERYWHERE = /* @__PURE__ */ new Set();
|
|
591
|
+
function localNamesIn(definitionNode, stops = DESCENDS_EVERYWHERE) {
|
|
579
592
|
const names = /* @__PURE__ */ new Set();
|
|
580
593
|
const visit = (node) => {
|
|
581
594
|
BINDS_A_NAME[node.type]?.(node, names);
|
|
582
595
|
for (const child of bodyStatements(node)) {
|
|
583
|
-
|
|
596
|
+
if (!stops.has(child.type)) {
|
|
597
|
+
visit(child);
|
|
598
|
+
}
|
|
584
599
|
}
|
|
585
600
|
};
|
|
586
601
|
visit(definitionNode);
|
|
@@ -593,11 +608,12 @@ function spellsAName(node) {
|
|
|
593
608
|
}
|
|
594
609
|
return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
|
|
595
610
|
}
|
|
611
|
+
var KEYWORD_LITERALS = /* @__PURE__ */ new Set(["__FILE__", "__LINE__", "__ENCODING__"]);
|
|
596
612
|
function isBareMethodCall(node, locals) {
|
|
597
613
|
if (node.type !== "identifier" || locals.has(node.text)) {
|
|
598
614
|
return false;
|
|
599
615
|
}
|
|
600
|
-
return !spellsAName(node);
|
|
616
|
+
return !KEYWORD_LITERALS.has(node.text) && !spellsAName(node);
|
|
601
617
|
}
|
|
602
618
|
|
|
603
619
|
// src/facts/locals.ts
|
|
@@ -748,25 +764,46 @@ function namesOfFile(tree) {
|
|
|
748
764
|
cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
|
|
749
765
|
return cache.file;
|
|
750
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);
|
|
751
778
|
function ownerOfName(node, name, enclosing) {
|
|
752
779
|
if (node.type === "constant") {
|
|
753
780
|
return null;
|
|
754
781
|
}
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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;
|
|
760
792
|
}
|
|
761
793
|
}
|
|
762
|
-
return
|
|
794
|
+
return null;
|
|
763
795
|
}
|
|
764
796
|
function isLocalName(node, name, enclosing) {
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
768
805
|
}
|
|
769
|
-
return
|
|
806
|
+
return false;
|
|
770
807
|
}
|
|
771
808
|
function blockDeclaring(node, name) {
|
|
772
809
|
let current = node.parent;
|
|
@@ -957,19 +994,33 @@ function calleeKeyOf(filePath, node, enclosing) {
|
|
|
957
994
|
}
|
|
958
995
|
return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
|
|
959
996
|
}
|
|
997
|
+
function invokedKeyOf(filePath, node, enclosing) {
|
|
998
|
+
const invoked = invokedValueOf(node);
|
|
999
|
+
return invoked === null ? null : readKey(filePath, invoked, enclosing);
|
|
1000
|
+
}
|
|
960
1001
|
function emitCall(emitter, call) {
|
|
961
|
-
|
|
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) {
|
|
962
1014
|
const method = call.type === "identifier" ? call : field(call, "method");
|
|
963
1015
|
const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
|
|
964
1016
|
if (method === null || calleeKey === null) {
|
|
965
|
-
return;
|
|
1017
|
+
return false;
|
|
966
1018
|
}
|
|
967
|
-
const receiver = field(call, "receiver");
|
|
968
|
-
const callKey = nodeId(emitter.filePath, call);
|
|
969
1019
|
add(emitter, "call", callKey, calleeKey);
|
|
970
1020
|
if (!emitter.insideMethod) {
|
|
971
1021
|
add(emitter, "callOutsideMethod", callKey);
|
|
972
1022
|
}
|
|
1023
|
+
const receiver = field(call, "receiver");
|
|
973
1024
|
if (receiver !== null) {
|
|
974
1025
|
add(
|
|
975
1026
|
emitter,
|
|
@@ -981,6 +1032,21 @@ function emitCall(emitter, call) {
|
|
|
981
1032
|
} else if (emitter.selfKey !== null) {
|
|
982
1033
|
add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
|
|
983
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) {
|
|
984
1050
|
const args = field(call, "arguments");
|
|
985
1051
|
let position = 0;
|
|
986
1052
|
for (const argument of args === null ? [] : children(args)) {
|
|
@@ -1100,12 +1166,58 @@ function walkExpressions(node, emitter, visit, turns = []) {
|
|
|
1100
1166
|
);
|
|
1101
1167
|
}
|
|
1102
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
|
+
}
|
|
1103
1211
|
function emitExpressionFacts(emitter, node) {
|
|
1104
1212
|
emitDynamicDefinition(emitter, node, []);
|
|
1105
1213
|
walkExpressions(node, emitter, (child, turns) => {
|
|
1106
1214
|
emitDynamicDefinition(emitter, child, turns);
|
|
1107
1215
|
if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
|
|
1108
1216
|
emitCall(emitter, child);
|
|
1217
|
+
emitKeyedFetch(emitter, child);
|
|
1218
|
+
}
|
|
1219
|
+
if (child.type === "element_reference") {
|
|
1220
|
+
emitKeyedElement(emitter, child);
|
|
1109
1221
|
}
|
|
1110
1222
|
if (ARRAY_TYPES.has(child.type)) {
|
|
1111
1223
|
emitArray(emitter, child);
|
|
@@ -1229,6 +1341,25 @@ function assignedValueOf(node) {
|
|
|
1229
1341
|
}
|
|
1230
1342
|
return field(node, "right");
|
|
1231
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
|
+
}
|
|
1232
1363
|
function emitMethodFacts(emitter, method) {
|
|
1233
1364
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1234
1365
|
add(emitter, "func", funcKey);
|
|
@@ -1243,7 +1374,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1243
1374
|
position += 1;
|
|
1244
1375
|
}
|
|
1245
1376
|
const inside = { ...emitter, enclosing: method };
|
|
1246
|
-
const body =
|
|
1377
|
+
const body = definitionBody(method);
|
|
1247
1378
|
if (body === null) {
|
|
1248
1379
|
return funcKey;
|
|
1249
1380
|
}
|
|
@@ -1275,6 +1406,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1275
1406
|
}
|
|
1276
1407
|
emitExpressionFacts(inside, body);
|
|
1277
1408
|
emitScopeWrites(inside, method, body);
|
|
1409
|
+
emitLambdasIn(inside, body);
|
|
1278
1410
|
return funcKey;
|
|
1279
1411
|
}
|
|
1280
1412
|
function isConstruction(value) {
|
|
@@ -1434,12 +1566,15 @@ function emitClassFacts(emitter, cls) {
|
|
|
1434
1566
|
add(emitter, "initializes", classKey, funcKey);
|
|
1435
1567
|
}
|
|
1436
1568
|
}
|
|
1437
|
-
const
|
|
1438
|
-
if (
|
|
1439
|
-
emitInstanceStores(within, funcKey,
|
|
1569
|
+
const methodBody2 = field(statement, "body");
|
|
1570
|
+
if (methodBody2 !== null) {
|
|
1571
|
+
emitInstanceStores(within, funcKey, methodBody2, stored);
|
|
1440
1572
|
}
|
|
1441
1573
|
}
|
|
1442
1574
|
emitInstanceWrites(within, classKey, collected);
|
|
1575
|
+
if (body !== null) {
|
|
1576
|
+
emitLambdasIn(within, body);
|
|
1577
|
+
}
|
|
1443
1578
|
return classKey;
|
|
1444
1579
|
}
|
|
1445
1580
|
function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
@@ -1475,6 +1610,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
|
1475
1610
|
walk(root);
|
|
1476
1611
|
emitExpressionFacts(emitter, root);
|
|
1477
1612
|
emitScopeWrites(emitter, null, root);
|
|
1613
|
+
emitLambdasIn(emitter, root);
|
|
1478
1614
|
}
|
|
1479
1615
|
|
|
1480
1616
|
// src/values/lowering.ts
|
|
@@ -2476,6 +2612,24 @@ function writtenNodeOf(node, db, site) {
|
|
|
2476
2612
|
const context = db === void 0 ? void 0 : contexts.get(db);
|
|
2477
2613
|
return context === void 0 ? null : context.writtenTo(node, site);
|
|
2478
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
|
+
}
|
|
2479
2633
|
function evaluatedValue(node, db, bindings, site) {
|
|
2480
2634
|
const evaluator = evaluatorFor(node, db);
|
|
2481
2635
|
if (bindings === void 0) {
|
|
@@ -2556,7 +2710,7 @@ function contextOver(db, nodes) {
|
|
|
2556
2710
|
if (file === null) {
|
|
2557
2711
|
return null;
|
|
2558
2712
|
}
|
|
2559
|
-
const key = readKey(file, node,
|
|
2713
|
+
const key = readKey(file, node, enclosingDefinition(node));
|
|
2560
2714
|
const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
|
|
2561
2715
|
return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
|
|
2562
2716
|
},
|
|
@@ -2566,7 +2720,7 @@ function contextOver(db, nodes) {
|
|
|
2566
2720
|
if (file === null || method === null) {
|
|
2567
2721
|
return null;
|
|
2568
2722
|
}
|
|
2569
|
-
const key = field(call, "receiver") === null ? readKey(file, method,
|
|
2723
|
+
const key = field(call, "receiver") === null ? readKey(file, method, enclosingDefinition(call)) : nodeId(file, method);
|
|
2570
2724
|
resolveValues(db, [key]);
|
|
2571
2725
|
const resolved = resolvedFunctions(db, key);
|
|
2572
2726
|
const settled = resolved.length === 1 ? resolved[0] : void 0;
|
|
@@ -2718,8 +2872,8 @@ function qualifyConstantRef(node, nesting) {
|
|
|
2718
2872
|
return compoundName(node);
|
|
2719
2873
|
}
|
|
2720
2874
|
if (node.type === "constant") {
|
|
2721
|
-
const
|
|
2722
|
-
return
|
|
2875
|
+
const innermost2 = nesting[0] ?? null;
|
|
2876
|
+
return innermost2 === null ? node.text : `${innermost2}::${node.text}`;
|
|
2723
2877
|
}
|
|
2724
2878
|
return null;
|
|
2725
2879
|
}
|
|
@@ -2776,9 +2930,9 @@ function ownIdentity(nameNode, outerNesting) {
|
|
|
2776
2930
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
2777
2931
|
}
|
|
2778
2932
|
function definedName(nameNode, outerNesting) {
|
|
2779
|
-
const
|
|
2780
|
-
if (nameNode.type === "scope_resolution" &&
|
|
2781
|
-
return `${
|
|
2933
|
+
const innermost2 = outerNesting[0] ?? null;
|
|
2934
|
+
if (nameNode.type === "scope_resolution" && innermost2 !== null && !nameNode.text.startsWith("::")) {
|
|
2935
|
+
return `${innermost2}::${nameNode.text}`;
|
|
2782
2936
|
}
|
|
2783
2937
|
return qualifyConstantRef(nameNode, outerNesting);
|
|
2784
2938
|
}
|
|
@@ -3402,24 +3556,38 @@ function isCall(node, locals, inherited) {
|
|
|
3402
3556
|
}
|
|
3403
3557
|
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
3404
3558
|
}
|
|
3405
|
-
function collectCalls(node,
|
|
3559
|
+
function collectCalls(node, read, inherited, found) {
|
|
3406
3560
|
for (const child of children2(node)) {
|
|
3407
|
-
if (
|
|
3561
|
+
if (read.stops.has(child.type)) {
|
|
3408
3562
|
continue;
|
|
3409
3563
|
}
|
|
3410
|
-
if (isCall(child, locals, inherited)) {
|
|
3564
|
+
if (isCall(child, read.locals, inherited)) {
|
|
3411
3565
|
found.push(child);
|
|
3412
3566
|
}
|
|
3413
|
-
collectCalls(child,
|
|
3567
|
+
collectCalls(child, read, inherited, found);
|
|
3414
3568
|
}
|
|
3415
3569
|
return found;
|
|
3416
3570
|
}
|
|
3417
|
-
function
|
|
3571
|
+
function methodBody(definitionNode) {
|
|
3418
3572
|
const body = field(definitionNode, "body");
|
|
3419
3573
|
if (body === null) {
|
|
3420
|
-
return
|
|
3574
|
+
return null;
|
|
3421
3575
|
}
|
|
3422
|
-
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, []);
|
|
3423
3591
|
}
|
|
3424
3592
|
function calleeText(call) {
|
|
3425
3593
|
const receiver = field(call, "receiver");
|
|
@@ -3463,14 +3631,23 @@ function enclosingStatement(call, body) {
|
|
|
3463
3631
|
return current;
|
|
3464
3632
|
}
|
|
3465
3633
|
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
|
|
3466
|
-
const
|
|
3467
|
-
if (
|
|
3634
|
+
const read = methodBody(definitionNode);
|
|
3635
|
+
if (read === null) {
|
|
3468
3636
|
return [];
|
|
3469
3637
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
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
|
|
3473
3646
|
);
|
|
3647
|
+
}
|
|
3648
|
+
function effectsOfBody(read, inherited, keepsArglessCall, facts) {
|
|
3649
|
+
const body = read.body;
|
|
3650
|
+
const calls = callsReported(bodyCalls(read, inherited), keepsArglessCall);
|
|
3474
3651
|
if (calls.length === 0) {
|
|
3475
3652
|
return [];
|
|
3476
3653
|
}
|
|
@@ -3876,6 +4053,57 @@ function clientCallUnits(root, pack, pattern, options) {
|
|
|
3876
4053
|
}
|
|
3877
4054
|
return units;
|
|
3878
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
|
+
}
|
|
3879
4107
|
function methodDefinitions(node, found = []) {
|
|
3880
4108
|
for (const child of node.namedChildren) {
|
|
3881
4109
|
if (child === null) {
|
|
@@ -4000,9 +4228,11 @@ function builderCallBehind(receiver, pattern, facts) {
|
|
|
4000
4228
|
return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
|
|
4001
4229
|
}
|
|
4002
4230
|
function urlIn(args, pattern, options, site) {
|
|
4231
|
+
return pathAt(urlNodeIn(args, pattern), options, site);
|
|
4232
|
+
}
|
|
4233
|
+
function urlNodeIn(args, pattern) {
|
|
4003
4234
|
const keyword = pattern.url.keyword;
|
|
4004
|
-
|
|
4005
|
-
return pathAt(written, options, site);
|
|
4235
|
+
return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
|
|
4006
4236
|
}
|
|
4007
4237
|
function pathAt(written, options, site) {
|
|
4008
4238
|
if (written === void 0) {
|
|
@@ -4124,11 +4354,11 @@ function namedParameters(db) {
|
|
|
4124
4354
|
}
|
|
4125
4355
|
const named2 = /* @__PURE__ */ new Map();
|
|
4126
4356
|
namedByDb.set(db, named2);
|
|
4127
|
-
const
|
|
4128
|
-
if (
|
|
4357
|
+
const objects = db.facts("environmentObject").map((row) => String(row[0]));
|
|
4358
|
+
if (objects.length === 0) {
|
|
4129
4359
|
return named2;
|
|
4130
4360
|
}
|
|
4131
|
-
|
|
4361
|
+
resolveEnvObjects(db, objects);
|
|
4132
4362
|
for (const row of db.facts("wantedParamNamesEnv")) {
|
|
4133
4363
|
const found = named2.get(String(row[0]));
|
|
4134
4364
|
if (found === void 0) {
|
|
@@ -4139,38 +4369,44 @@ function namedParameters(db) {
|
|
|
4139
4369
|
}
|
|
4140
4370
|
return named2;
|
|
4141
4371
|
}
|
|
4142
|
-
function
|
|
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 = [];
|
|
4372
|
+
function emitEnvFacts(db, file, root) {
|
|
4156
4373
|
(0, import_extractor4.walkDescendants)(root, null, {
|
|
4157
4374
|
at: (node) => {
|
|
4375
|
+
if (isEnv(node) && handsOnward(node)) {
|
|
4376
|
+
db.add("environmentObject", [
|
|
4377
|
+
readKey(file, node, enclosingDefinition(node))
|
|
4378
|
+
]);
|
|
4379
|
+
}
|
|
4158
4380
|
const site = envSiteAt(node);
|
|
4159
|
-
if (site !== null && stringLiteralValue(site.name) === null) {
|
|
4160
|
-
|
|
4381
|
+
if (site !== null && site.defaulted && stringLiteralValue(site.name) === null) {
|
|
4382
|
+
db.add(ENV_DEFAULTED, [nodeId(file, site.read)]);
|
|
4161
4383
|
}
|
|
4162
4384
|
},
|
|
4163
4385
|
into: () => null
|
|
4164
4386
|
});
|
|
4165
|
-
|
|
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;
|
|
4166
4398
|
}
|
|
4167
4399
|
function helperReads(calls, facts) {
|
|
4168
4400
|
const { db, file, named: named2 } = facts;
|
|
4169
4401
|
const callees = new NodeMap();
|
|
4170
4402
|
for (const call of calls) {
|
|
4171
|
-
const
|
|
4172
|
-
|
|
4173
|
-
|
|
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);
|
|
4174
4410
|
}
|
|
4175
4411
|
}
|
|
4176
4412
|
const reads = new NodeMap();
|
|
@@ -4179,7 +4415,7 @@ function helperReads(calls, facts) {
|
|
|
4179
4415
|
}
|
|
4180
4416
|
resolveValues(
|
|
4181
4417
|
db,
|
|
4182
|
-
[...callees].
|
|
4418
|
+
[...callees].flatMap(([, keys]) => keys)
|
|
4183
4419
|
);
|
|
4184
4420
|
for (const [call, callee] of callees) {
|
|
4185
4421
|
const args = namingArguments(db, named2, call, callee);
|
|
@@ -4193,10 +4429,10 @@ function helperReads(calls, facts) {
|
|
|
4193
4429
|
}
|
|
4194
4430
|
return reads;
|
|
4195
4431
|
}
|
|
4196
|
-
function namingArguments(db, named2, call,
|
|
4432
|
+
function namingArguments(db, named2, call, callees) {
|
|
4197
4433
|
const { positional, keyword } = readCallArgs(field(call, "arguments"));
|
|
4198
4434
|
const found = [];
|
|
4199
|
-
for (const func of resolvedFunctions(db,
|
|
4435
|
+
for (const func of callees.flatMap((key) => resolvedFunctions(db, key))) {
|
|
4200
4436
|
for (const row of db.lookup("paramOf", 0, func)) {
|
|
4201
4437
|
const sites = named2.get(String(row[2]));
|
|
4202
4438
|
const argument = positional[Number(row[1])];
|
|
@@ -4460,6 +4696,9 @@ function methodNamesOf(args, filter, facts) {
|
|
|
4460
4696
|
const value = named2 === void 0 ? null : stringValueOf(named2, facts);
|
|
4461
4697
|
return value === null ? [] : [value];
|
|
4462
4698
|
}
|
|
4699
|
+
return symbolArgumentNames(args, facts);
|
|
4700
|
+
}
|
|
4701
|
+
function symbolArgumentNames(args, facts) {
|
|
4463
4702
|
return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
|
|
4464
4703
|
}
|
|
4465
4704
|
function skipsOf(statement, filterName, facts) {
|
|
@@ -4498,19 +4737,315 @@ function actionsUnder(node, facts) {
|
|
|
4498
4737
|
}
|
|
4499
4738
|
|
|
4500
4739
|
// src/storage.ts
|
|
4501
|
-
var
|
|
4740
|
+
var import_ir_core2 = require("@suss/ir-core");
|
|
4741
|
+
var import_resolution7 = require("@suss/resolution");
|
|
4742
|
+
|
|
4743
|
+
// src/baseClass.ts
|
|
4502
4744
|
var import_resolution6 = require("@suss/resolution");
|
|
4503
|
-
function
|
|
4504
|
-
|
|
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])));
|
|
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;
|
|
4505
4765
|
}
|
|
4766
|
+
|
|
4767
|
+
// src/loaders.ts
|
|
4506
4768
|
function receiverOf(node) {
|
|
4507
4769
|
return node.type === "call" ? field(node, "receiver") : null;
|
|
4508
4770
|
}
|
|
4509
4771
|
function methodOf(node) {
|
|
4510
4772
|
return field(node, "method")?.text ?? "";
|
|
4511
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
|
+
}
|
|
4512
5047
|
function rootConstant(node) {
|
|
4513
|
-
const receiver =
|
|
5048
|
+
const receiver = receiverOf2(node);
|
|
4514
5049
|
if (receiver === null) {
|
|
4515
5050
|
return null;
|
|
4516
5051
|
}
|
|
@@ -4525,17 +5060,6 @@ function isConstant(node) {
|
|
|
4525
5060
|
function constantName(constant2) {
|
|
4526
5061
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
4527
5062
|
}
|
|
4528
|
-
function reachesBase(facts, classKey, bases) {
|
|
4529
|
-
(0, import_resolution6.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4530
|
-
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4531
|
-
}
|
|
4532
|
-
function classBehind(facts, file, constant2) {
|
|
4533
|
-
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
4534
|
-
const bound = new Set(
|
|
4535
|
-
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
4536
|
-
);
|
|
4537
|
-
return bound.size === 1 ? [...bound][0] : void 0;
|
|
4538
|
-
}
|
|
4539
5063
|
function modelPattern(constant2, file, options) {
|
|
4540
5064
|
const classKey = classBehind(options.facts, file, constant2);
|
|
4541
5065
|
if (classKey === void 0) {
|
|
@@ -4550,7 +5074,7 @@ function chainLinks(call) {
|
|
|
4550
5074
|
let current = call;
|
|
4551
5075
|
while (current !== null && current.type === "call") {
|
|
4552
5076
|
links.push(current);
|
|
4553
|
-
current =
|
|
5077
|
+
current = receiverOf2(current);
|
|
4554
5078
|
}
|
|
4555
5079
|
return links;
|
|
4556
5080
|
}
|
|
@@ -4563,9 +5087,17 @@ function kindOfCall(pattern, method) {
|
|
|
4563
5087
|
}
|
|
4564
5088
|
return void 0;
|
|
4565
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
|
+
}
|
|
4566
5098
|
function someLibraryDefines(options, method) {
|
|
4567
5099
|
return options.patterns.some(
|
|
4568
|
-
(pattern) =>
|
|
5100
|
+
(pattern) => libraryCallOf(pattern, method) !== void 0
|
|
4569
5101
|
);
|
|
4570
5102
|
}
|
|
4571
5103
|
function argumentsOf2(call) {
|
|
@@ -4600,7 +5132,7 @@ function hasPositionalArgument(call, facts) {
|
|
|
4600
5132
|
);
|
|
4601
5133
|
}
|
|
4602
5134
|
function columnsAskedFor(call, pattern, facts) {
|
|
4603
|
-
if (!(pattern.columnArguments ?? []).includes(
|
|
5135
|
+
if (!(pattern.columnArguments ?? []).includes(methodOf2(call))) {
|
|
4604
5136
|
return [];
|
|
4605
5137
|
}
|
|
4606
5138
|
return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
|
|
@@ -4610,14 +5142,14 @@ function primaryKeySelector(call, pattern, facts) {
|
|
|
4610
5142
|
if (byPrimaryKey === void 0) {
|
|
4611
5143
|
return [];
|
|
4612
5144
|
}
|
|
4613
|
-
const picks = byPrimaryKey.methods.includes(
|
|
5145
|
+
const picks = byPrimaryKey.methods.includes(methodOf2(call)) && hasPositionalArgument(call, facts);
|
|
4614
5146
|
return picks ? [byPrimaryKey.column] : [];
|
|
4615
5147
|
}
|
|
4616
5148
|
function selectorOf(call, pattern, facts) {
|
|
4617
5149
|
const picked = [];
|
|
4618
5150
|
for (const link of chainLinks(call)) {
|
|
4619
5151
|
picked.push(...primaryKeySelector(link, pattern, facts));
|
|
4620
|
-
if (pattern.reads.includes(
|
|
5152
|
+
if (pattern.reads.includes(methodOf2(link))) {
|
|
4621
5153
|
picked.push(...keywordKeys(link, facts));
|
|
4622
5154
|
}
|
|
4623
5155
|
}
|
|
@@ -4634,10 +5166,10 @@ function fieldsOf(call, pattern, kind, facts) {
|
|
|
4634
5166
|
];
|
|
4635
5167
|
}
|
|
4636
5168
|
function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
4637
|
-
const operation =
|
|
5169
|
+
const operation = methodOf2(call);
|
|
4638
5170
|
return {
|
|
4639
5171
|
type: "interaction",
|
|
4640
|
-
binding: (0,
|
|
5172
|
+
binding: (0, import_ir_core2.storageBinding)({
|
|
4641
5173
|
recognition: "ruby-storage",
|
|
4642
5174
|
storageSystem: pattern.storageSystem,
|
|
4643
5175
|
scope: "default",
|
|
@@ -4654,7 +5186,7 @@ function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
|
4654
5186
|
};
|
|
4655
5187
|
}
|
|
4656
5188
|
function classSettledOn(facts, key) {
|
|
4657
|
-
(0,
|
|
5189
|
+
(0, import_resolution7.askResolution)(facts, [key], "wanted", RUBY_PROGRAM);
|
|
4658
5190
|
const objects = new Set(
|
|
4659
5191
|
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
4660
5192
|
);
|
|
@@ -4686,7 +5218,7 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4686
5218
|
if (constant2 !== null) {
|
|
4687
5219
|
return constantReceiver(constant2, file, options.facts);
|
|
4688
5220
|
}
|
|
4689
|
-
const receiver =
|
|
5221
|
+
const receiver = receiverOf2(call);
|
|
4690
5222
|
if (receiver === null) {
|
|
4691
5223
|
return void 0;
|
|
4692
5224
|
}
|
|
@@ -4694,60 +5226,91 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4694
5226
|
}
|
|
4695
5227
|
function libraryCallIn(call, options) {
|
|
4696
5228
|
return chainLinks(call).find(
|
|
4697
|
-
(link) => someLibraryDefines(options,
|
|
5229
|
+
(link) => someLibraryDefines(options, methodOf2(link))
|
|
4698
5230
|
) ?? null;
|
|
4699
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
|
+
}
|
|
4700
5264
|
function modelCallEffects(call, file, options, enclosing) {
|
|
4701
5265
|
const worked = libraryCallIn(call, options);
|
|
4702
5266
|
if (worked === null) {
|
|
4703
5267
|
return [];
|
|
4704
5268
|
}
|
|
4705
|
-
const method =
|
|
5269
|
+
const method = methodOf2(worked);
|
|
4706
5270
|
const target = receiverClass(worked, file, options, enclosing);
|
|
4707
5271
|
if (target === void 0) {
|
|
4708
5272
|
return [];
|
|
4709
5273
|
}
|
|
4710
5274
|
for (const pattern of options.patterns) {
|
|
4711
|
-
const
|
|
4712
|
-
if (
|
|
5275
|
+
const library = libraryCallOf(pattern, method);
|
|
5276
|
+
if (library === void 0 || !reachesBase(options.facts, target.classKey, pattern.baseClasses)) {
|
|
4713
5277
|
continue;
|
|
4714
5278
|
}
|
|
4715
5279
|
if (projectDeclares(options.facts, target.classKey, method)) {
|
|
4716
5280
|
return [];
|
|
4717
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) : [];
|
|
4718
5286
|
return [
|
|
4719
5287
|
storageEffect(
|
|
4720
5288
|
worked,
|
|
4721
5289
|
target.container,
|
|
4722
5290
|
pattern,
|
|
4723
|
-
kind,
|
|
5291
|
+
library.kind,
|
|
4724
5292
|
selectorOf(worked, pattern, options.facts),
|
|
4725
|
-
fieldsOf(worked, pattern, kind, options.facts)
|
|
4726
|
-
)
|
|
5293
|
+
fieldsOf(worked, pattern, library.kind, options.facts)
|
|
5294
|
+
),
|
|
5295
|
+
...ran.map(callbackEffect)
|
|
4727
5296
|
];
|
|
4728
5297
|
}
|
|
4729
5298
|
return [];
|
|
4730
5299
|
}
|
|
4731
|
-
function
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
return node.text === loader.loader;
|
|
4737
|
-
}
|
|
4738
|
-
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
4739
|
-
}
|
|
4740
|
-
function loaderPick(call, loader) {
|
|
4741
|
-
const method = methodOf(call);
|
|
4742
|
-
const receiver = receiverOf(call);
|
|
4743
|
-
if (receiver === null) {
|
|
4744
|
-
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 [];
|
|
4745
5305
|
}
|
|
4746
|
-
|
|
4747
|
-
|
|
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);
|
|
4748
5312
|
}
|
|
4749
|
-
|
|
4750
|
-
return picks ? receiver : null;
|
|
5313
|
+
return [];
|
|
4751
5314
|
}
|
|
4752
5315
|
function loaderCallEffects(call, file, options) {
|
|
4753
5316
|
const effects = [];
|
|
@@ -4773,10 +5336,24 @@ function loaderCallEffects(call, file, options) {
|
|
|
4773
5336
|
}
|
|
4774
5337
|
function effectsOfCall(call, file, options, enclosing) {
|
|
4775
5338
|
const onModel = modelCallEffects(call, file, options, enclosing);
|
|
4776
|
-
|
|
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
|
+
});
|
|
4777
5351
|
}
|
|
4778
5352
|
function storageClaims(call, file, options, enclosing = null) {
|
|
4779
|
-
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;
|
|
4780
5357
|
}
|
|
4781
5358
|
function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
4782
5359
|
const keys = [];
|
|
@@ -4785,7 +5362,7 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4785
5362
|
if (worked === null || rootConstant(worked) !== null) {
|
|
4786
5363
|
continue;
|
|
4787
5364
|
}
|
|
4788
|
-
const receiver =
|
|
5365
|
+
const receiver = receiverOf2(worked);
|
|
4789
5366
|
if (receiver !== null) {
|
|
4790
5367
|
keys.push(readKey(file, receiver, enclosing));
|
|
4791
5368
|
}
|
|
@@ -4793,18 +5370,18 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4793
5370
|
return keys;
|
|
4794
5371
|
}
|
|
4795
5372
|
function storageEffects(calls, file, options, enclosing = null) {
|
|
4796
|
-
if (options
|
|
5373
|
+
if (!saysAnything(options)) {
|
|
4797
5374
|
return [];
|
|
4798
5375
|
}
|
|
4799
5376
|
const partOfOne = /* @__PURE__ */ new Set();
|
|
4800
5377
|
for (const call of calls) {
|
|
4801
|
-
const receiver =
|
|
5378
|
+
const receiver = receiverOf2(call);
|
|
4802
5379
|
if (receiver !== null) {
|
|
4803
5380
|
partOfOne.add(receiver.id);
|
|
4804
5381
|
}
|
|
4805
5382
|
}
|
|
4806
5383
|
const chains = calls.filter((call) => !partOfOne.has(call.id));
|
|
4807
|
-
(0,
|
|
5384
|
+
(0, import_resolution7.askResolution)(
|
|
4808
5385
|
options.facts,
|
|
4809
5386
|
receiverKeysToAsk(chains, file, options, enclosing),
|
|
4810
5387
|
"wanted",
|
|
@@ -4929,14 +5506,18 @@ async function discoverUnits(root, options) {
|
|
|
4929
5506
|
file: options.absoluteFile ?? options.filePath
|
|
4930
5507
|
}));
|
|
4931
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
|
+
);
|
|
4932
5518
|
for (const pack of options.packs) {
|
|
4933
5519
|
for (const pattern of pack.clients ?? []) {
|
|
4934
|
-
units.push(
|
|
4935
|
-
...clientCallUnits(root, pack, pattern, {
|
|
4936
|
-
filePath: options.filePath,
|
|
4937
|
-
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
4938
|
-
})
|
|
4939
|
-
);
|
|
5520
|
+
units.push(...clientCallUnits(root, pack, pattern, clientOptions));
|
|
4940
5521
|
}
|
|
4941
5522
|
}
|
|
4942
5523
|
for (const info of classes) {
|
|
@@ -5946,7 +6527,7 @@ function emitConstantBindings(db, perFile) {
|
|
|
5946
6527
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
5947
6528
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
5948
6529
|
var import_datalog4 = require("@suss/datalog");
|
|
5949
|
-
var
|
|
6530
|
+
var import_resolution10 = require("@suss/resolution");
|
|
5950
6531
|
|
|
5951
6532
|
// src/parser.ts
|
|
5952
6533
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -6010,7 +6591,7 @@ var import_node_path4 = __toESM(require("path"), 1);
|
|
|
6010
6591
|
var import_behavioral_ir5 = require("@suss/behavioral-ir");
|
|
6011
6592
|
var import_datalog3 = require("@suss/datalog");
|
|
6012
6593
|
var import_extractor10 = require("@suss/extractor");
|
|
6013
|
-
var
|
|
6594
|
+
var import_resolution9 = require("@suss/resolution");
|
|
6014
6595
|
|
|
6015
6596
|
// src/diagnostics.ts
|
|
6016
6597
|
var import_extractor6 = require("@suss/extractor");
|
|
@@ -6027,6 +6608,47 @@ function buildRubyExtractionReport(args) {
|
|
|
6027
6608
|
});
|
|
6028
6609
|
}
|
|
6029
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
|
+
|
|
6030
6652
|
// src/facts.ts
|
|
6031
6653
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
6032
6654
|
var import_extractor7 = require("@suss/extractor");
|
|
@@ -6097,7 +6719,7 @@ var import_datalog2 = require("@suss/datalog");
|
|
|
6097
6719
|
var import_extractor8 = require("@suss/extractor");
|
|
6098
6720
|
|
|
6099
6721
|
// src/reach/resolveCallee.ts
|
|
6100
|
-
var
|
|
6722
|
+
var import_resolution8 = require("@suss/resolution");
|
|
6101
6723
|
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
6102
6724
|
var INVOKES_RECEIVER = "call";
|
|
6103
6725
|
var STOP_FOR = {
|
|
@@ -6126,10 +6748,14 @@ function calleeSpellings(calls, ctx) {
|
|
|
6126
6748
|
}
|
|
6127
6749
|
return {
|
|
6128
6750
|
spellingOf,
|
|
6129
|
-
outcomes: (0,
|
|
6751
|
+
outcomes: (0, import_resolution8.calleeOutcomes)(ctx.facts, [...keys], RUBY_PROGRAM)
|
|
6130
6752
|
};
|
|
6131
6753
|
}
|
|
6132
6754
|
function resolveCallee(call, site, ctx, read) {
|
|
6755
|
+
const throughLoader = loaderSourceCallee(call, site, ctx);
|
|
6756
|
+
if (throughLoader !== null) {
|
|
6757
|
+
return throughLoader;
|
|
6758
|
+
}
|
|
6133
6759
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
6134
6760
|
if (spelling.kind === "stopped") {
|
|
6135
6761
|
return spelling;
|
|
@@ -6139,12 +6765,27 @@ function resolveCallee(call, site, ctx, read) {
|
|
|
6139
6765
|
}
|
|
6140
6766
|
return asCallee(spelling, outcomeFor(spelling.key, ctx, read), site, ctx);
|
|
6141
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
|
+
}
|
|
6142
6783
|
function outcomeFor(key, ctx, read) {
|
|
6143
|
-
return read?.outcomes.get(key) ?? (0,
|
|
6784
|
+
return read?.outcomes.get(key) ?? (0, import_resolution8.calleeOutcomeOf)(ctx.facts, key, RUBY_PROGRAM);
|
|
6144
6785
|
}
|
|
6145
6786
|
function mightReadAsACall(call, site, ctx) {
|
|
6146
6787
|
const spelling = spellingFor(call, site);
|
|
6147
|
-
return spelling.kind === "receiver" && (0,
|
|
6788
|
+
return spelling.kind === "receiver" && (0, import_resolution8.couldBeSettled)(ctx.facts, spelling.key);
|
|
6148
6789
|
}
|
|
6149
6790
|
function readsAsACall(call, site, ctx, read) {
|
|
6150
6791
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
@@ -6320,6 +6961,7 @@ async function reachedFunctions(seeds, options) {
|
|
|
6320
6961
|
const parameterCallsByKey = /* @__PURE__ */ new Map();
|
|
6321
6962
|
const propertyReadsByKey = /* @__PURE__ */ new Map();
|
|
6322
6963
|
const passedPositions = /* @__PURE__ */ new Set();
|
|
6964
|
+
const followedKeys = /* @__PURE__ */ new Set();
|
|
6323
6965
|
for (const seed of seeds) {
|
|
6324
6966
|
seedKeys.add(seed.key);
|
|
6325
6967
|
functionByKey.set(seed.key, {
|
|
@@ -6367,6 +7009,9 @@ async function reachedFunctions(seeds, options) {
|
|
|
6367
7009
|
for (const position of scan.passedPositions) {
|
|
6368
7010
|
passedPositions.add(position);
|
|
6369
7011
|
}
|
|
7012
|
+
if (scan.followed.length > 0) {
|
|
7013
|
+
followedKeys.add(key);
|
|
7014
|
+
}
|
|
6370
7015
|
for (const target of scan.followed) {
|
|
6371
7016
|
const calleeKey = keyOf(target);
|
|
6372
7017
|
if (!functionByKey.has(calleeKey)) {
|
|
@@ -6403,7 +7048,8 @@ async function reachedFunctions(seeds, options) {
|
|
|
6403
7048
|
argTargetsByKey,
|
|
6404
7049
|
parameterCallsByKey,
|
|
6405
7050
|
propertyReadsByKey,
|
|
6406
|
-
passedPositions
|
|
7051
|
+
passedPositions,
|
|
7052
|
+
followedKeys
|
|
6407
7053
|
};
|
|
6408
7054
|
}
|
|
6409
7055
|
function keyOf(target) {
|
|
@@ -6442,13 +7088,9 @@ function methodReferenceSymbol(node) {
|
|
|
6442
7088
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
6443
7089
|
}
|
|
6444
7090
|
function bodyOf2(source, options) {
|
|
6445
|
-
const site =
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
owner: keyOf(source),
|
|
6449
|
-
enclosingQualifiedName: source.enclosingQualifiedName
|
|
6450
|
-
};
|
|
6451
|
-
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);
|
|
6452
7094
|
const calls = callsReported(
|
|
6453
7095
|
written,
|
|
6454
7096
|
(call) => mightReadAsACall(call, site, options.context)
|
|
@@ -6460,6 +7102,17 @@ function bodyOf2(source, options) {
|
|
|
6460
7102
|
written: calls.map((call) => ({ call, site }))
|
|
6461
7103
|
};
|
|
6462
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
|
+
}
|
|
6463
7116
|
function callsMade(asked, site, ctx, spellings) {
|
|
6464
7117
|
return asked.filter(
|
|
6465
7118
|
(call) => !isArglessReceiverCall(call) || readsAsACall(call, site, ctx, spellings)
|
|
@@ -6467,7 +7120,7 @@ function callsMade(asked, site, ctx, spellings) {
|
|
|
6467
7120
|
}
|
|
6468
7121
|
function scanBody(source, ctx, options, read) {
|
|
6469
7122
|
const displayPathOf = options.displayPathOf;
|
|
6470
|
-
if (
|
|
7123
|
+
if (readableBodyOf(source.node) === null) {
|
|
6471
7124
|
return EMPTY_SCAN;
|
|
6472
7125
|
}
|
|
6473
7126
|
const site = read.site;
|
|
@@ -6510,9 +7163,34 @@ function scanBody(source, ctx, options, read) {
|
|
|
6510
7163
|
}
|
|
6511
7164
|
}
|
|
6512
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
|
+
};
|
|
6513
7181
|
for (const call of calls) {
|
|
6514
7182
|
const callee = calleeText(call);
|
|
6515
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
|
+
}
|
|
6516
7194
|
const placed = outcome.kind === "followed" ? {
|
|
6517
7195
|
file: displayPathOf(outcome.target.file),
|
|
6518
7196
|
span: spanOf(outcome.target.node)
|
|
@@ -6645,7 +7323,7 @@ function parameterName(param) {
|
|
|
6645
7323
|
|
|
6646
7324
|
// src/reach/context.ts
|
|
6647
7325
|
var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
6648
|
-
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
|
|
7326
|
+
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map(), loaders = []) {
|
|
6649
7327
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
6650
7328
|
const classes = [];
|
|
6651
7329
|
const classNames = /* @__PURE__ */ new Map();
|
|
@@ -6721,7 +7399,8 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
|
|
|
6721
7399
|
classNames,
|
|
6722
7400
|
definitions,
|
|
6723
7401
|
bodyBlocks,
|
|
6724
|
-
dynamicNames
|
|
7402
|
+
dynamicNames,
|
|
7403
|
+
loaders
|
|
6725
7404
|
};
|
|
6726
7405
|
}
|
|
6727
7406
|
function namedChildren(node) {
|
|
@@ -6789,7 +7468,7 @@ function factsForFile(options) {
|
|
|
6789
7468
|
const db = new import_datalog3.Database();
|
|
6790
7469
|
const bodyBlocks = bodyBlocksIn(options.packs);
|
|
6791
7470
|
emitValueFacts(db, options.file, options.root, bodyBlocks);
|
|
6792
|
-
|
|
7471
|
+
emitEnvFacts(db, options.file, options.root);
|
|
6793
7472
|
emitConstantBindings(db, [
|
|
6794
7473
|
collectFileConstants(
|
|
6795
7474
|
options.file,
|
|
@@ -6803,9 +7482,22 @@ function factsForFile(options) {
|
|
|
6803
7482
|
files: [{ file: options.file, root: options.root }],
|
|
6804
7483
|
definitions: new Map(methodDefinitionsIn(options.file, options.root))
|
|
6805
7484
|
});
|
|
6806
|
-
(0,
|
|
7485
|
+
(0, import_resolution9.addPackWords)(db, packWordsOf(options.packs));
|
|
6807
7486
|
return db;
|
|
6808
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
|
+
}
|
|
6809
7501
|
function alreadyDiscovered(seen, raw) {
|
|
6810
7502
|
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
6811
7503
|
const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir5.boundaryKey)(raw.boundaryBinding) ?? "";
|
|
@@ -6861,7 +7553,7 @@ async function extractRubyProject(options) {
|
|
|
6861
7553
|
}
|
|
6862
7554
|
parsed.push({ file, root });
|
|
6863
7555
|
emitValueFacts(db, file, root, bodyBlocks);
|
|
6864
|
-
|
|
7556
|
+
emitEnvFacts(db, file, root);
|
|
6865
7557
|
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
6866
7558
|
definitions.set(key, method);
|
|
6867
7559
|
}
|
|
@@ -6877,7 +7569,16 @@ async function extractRubyProject(options) {
|
|
|
6877
7569
|
emitRequireFacts(db, file, root, known);
|
|
6878
7570
|
}
|
|
6879
7571
|
bindEvaluator(db, { files: parsed, definitions });
|
|
6880
|
-
(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
|
+
}
|
|
6881
7582
|
return readDynamicNames(
|
|
6882
7583
|
db,
|
|
6883
7584
|
new Map(parsed.map(({ file, root }) => [file, root]))
|
|
@@ -6885,11 +7586,17 @@ async function extractRubyProject(options) {
|
|
|
6885
7586
|
});
|
|
6886
7587
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
6887
7588
|
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
6888
|
-
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;
|
|
6889
7596
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
6890
7597
|
const reachContext = await timer.timeAsync(
|
|
6891
7598
|
"discover",
|
|
6892
|
-
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames)
|
|
7599
|
+
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames, loaderPatterns)
|
|
6893
7600
|
);
|
|
6894
7601
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? import_node_path4.default.relative(options.workspaceRoot, file) : file;
|
|
6895
7602
|
const seeds = [];
|
|
@@ -6939,26 +7646,34 @@ async function extractRubyProject(options) {
|
|
|
6939
7646
|
});
|
|
6940
7647
|
}
|
|
6941
7648
|
}
|
|
6942
|
-
const
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
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
|
+
});
|
|
6962
7677
|
}
|
|
6963
7678
|
for (const pack of options.packs) {
|
|
6964
7679
|
for (const pattern of pack.discovery) {
|
|
@@ -6996,6 +7711,9 @@ async function extractRubyProject(options) {
|
|
|
6996
7711
|
continue;
|
|
6997
7712
|
}
|
|
6998
7713
|
const { raw, seedKey } = entry;
|
|
7714
|
+
if (entry.onlyIfItReaches === true && (seedKey === null || !reached.followedKeys.has(seedKey))) {
|
|
7715
|
+
continue;
|
|
7716
|
+
}
|
|
6999
7717
|
if (seedKey !== null) {
|
|
7000
7718
|
dropPropertyReads(raw, reached.propertyReadsByKey.get(seedKey));
|
|
7001
7719
|
}
|
|
@@ -7104,7 +7822,7 @@ function findRubyFiles(root) {
|
|
|
7104
7822
|
}
|
|
7105
7823
|
|
|
7106
7824
|
// src/facts/why.ts
|
|
7107
|
-
var WITNESS_RULES = [...
|
|
7825
|
+
var WITNESS_RULES = [...import_resolution10.RESOLUTION_RULES, ...RUBY_RULES];
|
|
7108
7826
|
function namedChildrenOf(node) {
|
|
7109
7827
|
return node.namedChildren.filter((child) => child !== null);
|
|
7110
7828
|
}
|
|
@@ -7143,7 +7861,7 @@ function width(node) {
|
|
|
7143
7861
|
return node.endIndex - node.startIndex;
|
|
7144
7862
|
}
|
|
7145
7863
|
function askedKey(value) {
|
|
7146
|
-
const enclosing =
|
|
7864
|
+
const enclosing = enclosingDefinition(value.node);
|
|
7147
7865
|
return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
|
|
7148
7866
|
}
|
|
7149
7867
|
var RubyWhySession = class {
|
|
@@ -7218,7 +7936,7 @@ var RubyWhySession = class {
|
|
|
7218
7936
|
* which the caller says in its own words.
|
|
7219
7937
|
*/
|
|
7220
7938
|
explain(value, options = {}) {
|
|
7221
|
-
return (0,
|
|
7939
|
+
return (0, import_resolution10.explainResolvedKey)({
|
|
7222
7940
|
db: this.db,
|
|
7223
7941
|
rules: WITNESS_RULES,
|
|
7224
7942
|
key: askedKey(value),
|
|
@@ -7359,6 +8077,8 @@ async function rubyStubEvidence(options) {
|
|
|
7359
8077
|
resolveConstantFile,
|
|
7360
8078
|
rubyStubEvidence,
|
|
7361
8079
|
runStatements,
|
|
8080
|
+
storageClaims,
|
|
8081
|
+
storageEffects,
|
|
7362
8082
|
stringLiteralValue,
|
|
7363
8083
|
stringValueOf,
|
|
7364
8084
|
symbolValue,
|