@suss/adapter-ruby 0.32.0 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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.js
CHANGED
|
@@ -62,12 +62,19 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
|
62
62
|
"module",
|
|
63
63
|
"singleton_class"
|
|
64
64
|
]);
|
|
65
|
+
var PROGRAM_TYPE = "program";
|
|
66
|
+
var MODULE_SCOPE_STOPS = /* @__PURE__ */ new Set([
|
|
67
|
+
...OWN_BODY_TYPES,
|
|
68
|
+
"block",
|
|
69
|
+
"do_block"
|
|
70
|
+
]);
|
|
65
71
|
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
66
72
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
67
|
-
|
|
73
|
+
var LAMBDA_TYPE = "lambda";
|
|
74
|
+
function enclosingDefinition(node) {
|
|
68
75
|
let current = node.parent;
|
|
69
76
|
while (current !== null) {
|
|
70
|
-
if (METHOD_TYPES.has(current.type)) {
|
|
77
|
+
if (METHOD_TYPES.has(current.type) || current.type === LAMBDA_TYPE) {
|
|
71
78
|
return current;
|
|
72
79
|
}
|
|
73
80
|
current = current.parent;
|
|
@@ -341,7 +348,7 @@ import { nodeOfKey as nodeOfKey2 } from "@suss/resolution";
|
|
|
341
348
|
import { hole as hole2, literalOf as literalOf3, piecesOf } from "@suss/values";
|
|
342
349
|
|
|
343
350
|
// src/values/evaluator.ts
|
|
344
|
-
import { nodeOfKey } from "@suss/resolution";
|
|
351
|
+
import { nodeOfKey, writtenValuesByKey } from "@suss/resolution";
|
|
345
352
|
import { Evaluator, force, literalOf, text } from "@suss/values";
|
|
346
353
|
|
|
347
354
|
// src/facts/resolve.ts
|
|
@@ -401,11 +408,14 @@ var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
|
|
|
401
408
|
function resolveValues(db, keys) {
|
|
402
409
|
askResolution(db, keys, "wanted", RUBY_PROGRAM);
|
|
403
410
|
}
|
|
404
|
-
function
|
|
405
|
-
askResolution(db,
|
|
411
|
+
function resolveEnvObjects(db, objects) {
|
|
412
|
+
askResolution(db, objects, "wantedEnvObject", RUBY_PROGRAM);
|
|
406
413
|
}
|
|
407
414
|
function resolvedFunctions(db, key) {
|
|
408
|
-
return
|
|
415
|
+
return [
|
|
416
|
+
...db.lookup("wantedResolves", 0, key),
|
|
417
|
+
...db.lookup("wantedGivesBack", 0, key)
|
|
418
|
+
].map((row) => String(row[1]));
|
|
409
419
|
}
|
|
410
420
|
function writtenValueOf(db, key) {
|
|
411
421
|
resolveValues(db, [key]);
|
|
@@ -505,12 +515,15 @@ var BINDS_A_NAME = {
|
|
|
505
515
|
PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
|
|
506
516
|
)
|
|
507
517
|
};
|
|
508
|
-
|
|
518
|
+
var DESCENDS_EVERYWHERE = /* @__PURE__ */ new Set();
|
|
519
|
+
function localNamesIn(definitionNode, stops = DESCENDS_EVERYWHERE) {
|
|
509
520
|
const names = /* @__PURE__ */ new Set();
|
|
510
521
|
const visit = (node) => {
|
|
511
522
|
BINDS_A_NAME[node.type]?.(node, names);
|
|
512
523
|
for (const child of bodyStatements(node)) {
|
|
513
|
-
|
|
524
|
+
if (!stops.has(child.type)) {
|
|
525
|
+
visit(child);
|
|
526
|
+
}
|
|
514
527
|
}
|
|
515
528
|
};
|
|
516
529
|
visit(definitionNode);
|
|
@@ -523,11 +536,12 @@ function spellsAName(node) {
|
|
|
523
536
|
}
|
|
524
537
|
return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
|
|
525
538
|
}
|
|
539
|
+
var KEYWORD_LITERALS = /* @__PURE__ */ new Set(["__FILE__", "__LINE__", "__ENCODING__"]);
|
|
526
540
|
function isBareMethodCall(node, locals) {
|
|
527
541
|
if (node.type !== "identifier" || locals.has(node.text)) {
|
|
528
542
|
return false;
|
|
529
543
|
}
|
|
530
|
-
return !spellsAName(node);
|
|
544
|
+
return !KEYWORD_LITERALS.has(node.text) && !spellsAName(node);
|
|
531
545
|
}
|
|
532
546
|
|
|
533
547
|
// src/facts/locals.ts
|
|
@@ -678,25 +692,46 @@ function namesOfFile(tree) {
|
|
|
678
692
|
cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
|
|
679
693
|
return cache.file;
|
|
680
694
|
}
|
|
695
|
+
function scopesAround(enclosing) {
|
|
696
|
+
const chain = [];
|
|
697
|
+
let current = enclosing;
|
|
698
|
+
while (current !== null && current.type === LAMBDA_TYPE) {
|
|
699
|
+
chain.push(current);
|
|
700
|
+
current = enclosingDefinition(current);
|
|
701
|
+
}
|
|
702
|
+
chain.push(current);
|
|
703
|
+
return chain;
|
|
704
|
+
}
|
|
705
|
+
var namesOfScope = (scope, node) => scope === null ? namesOfFile(node.tree) : namesOfMethod(scope);
|
|
681
706
|
function ownerOfName(node, name, enclosing) {
|
|
682
707
|
if (node.type === "constant") {
|
|
683
708
|
return null;
|
|
684
709
|
}
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
710
|
+
for (const scope of scopesAround(enclosing)) {
|
|
711
|
+
const names = namesOfScope(scope, node);
|
|
712
|
+
if (names.blockParams.has(name)) {
|
|
713
|
+
const block = blockDeclaring(node, name);
|
|
714
|
+
if (block !== null) {
|
|
715
|
+
return block;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (scope !== null && names.locals.has(name)) {
|
|
719
|
+
return scope;
|
|
690
720
|
}
|
|
691
721
|
}
|
|
692
|
-
return
|
|
722
|
+
return null;
|
|
693
723
|
}
|
|
694
724
|
function isLocalName(node, name, enclosing) {
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
725
|
+
for (const scope of scopesAround(enclosing)) {
|
|
726
|
+
const names = namesOfScope(scope, node);
|
|
727
|
+
if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
if (names.locals.has(name)) {
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
698
733
|
}
|
|
699
|
-
return
|
|
734
|
+
return false;
|
|
700
735
|
}
|
|
701
736
|
function blockDeclaring(node, name) {
|
|
702
737
|
let current = node.parent;
|
|
@@ -887,19 +922,33 @@ function calleeKeyOf(filePath, node, enclosing) {
|
|
|
887
922
|
}
|
|
888
923
|
return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
|
|
889
924
|
}
|
|
925
|
+
function invokedKeyOf(filePath, node, enclosing) {
|
|
926
|
+
const invoked = invokedValueOf(node);
|
|
927
|
+
return invoked === null ? null : readKey(filePath, invoked, enclosing);
|
|
928
|
+
}
|
|
890
929
|
function emitCall(emitter, call) {
|
|
891
|
-
|
|
930
|
+
const callKey = nodeId(emitter.filePath, call);
|
|
931
|
+
add(emitter, "writtenValue", callKey);
|
|
932
|
+
const invoked = invokedValueOf(call);
|
|
933
|
+
if (invoked !== null) {
|
|
934
|
+
add(emitter, "call", callKey, valueKey(emitter, invoked));
|
|
935
|
+
}
|
|
936
|
+
if (!emitMessageSent(emitter, call, callKey) && invoked === null) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
emitCallArguments(emitter, call, callKey);
|
|
940
|
+
}
|
|
941
|
+
function emitMessageSent(emitter, call, callKey) {
|
|
892
942
|
const method = call.type === "identifier" ? call : field(call, "method");
|
|
893
943
|
const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
|
|
894
944
|
if (method === null || calleeKey === null) {
|
|
895
|
-
return;
|
|
945
|
+
return false;
|
|
896
946
|
}
|
|
897
|
-
const receiver = field(call, "receiver");
|
|
898
|
-
const callKey = nodeId(emitter.filePath, call);
|
|
899
947
|
add(emitter, "call", callKey, calleeKey);
|
|
900
948
|
if (!emitter.insideMethod) {
|
|
901
949
|
add(emitter, "callOutsideMethod", callKey);
|
|
902
950
|
}
|
|
951
|
+
const receiver = field(call, "receiver");
|
|
903
952
|
if (receiver !== null) {
|
|
904
953
|
add(
|
|
905
954
|
emitter,
|
|
@@ -911,6 +960,21 @@ function emitCall(emitter, call) {
|
|
|
911
960
|
} else if (emitter.selfKey !== null) {
|
|
912
961
|
add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
|
|
913
962
|
}
|
|
963
|
+
return true;
|
|
964
|
+
}
|
|
965
|
+
var INVOKE_METHOD = "call";
|
|
966
|
+
function invokedValueOf(call) {
|
|
967
|
+
if (call.type !== "call") {
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
const receiver = field(call, "receiver");
|
|
971
|
+
if (receiver === null) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
const method = field(call, "method");
|
|
975
|
+
return method === null || method.text === INVOKE_METHOD ? receiver : null;
|
|
976
|
+
}
|
|
977
|
+
function emitCallArguments(emitter, call, callKey) {
|
|
914
978
|
const args = field(call, "arguments");
|
|
915
979
|
let position = 0;
|
|
916
980
|
for (const argument of args === null ? [] : children(args)) {
|
|
@@ -1030,12 +1094,58 @@ function walkExpressions(node, emitter, visit, turns = []) {
|
|
|
1030
1094
|
);
|
|
1031
1095
|
}
|
|
1032
1096
|
}
|
|
1097
|
+
var WRITTEN_KEY_TYPES = /* @__PURE__ */ new Set([
|
|
1098
|
+
"string",
|
|
1099
|
+
"integer",
|
|
1100
|
+
"simple_symbol",
|
|
1101
|
+
"bare_string",
|
|
1102
|
+
"bare_symbol"
|
|
1103
|
+
]);
|
|
1104
|
+
function emitKeyedRead(emitter, site, container, key) {
|
|
1105
|
+
if (WRITTEN_KEY_TYPES.has(key.type)) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
add(
|
|
1109
|
+
emitter,
|
|
1110
|
+
"readsKeyed",
|
|
1111
|
+
nodeId(emitter.filePath, site),
|
|
1112
|
+
valueKey(emitter, container),
|
|
1113
|
+
valueKey(emitter, key)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
function emitKeyedElement(emitter, node) {
|
|
1117
|
+
const object = field(node, "object");
|
|
1118
|
+
if (object === null || isWriteTarget(node)) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const index = node.namedChildren.find(
|
|
1122
|
+
(child) => child !== null && child.id !== object.id
|
|
1123
|
+
);
|
|
1124
|
+
if (index !== void 0) {
|
|
1125
|
+
emitKeyedRead(emitter, node, object, index);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
function emitKeyedFetch(emitter, call) {
|
|
1129
|
+
const receiver = field(call, "receiver");
|
|
1130
|
+
if (receiver === null || field(call, "method")?.text !== "fetch") {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const { positional } = readCallArgs(field(call, "arguments"));
|
|
1134
|
+
const key = positional[0];
|
|
1135
|
+
if (key !== void 0) {
|
|
1136
|
+
emitKeyedRead(emitter, call, receiver, key);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1033
1139
|
function emitExpressionFacts(emitter, node) {
|
|
1034
1140
|
emitDynamicDefinition(emitter, node, []);
|
|
1035
1141
|
walkExpressions(node, emitter, (child, turns) => {
|
|
1036
1142
|
emitDynamicDefinition(emitter, child, turns);
|
|
1037
1143
|
if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
|
|
1038
1144
|
emitCall(emitter, child);
|
|
1145
|
+
emitKeyedFetch(emitter, child);
|
|
1146
|
+
}
|
|
1147
|
+
if (child.type === "element_reference") {
|
|
1148
|
+
emitKeyedElement(emitter, child);
|
|
1039
1149
|
}
|
|
1040
1150
|
if (ARRAY_TYPES.has(child.type)) {
|
|
1041
1151
|
emitArray(emitter, child);
|
|
@@ -1159,6 +1269,25 @@ function assignedValueOf(node) {
|
|
|
1159
1269
|
}
|
|
1160
1270
|
return field(node, "right");
|
|
1161
1271
|
}
|
|
1272
|
+
function emitLambdasIn(emitter, body) {
|
|
1273
|
+
for (const child of children(body)) {
|
|
1274
|
+
if (child.type === LAMBDA_TYPE) {
|
|
1275
|
+
emitMethodFacts(emitter, child);
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
emitLambdasIn(emitter, child);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function definitionBody(definition) {
|
|
1285
|
+
const body = field(definition, "body");
|
|
1286
|
+
if (body === null || definition.type !== LAMBDA_TYPE) {
|
|
1287
|
+
return body;
|
|
1288
|
+
}
|
|
1289
|
+
return field(body, "body");
|
|
1290
|
+
}
|
|
1162
1291
|
function emitMethodFacts(emitter, method) {
|
|
1163
1292
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1164
1293
|
add(emitter, "func", funcKey);
|
|
@@ -1173,7 +1302,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1173
1302
|
position += 1;
|
|
1174
1303
|
}
|
|
1175
1304
|
const inside = { ...emitter, enclosing: method };
|
|
1176
|
-
const body =
|
|
1305
|
+
const body = definitionBody(method);
|
|
1177
1306
|
if (body === null) {
|
|
1178
1307
|
return funcKey;
|
|
1179
1308
|
}
|
|
@@ -1205,6 +1334,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1205
1334
|
}
|
|
1206
1335
|
emitExpressionFacts(inside, body);
|
|
1207
1336
|
emitScopeWrites(inside, method, body);
|
|
1337
|
+
emitLambdasIn(inside, body);
|
|
1208
1338
|
return funcKey;
|
|
1209
1339
|
}
|
|
1210
1340
|
function isConstruction(value) {
|
|
@@ -1364,12 +1494,15 @@ function emitClassFacts(emitter, cls) {
|
|
|
1364
1494
|
add(emitter, "initializes", classKey, funcKey);
|
|
1365
1495
|
}
|
|
1366
1496
|
}
|
|
1367
|
-
const
|
|
1368
|
-
if (
|
|
1369
|
-
emitInstanceStores(within, funcKey,
|
|
1497
|
+
const methodBody2 = field(statement, "body");
|
|
1498
|
+
if (methodBody2 !== null) {
|
|
1499
|
+
emitInstanceStores(within, funcKey, methodBody2, stored);
|
|
1370
1500
|
}
|
|
1371
1501
|
}
|
|
1372
1502
|
emitInstanceWrites(within, classKey, collected);
|
|
1503
|
+
if (body !== null) {
|
|
1504
|
+
emitLambdasIn(within, body);
|
|
1505
|
+
}
|
|
1373
1506
|
return classKey;
|
|
1374
1507
|
}
|
|
1375
1508
|
function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
@@ -1405,6 +1538,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
|
1405
1538
|
walk(root);
|
|
1406
1539
|
emitExpressionFacts(emitter, root);
|
|
1407
1540
|
emitScopeWrites(emitter, null, root);
|
|
1541
|
+
emitLambdasIn(emitter, root);
|
|
1408
1542
|
}
|
|
1409
1543
|
|
|
1410
1544
|
// src/values/lowering.ts
|
|
@@ -2424,6 +2558,24 @@ function writtenNodeOf(node, db, site) {
|
|
|
2424
2558
|
const context = db === void 0 ? void 0 : contexts.get(db);
|
|
2425
2559
|
return context === void 0 ? null : context.writtenTo(node, site);
|
|
2426
2560
|
}
|
|
2561
|
+
function askWrittenValues(nodes, db) {
|
|
2562
|
+
if (db === void 0 || !contexts.has(db)) {
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2566
|
+
for (const node of nodes) {
|
|
2567
|
+
const file = filesByTree.get(node.tree);
|
|
2568
|
+
if (file !== void 0) {
|
|
2569
|
+
keys.add(readKey(file, node, enclosingDefinition(node)));
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
if (keys.size === 0) {
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
const asked = [...keys];
|
|
2576
|
+
resolveValues(db, asked);
|
|
2577
|
+
writtenValuesByKey(db, asked, (more) => resolveValues(db, more));
|
|
2578
|
+
}
|
|
2427
2579
|
function evaluatedValue(node, db, bindings, site) {
|
|
2428
2580
|
const evaluator = evaluatorFor(node, db);
|
|
2429
2581
|
if (bindings === void 0) {
|
|
@@ -2504,7 +2656,7 @@ function contextOver(db, nodes) {
|
|
|
2504
2656
|
if (file === null) {
|
|
2505
2657
|
return null;
|
|
2506
2658
|
}
|
|
2507
|
-
const key = readKey(file, node,
|
|
2659
|
+
const key = readKey(file, node, enclosingDefinition(node));
|
|
2508
2660
|
const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
|
|
2509
2661
|
return answer === null ? null : nodeOfKey(rootsByFile, answer);
|
|
2510
2662
|
},
|
|
@@ -2514,7 +2666,7 @@ function contextOver(db, nodes) {
|
|
|
2514
2666
|
if (file === null || method === null) {
|
|
2515
2667
|
return null;
|
|
2516
2668
|
}
|
|
2517
|
-
const key = field(call, "receiver") === null ? readKey(file, method,
|
|
2669
|
+
const key = field(call, "receiver") === null ? readKey(file, method, enclosingDefinition(call)) : nodeId(file, method);
|
|
2518
2670
|
resolveValues(db, [key]);
|
|
2519
2671
|
const resolved = resolvedFunctions(db, key);
|
|
2520
2672
|
const settled = resolved.length === 1 ? resolved[0] : void 0;
|
|
@@ -2666,8 +2818,8 @@ function qualifyConstantRef(node, nesting) {
|
|
|
2666
2818
|
return compoundName(node);
|
|
2667
2819
|
}
|
|
2668
2820
|
if (node.type === "constant") {
|
|
2669
|
-
const
|
|
2670
|
-
return
|
|
2821
|
+
const innermost2 = nesting[0] ?? null;
|
|
2822
|
+
return innermost2 === null ? node.text : `${innermost2}::${node.text}`;
|
|
2671
2823
|
}
|
|
2672
2824
|
return null;
|
|
2673
2825
|
}
|
|
@@ -2724,9 +2876,9 @@ function ownIdentity(nameNode, outerNesting) {
|
|
|
2724
2876
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
2725
2877
|
}
|
|
2726
2878
|
function definedName(nameNode, outerNesting) {
|
|
2727
|
-
const
|
|
2728
|
-
if (nameNode.type === "scope_resolution" &&
|
|
2729
|
-
return `${
|
|
2879
|
+
const innermost2 = outerNesting[0] ?? null;
|
|
2880
|
+
if (nameNode.type === "scope_resolution" && innermost2 !== null && !nameNode.text.startsWith("::")) {
|
|
2881
|
+
return `${innermost2}::${nameNode.text}`;
|
|
2730
2882
|
}
|
|
2731
2883
|
return qualifyConstantRef(nameNode, outerNesting);
|
|
2732
2884
|
}
|
|
@@ -3354,24 +3506,38 @@ function isCall(node, locals, inherited) {
|
|
|
3354
3506
|
}
|
|
3355
3507
|
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
3356
3508
|
}
|
|
3357
|
-
function collectCalls(node,
|
|
3509
|
+
function collectCalls(node, read, inherited, found) {
|
|
3358
3510
|
for (const child of children2(node)) {
|
|
3359
|
-
if (
|
|
3511
|
+
if (read.stops.has(child.type)) {
|
|
3360
3512
|
continue;
|
|
3361
3513
|
}
|
|
3362
|
-
if (isCall(child, locals, inherited)) {
|
|
3514
|
+
if (isCall(child, read.locals, inherited)) {
|
|
3363
3515
|
found.push(child);
|
|
3364
3516
|
}
|
|
3365
|
-
collectCalls(child,
|
|
3517
|
+
collectCalls(child, read, inherited, found);
|
|
3366
3518
|
}
|
|
3367
3519
|
return found;
|
|
3368
3520
|
}
|
|
3369
|
-
function
|
|
3521
|
+
function methodBody(definitionNode) {
|
|
3370
3522
|
const body = field(definitionNode, "body");
|
|
3371
3523
|
if (body === null) {
|
|
3372
|
-
return
|
|
3524
|
+
return null;
|
|
3373
3525
|
}
|
|
3374
|
-
return
|
|
3526
|
+
return {
|
|
3527
|
+
body,
|
|
3528
|
+
locals: localNamesIn(definitionNode),
|
|
3529
|
+
stops: OWN_BODY_TYPES
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
function moduleScopeBody(root) {
|
|
3533
|
+
return {
|
|
3534
|
+
body: root,
|
|
3535
|
+
locals: localNamesIn(root, MODULE_SCOPE_STOPS),
|
|
3536
|
+
stops: MODULE_SCOPE_STOPS
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
function bodyCalls(read, inherited = NO_INHERITED_METHODS) {
|
|
3540
|
+
return collectCalls(read.body, read, inherited, []);
|
|
3375
3541
|
}
|
|
3376
3542
|
function calleeText(call) {
|
|
3377
3543
|
const receiver = field(call, "receiver");
|
|
@@ -3415,14 +3581,23 @@ function enclosingStatement(call, body) {
|
|
|
3415
3581
|
return current;
|
|
3416
3582
|
}
|
|
3417
3583
|
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
|
|
3418
|
-
const
|
|
3419
|
-
if (
|
|
3584
|
+
const read = methodBody(definitionNode);
|
|
3585
|
+
if (read === null) {
|
|
3420
3586
|
return [];
|
|
3421
3587
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3588
|
+
return effectsOfBody(read, inherited, keepsArglessCall, facts);
|
|
3589
|
+
}
|
|
3590
|
+
function moduleScopeInvocationEffects(root, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
|
|
3591
|
+
return effectsOfBody(
|
|
3592
|
+
moduleScopeBody(root),
|
|
3593
|
+
inherited,
|
|
3594
|
+
keepsArglessCall,
|
|
3595
|
+
facts
|
|
3425
3596
|
);
|
|
3597
|
+
}
|
|
3598
|
+
function effectsOfBody(read, inherited, keepsArglessCall, facts) {
|
|
3599
|
+
const body = read.body;
|
|
3600
|
+
const calls = callsReported(bodyCalls(read, inherited), keepsArglessCall);
|
|
3426
3601
|
if (calls.length === 0) {
|
|
3427
3602
|
return [];
|
|
3428
3603
|
}
|
|
@@ -3834,6 +4009,57 @@ function clientCallUnits(root, pack, pattern, options) {
|
|
|
3834
4009
|
}
|
|
3835
4010
|
return units;
|
|
3836
4011
|
}
|
|
4012
|
+
function askClientCallReads(root, patterns, options) {
|
|
4013
|
+
const facts = options.facts;
|
|
4014
|
+
if (facts === void 0 || patterns.length === 0) {
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
const calls = [];
|
|
4018
|
+
for (const method of methodDefinitions(root)) {
|
|
4019
|
+
calls.push(...callsUnder(method));
|
|
4020
|
+
}
|
|
4021
|
+
if (patterns.some((pattern) => (pattern.receiverBuilders ?? []).length > 0)) {
|
|
4022
|
+
askWrittenValues(receiversOf(calls), facts);
|
|
4023
|
+
}
|
|
4024
|
+
askWrittenValues(requestArguments(calls, patterns, options), facts);
|
|
4025
|
+
}
|
|
4026
|
+
function receiversOf(calls) {
|
|
4027
|
+
const found = [];
|
|
4028
|
+
for (const call of calls) {
|
|
4029
|
+
const receiver = field(call, "receiver");
|
|
4030
|
+
if (receiver !== null) {
|
|
4031
|
+
found.push(receiver);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
return found;
|
|
4035
|
+
}
|
|
4036
|
+
function requestArguments(calls, patterns, options) {
|
|
4037
|
+
const found = [];
|
|
4038
|
+
for (const call of calls) {
|
|
4039
|
+
const receiver = field(call, "receiver");
|
|
4040
|
+
const called = field(call, "method")?.text;
|
|
4041
|
+
if (receiver === null || called === void 0) {
|
|
4042
|
+
continue;
|
|
4043
|
+
}
|
|
4044
|
+
for (const pattern of patterns) {
|
|
4045
|
+
if (!isLibraryReceiver(receiver, pattern, options)) {
|
|
4046
|
+
continue;
|
|
4047
|
+
}
|
|
4048
|
+
const argument = requestArgument(call, called, pattern);
|
|
4049
|
+
if (argument !== void 0) {
|
|
4050
|
+
found.push(argument);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
return found;
|
|
4055
|
+
}
|
|
4056
|
+
function requestArgument(call, called, pattern) {
|
|
4057
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
4058
|
+
if (pattern.verbMethodNames[called] !== void 0) {
|
|
4059
|
+
return urlNodeIn(args, pattern);
|
|
4060
|
+
}
|
|
4061
|
+
return pattern.requestObject?.attribute === called ? args.positional[0] : void 0;
|
|
4062
|
+
}
|
|
3837
4063
|
function methodDefinitions(node, found = []) {
|
|
3838
4064
|
for (const child of node.namedChildren) {
|
|
3839
4065
|
if (child === null) {
|
|
@@ -3958,9 +4184,11 @@ function builderCallBehind(receiver, pattern, facts) {
|
|
|
3958
4184
|
return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
|
|
3959
4185
|
}
|
|
3960
4186
|
function urlIn(args, pattern, options, site) {
|
|
4187
|
+
return pathAt(urlNodeIn(args, pattern), options, site);
|
|
4188
|
+
}
|
|
4189
|
+
function urlNodeIn(args, pattern) {
|
|
3961
4190
|
const keyword = pattern.url.keyword;
|
|
3962
|
-
|
|
3963
|
-
return pathAt(written, options, site);
|
|
4191
|
+
return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
|
|
3964
4192
|
}
|
|
3965
4193
|
function pathAt(written, options, site) {
|
|
3966
4194
|
if (written === void 0) {
|
|
@@ -4082,11 +4310,11 @@ function namedParameters(db) {
|
|
|
4082
4310
|
}
|
|
4083
4311
|
const named2 = /* @__PURE__ */ new Map();
|
|
4084
4312
|
namedByDb.set(db, named2);
|
|
4085
|
-
const
|
|
4086
|
-
if (
|
|
4313
|
+
const objects = db.facts("environmentObject").map((row) => String(row[0]));
|
|
4314
|
+
if (objects.length === 0) {
|
|
4087
4315
|
return named2;
|
|
4088
4316
|
}
|
|
4089
|
-
|
|
4317
|
+
resolveEnvObjects(db, objects);
|
|
4090
4318
|
for (const row of db.facts("wantedParamNamesEnv")) {
|
|
4091
4319
|
const found = named2.get(String(row[0]));
|
|
4092
4320
|
if (found === void 0) {
|
|
@@ -4097,38 +4325,44 @@ function namedParameters(db) {
|
|
|
4097
4325
|
}
|
|
4098
4326
|
return named2;
|
|
4099
4327
|
}
|
|
4100
|
-
function
|
|
4101
|
-
for (const site of envNameSites(root)) {
|
|
4102
|
-
const key = nodeId(file, site.read);
|
|
4103
|
-
db.add("readsEnvNamed", [
|
|
4104
|
-
key,
|
|
4105
|
-
readKey(file, site.name, enclosingMethod(site.name))
|
|
4106
|
-
]);
|
|
4107
|
-
if (site.defaulted) {
|
|
4108
|
-
db.add(ENV_DEFAULTED, [key]);
|
|
4109
|
-
}
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
function envNameSites(root) {
|
|
4113
|
-
const sites = [];
|
|
4328
|
+
function emitEnvFacts(db, file, root) {
|
|
4114
4329
|
walkDescendants2(root, null, {
|
|
4115
4330
|
at: (node) => {
|
|
4331
|
+
if (isEnv(node) && handsOnward(node)) {
|
|
4332
|
+
db.add("environmentObject", [
|
|
4333
|
+
readKey(file, node, enclosingDefinition(node))
|
|
4334
|
+
]);
|
|
4335
|
+
}
|
|
4116
4336
|
const site = envSiteAt(node);
|
|
4117
|
-
if (site !== null && stringLiteralValue(site.name) === null) {
|
|
4118
|
-
|
|
4337
|
+
if (site !== null && site.defaulted && stringLiteralValue(site.name) === null) {
|
|
4338
|
+
db.add(ENV_DEFAULTED, [nodeId(file, site.read)]);
|
|
4119
4339
|
}
|
|
4120
4340
|
},
|
|
4121
4341
|
into: () => null
|
|
4122
4342
|
});
|
|
4123
|
-
|
|
4343
|
+
}
|
|
4344
|
+
function handsOnward(node) {
|
|
4345
|
+
const parent = node.parent;
|
|
4346
|
+
if (parent === null) {
|
|
4347
|
+
return true;
|
|
4348
|
+
}
|
|
4349
|
+
if (parent.type === "element_reference" || parent.type === "call") {
|
|
4350
|
+
const site = envSiteAt(parent);
|
|
4351
|
+
return site === null || stringLiteralValue(site.name) === null;
|
|
4352
|
+
}
|
|
4353
|
+
return true;
|
|
4124
4354
|
}
|
|
4125
4355
|
function helperReads(calls, facts) {
|
|
4126
4356
|
const { db, file, named: named2 } = facts;
|
|
4127
4357
|
const callees = new NodeMap();
|
|
4128
4358
|
for (const call of calls) {
|
|
4129
|
-
const
|
|
4130
|
-
|
|
4131
|
-
|
|
4359
|
+
const enclosing = enclosingDefinition(call);
|
|
4360
|
+
const keys = [
|
|
4361
|
+
calleeKeyOf(file, call, enclosing),
|
|
4362
|
+
invokedKeyOf(file, call, enclosing)
|
|
4363
|
+
].filter((key) => key !== null);
|
|
4364
|
+
if (keys.length > 0) {
|
|
4365
|
+
callees.set(call, keys);
|
|
4132
4366
|
}
|
|
4133
4367
|
}
|
|
4134
4368
|
const reads = new NodeMap();
|
|
@@ -4137,7 +4371,7 @@ function helperReads(calls, facts) {
|
|
|
4137
4371
|
}
|
|
4138
4372
|
resolveValues(
|
|
4139
4373
|
db,
|
|
4140
|
-
[...callees].
|
|
4374
|
+
[...callees].flatMap(([, keys]) => keys)
|
|
4141
4375
|
);
|
|
4142
4376
|
for (const [call, callee] of callees) {
|
|
4143
4377
|
const args = namingArguments(db, named2, call, callee);
|
|
@@ -4151,10 +4385,10 @@ function helperReads(calls, facts) {
|
|
|
4151
4385
|
}
|
|
4152
4386
|
return reads;
|
|
4153
4387
|
}
|
|
4154
|
-
function namingArguments(db, named2, call,
|
|
4388
|
+
function namingArguments(db, named2, call, callees) {
|
|
4155
4389
|
const { positional, keyword } = readCallArgs(field(call, "arguments"));
|
|
4156
4390
|
const found = [];
|
|
4157
|
-
for (const func of resolvedFunctions(db,
|
|
4391
|
+
for (const func of callees.flatMap((key) => resolvedFunctions(db, key))) {
|
|
4158
4392
|
for (const row of db.lookup("paramOf", 0, func)) {
|
|
4159
4393
|
const sites = named2.get(String(row[2]));
|
|
4160
4394
|
const argument = positional[Number(row[1])];
|
|
@@ -4418,6 +4652,9 @@ function methodNamesOf(args, filter, facts) {
|
|
|
4418
4652
|
const value = named2 === void 0 ? null : stringValueOf(named2, facts);
|
|
4419
4653
|
return value === null ? [] : [value];
|
|
4420
4654
|
}
|
|
4655
|
+
return symbolArgumentNames(args, facts);
|
|
4656
|
+
}
|
|
4657
|
+
function symbolArgumentNames(args, facts) {
|
|
4421
4658
|
return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
|
|
4422
4659
|
}
|
|
4423
4660
|
function skipsOf(statement, filterName, facts) {
|
|
@@ -4456,19 +4693,315 @@ function actionsUnder(node, facts) {
|
|
|
4456
4693
|
}
|
|
4457
4694
|
|
|
4458
4695
|
// src/storage.ts
|
|
4459
|
-
import { storageBinding } from "@suss/ir-core";
|
|
4696
|
+
import { storageBinding as storageBinding2 } from "@suss/ir-core";
|
|
4697
|
+
import { askResolution as askResolution3 } from "@suss/resolution";
|
|
4698
|
+
|
|
4699
|
+
// src/baseClass.ts
|
|
4460
4700
|
import { askResolution as askResolution2 } from "@suss/resolution";
|
|
4461
|
-
function
|
|
4462
|
-
|
|
4701
|
+
function classBehind(facts, file, constant2) {
|
|
4702
|
+
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
4703
|
+
const bound = new Set(
|
|
4704
|
+
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
4705
|
+
);
|
|
4706
|
+
return bound.size === 1 ? [...bound][0] : void 0;
|
|
4707
|
+
}
|
|
4708
|
+
function reachesBase(facts, classKey, bases) {
|
|
4709
|
+
askResolution2(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4710
|
+
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4463
4711
|
}
|
|
4712
|
+
function enclosingClassKey(node, file) {
|
|
4713
|
+
let current = node.parent;
|
|
4714
|
+
while (current !== null) {
|
|
4715
|
+
if (NESTING_TYPES.has(current.type)) {
|
|
4716
|
+
return nodeId(file, current);
|
|
4717
|
+
}
|
|
4718
|
+
current = current.parent;
|
|
4719
|
+
}
|
|
4720
|
+
return null;
|
|
4721
|
+
}
|
|
4722
|
+
|
|
4723
|
+
// src/loaders.ts
|
|
4464
4724
|
function receiverOf(node) {
|
|
4465
4725
|
return node.type === "call" ? field(node, "receiver") : null;
|
|
4466
4726
|
}
|
|
4467
4727
|
function methodOf(node) {
|
|
4468
4728
|
return field(node, "method")?.text ?? "";
|
|
4469
4729
|
}
|
|
4730
|
+
function isLoader(node, loader) {
|
|
4731
|
+
if (node === null) {
|
|
4732
|
+
return false;
|
|
4733
|
+
}
|
|
4734
|
+
if (node.type === "identifier") {
|
|
4735
|
+
return node.text === loader.loader;
|
|
4736
|
+
}
|
|
4737
|
+
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
4738
|
+
}
|
|
4739
|
+
function loaderPick(call, loader) {
|
|
4740
|
+
const method = methodOf(call);
|
|
4741
|
+
const receiver = receiverOf(call);
|
|
4742
|
+
if (receiver === null) {
|
|
4743
|
+
return loader.shortcuts.includes(method) ? call : null;
|
|
4744
|
+
}
|
|
4745
|
+
if (!loader.reads.includes(method) || receiver.type !== "call") {
|
|
4746
|
+
return null;
|
|
4747
|
+
}
|
|
4748
|
+
const picks = methodOf(receiver) === loader.pick && isLoader(receiverOf(receiver), loader);
|
|
4749
|
+
return picks ? receiver : null;
|
|
4750
|
+
}
|
|
4751
|
+
function pickedSource(call, loader) {
|
|
4752
|
+
const source = loader.source;
|
|
4753
|
+
const pick = source === void 0 ? null : loaderPick(call, loader);
|
|
4754
|
+
if (source === void 0 || pick === null || methodOf(pick) !== loader.pick) {
|
|
4755
|
+
return null;
|
|
4756
|
+
}
|
|
4757
|
+
const args = field(pick, "arguments");
|
|
4758
|
+
const given = args === null ? void 0 : args.namedChildren[source.at];
|
|
4759
|
+
return given?.type === "constant" || given?.type === "scope_resolution" ? given : null;
|
|
4760
|
+
}
|
|
4761
|
+
|
|
4762
|
+
// src/rawSql.ts
|
|
4763
|
+
import { storageBinding } from "@suss/ir-core";
|
|
4764
|
+
import { readSqlAccess, splitQualifiedTable, sqlFromParts } from "@suss/sql";
|
|
4765
|
+
import { piecesOf as piecesOf2 } from "@suss/values";
|
|
4766
|
+
var MOST_HOPS = 8;
|
|
4767
|
+
var NOWHERE = { scope: null, container: null };
|
|
4768
|
+
function rawSqlEffects(call, options) {
|
|
4769
|
+
for (const pattern of options.patterns) {
|
|
4770
|
+
const effects = patternEffects(call, pattern, options);
|
|
4771
|
+
if (effects.length > 0) {
|
|
4772
|
+
return effects;
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
return [];
|
|
4776
|
+
}
|
|
4777
|
+
function patternEffects(call, pattern, options) {
|
|
4778
|
+
const receiver = field(call, "receiver");
|
|
4779
|
+
if (receiver === null) {
|
|
4780
|
+
return [];
|
|
4781
|
+
}
|
|
4782
|
+
const address = addressBehind(receiver, pattern, options, 0);
|
|
4783
|
+
if (address === null) {
|
|
4784
|
+
return [];
|
|
4785
|
+
}
|
|
4786
|
+
const method = field(call, "method")?.text ?? "";
|
|
4787
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
4788
|
+
const place = pattern.statements?.[method];
|
|
4789
|
+
if (place !== void 0) {
|
|
4790
|
+
const statement = statementAt(
|
|
4791
|
+
args,
|
|
4792
|
+
place,
|
|
4793
|
+
options.facts,
|
|
4794
|
+
pattern.bindPlaceholder
|
|
4795
|
+
);
|
|
4796
|
+
return statementEffects(call, statement, address, pattern);
|
|
4797
|
+
}
|
|
4798
|
+
return rowCallEffects(call, args, address, pattern, options);
|
|
4799
|
+
}
|
|
4800
|
+
function statementEffects(call, statement, address, store) {
|
|
4801
|
+
if (statement === null) {
|
|
4802
|
+
return [];
|
|
4803
|
+
}
|
|
4804
|
+
return readSqlAccess(statement, { dialect: store.dialect }).map(
|
|
4805
|
+
(access) => effectOf(
|
|
4806
|
+
call,
|
|
4807
|
+
store,
|
|
4808
|
+
{
|
|
4809
|
+
scope: innermost(access.qualifier) ?? address.scope,
|
|
4810
|
+
container: access.table
|
|
4811
|
+
},
|
|
4812
|
+
{
|
|
4813
|
+
kind: access.kind,
|
|
4814
|
+
fields: access.fields,
|
|
4815
|
+
selector: access.selector
|
|
4816
|
+
}
|
|
4817
|
+
)
|
|
4818
|
+
);
|
|
4819
|
+
}
|
|
4820
|
+
function innermost(qualifier) {
|
|
4821
|
+
return qualifier[qualifier.length - 1] ?? null;
|
|
4822
|
+
}
|
|
4823
|
+
function rowCallEffects(call, args, address, pattern, options) {
|
|
4824
|
+
const method = field(call, "method")?.text ?? "";
|
|
4825
|
+
const rowCall = pattern.rowCalls?.[method];
|
|
4826
|
+
if (rowCall === void 0) {
|
|
4827
|
+
return [];
|
|
4828
|
+
}
|
|
4829
|
+
const given = rowCall.container === void 0 ? null : namedTable(stringAt(args, rowCall.container, options.facts));
|
|
4830
|
+
const reached = {
|
|
4831
|
+
scope: given?.scope ?? address.scope,
|
|
4832
|
+
container: given?.container ?? address.container
|
|
4833
|
+
};
|
|
4834
|
+
if (reached.container === null) {
|
|
4835
|
+
return [];
|
|
4836
|
+
}
|
|
4837
|
+
return [
|
|
4838
|
+
effectOf(call, pattern, reached, {
|
|
4839
|
+
kind: rowCall.kind,
|
|
4840
|
+
fields: [],
|
|
4841
|
+
selector: []
|
|
4842
|
+
})
|
|
4843
|
+
];
|
|
4844
|
+
}
|
|
4845
|
+
function namedTable(name) {
|
|
4846
|
+
const split = name === null ? null : splitQualifiedTable(name);
|
|
4847
|
+
return split === null ? null : { scope: innermost(split.qualifier), container: split.table };
|
|
4848
|
+
}
|
|
4849
|
+
function effectOf(call, store, address, access) {
|
|
4850
|
+
const operation = field(call, "method")?.text ?? "";
|
|
4851
|
+
return {
|
|
4852
|
+
type: "interaction",
|
|
4853
|
+
binding: storageBinding({
|
|
4854
|
+
recognition: "ruby-raw-sql",
|
|
4855
|
+
storageSystem: store.storageSystem,
|
|
4856
|
+
scope: address.scope ?? store.scope ?? "default",
|
|
4857
|
+
container: address.container
|
|
4858
|
+
}),
|
|
4859
|
+
callee: call.text,
|
|
4860
|
+
interaction: {
|
|
4861
|
+
class: "storage-access",
|
|
4862
|
+
kind: access.kind,
|
|
4863
|
+
fields: access.fields,
|
|
4864
|
+
operation,
|
|
4865
|
+
...access.selector.length > 0 ? { selector: access.selector } : {}
|
|
4866
|
+
}
|
|
4867
|
+
};
|
|
4868
|
+
}
|
|
4869
|
+
function addressBehind(receiver, pattern, options, hops) {
|
|
4870
|
+
if (hops > MOST_HOPS) {
|
|
4871
|
+
return null;
|
|
4872
|
+
}
|
|
4873
|
+
if (receiver.type !== "call") {
|
|
4874
|
+
const written = writtenNodeOf(receiver, options.facts);
|
|
4875
|
+
if (written !== null && written.id !== receiver.id) {
|
|
4876
|
+
return addressBehind(written, pattern, options, hops + 1);
|
|
4877
|
+
}
|
|
4878
|
+
return receiver.type === "identifier" && buildsOnOwnClass(receiver, receiver.text, pattern, options) ? NOWHERE : null;
|
|
4879
|
+
}
|
|
4880
|
+
const method = field(receiver, "method")?.text ?? "";
|
|
4881
|
+
const inner = field(receiver, "receiver");
|
|
4882
|
+
if (inner === null) {
|
|
4883
|
+
return buildsOnOwnClass(receiver, method, pattern, options) ? NOWHERE : null;
|
|
4884
|
+
}
|
|
4885
|
+
if (namesConstant2(inner, pattern.constantName) || namesSubclass(inner, pattern, options)) {
|
|
4886
|
+
return pattern.clientBuilders.includes(method) ? NOWHERE : null;
|
|
4887
|
+
}
|
|
4888
|
+
const addressing = pattern.addressing?.[method];
|
|
4889
|
+
if (addressing === void 0 || inner === null) {
|
|
4890
|
+
return null;
|
|
4891
|
+
}
|
|
4892
|
+
const behind = addressBehind(inner, pattern, options, hops + 1);
|
|
4893
|
+
if (behind === null) {
|
|
4894
|
+
return null;
|
|
4895
|
+
}
|
|
4896
|
+
const named2 = stringAt(
|
|
4897
|
+
readCallArgs(field(receiver, "arguments")),
|
|
4898
|
+
addressing,
|
|
4899
|
+
options.facts
|
|
4900
|
+
);
|
|
4901
|
+
if (addressing.says === "scope") {
|
|
4902
|
+
return { scope: named2 ?? behind.scope, container: behind.container };
|
|
4903
|
+
}
|
|
4904
|
+
const table = namedTable(named2);
|
|
4905
|
+
return {
|
|
4906
|
+
scope: table?.scope ?? behind.scope,
|
|
4907
|
+
container: table?.container ?? behind.container
|
|
4908
|
+
};
|
|
4909
|
+
}
|
|
4910
|
+
function namesConstant2(receiver, constantName2) {
|
|
4911
|
+
if (receiver.type === "constant") {
|
|
4912
|
+
return receiver.text === constantName2;
|
|
4913
|
+
}
|
|
4914
|
+
return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
|
|
4915
|
+
}
|
|
4916
|
+
function namesSubclass(receiver, pattern, options) {
|
|
4917
|
+
const bases = pattern.baseClasses ?? [];
|
|
4918
|
+
const facts = options.facts;
|
|
4919
|
+
const file = options.file;
|
|
4920
|
+
if (bases.length === 0 || facts === void 0 || file === void 0 || receiver.type !== "constant" && receiver.type !== "scope_resolution") {
|
|
4921
|
+
return false;
|
|
4922
|
+
}
|
|
4923
|
+
const classKey = classBehind(facts, file, receiver);
|
|
4924
|
+
return classKey !== void 0 && reachesBase(facts, classKey, bases);
|
|
4925
|
+
}
|
|
4926
|
+
function buildsOnOwnClass(node, method, pattern, options) {
|
|
4927
|
+
const bases = pattern.baseClasses ?? [];
|
|
4928
|
+
const facts = options.facts;
|
|
4929
|
+
const file = options.file;
|
|
4930
|
+
if (bases.length === 0 || facts === void 0 || file === void 0 || !pattern.clientBuilders.includes(method)) {
|
|
4931
|
+
return false;
|
|
4932
|
+
}
|
|
4933
|
+
const classKey = enclosingClassKey(node, file);
|
|
4934
|
+
return classKey !== null && reachesBase(facts, classKey, bases);
|
|
4935
|
+
}
|
|
4936
|
+
function argumentAt(args, place) {
|
|
4937
|
+
const keyword = place.keyword;
|
|
4938
|
+
return (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[place.at];
|
|
4939
|
+
}
|
|
4940
|
+
function stringAt(args, place, facts) {
|
|
4941
|
+
const written = argumentAt(args, place);
|
|
4942
|
+
if (written === void 0) {
|
|
4943
|
+
return null;
|
|
4944
|
+
}
|
|
4945
|
+
const pieces = piecesOf2(evaluatedValue(written, facts));
|
|
4946
|
+
const only = pieces.length === 1 ? pieces[0] : void 0;
|
|
4947
|
+
if (only === void 0 || only.kind !== "text" || only.options.length !== 1) {
|
|
4948
|
+
return null;
|
|
4949
|
+
}
|
|
4950
|
+
return only.options[0] ?? null;
|
|
4951
|
+
}
|
|
4952
|
+
function statementAt(args, place, facts, bindPlaceholder) {
|
|
4953
|
+
const written = argumentAt(args, place);
|
|
4954
|
+
if (written === void 0) {
|
|
4955
|
+
return null;
|
|
4956
|
+
}
|
|
4957
|
+
const parts = literalParts(statementValue(evaluatedValue(written, facts)));
|
|
4958
|
+
if (parts === null) {
|
|
4959
|
+
return null;
|
|
4960
|
+
}
|
|
4961
|
+
const statement = sqlFromParts(bindsSplit(parts, bindPlaceholder));
|
|
4962
|
+
return statement.trim() === "" ? null : statement;
|
|
4963
|
+
}
|
|
4964
|
+
function bindsSplit(parts, bindPlaceholder) {
|
|
4965
|
+
if (bindPlaceholder === void 0 || bindPlaceholder === "") {
|
|
4966
|
+
return [...parts];
|
|
4967
|
+
}
|
|
4968
|
+
return parts.flatMap((part) => part.split(bindPlaceholder));
|
|
4969
|
+
}
|
|
4970
|
+
function statementValue(value) {
|
|
4971
|
+
if (value.kind !== "sequence") {
|
|
4972
|
+
return value;
|
|
4973
|
+
}
|
|
4974
|
+
const first = value.items[0];
|
|
4975
|
+
return first === void 0 ? value : first.value;
|
|
4976
|
+
}
|
|
4977
|
+
function literalParts(value) {
|
|
4978
|
+
if (value.kind !== "string") {
|
|
4979
|
+
return null;
|
|
4980
|
+
}
|
|
4981
|
+
const parts = [""];
|
|
4982
|
+
for (const piece of value.pieces) {
|
|
4983
|
+
const only = piece.kind === "text" ? piece.options : [];
|
|
4984
|
+
if (only.length === 1) {
|
|
4985
|
+
parts[parts.length - 1] += only[0] ?? "";
|
|
4986
|
+
continue;
|
|
4987
|
+
}
|
|
4988
|
+
parts.push("");
|
|
4989
|
+
}
|
|
4990
|
+
return parts;
|
|
4991
|
+
}
|
|
4992
|
+
|
|
4993
|
+
// src/storage.ts
|
|
4994
|
+
function children3(node) {
|
|
4995
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
4996
|
+
}
|
|
4997
|
+
function receiverOf2(node) {
|
|
4998
|
+
return node.type === "call" ? field(node, "receiver") : null;
|
|
4999
|
+
}
|
|
5000
|
+
function methodOf2(node) {
|
|
5001
|
+
return field(node, "method")?.text ?? "";
|
|
5002
|
+
}
|
|
4470
5003
|
function rootConstant(node) {
|
|
4471
|
-
const receiver =
|
|
5004
|
+
const receiver = receiverOf2(node);
|
|
4472
5005
|
if (receiver === null) {
|
|
4473
5006
|
return null;
|
|
4474
5007
|
}
|
|
@@ -4483,17 +5016,6 @@ function isConstant(node) {
|
|
|
4483
5016
|
function constantName(constant2) {
|
|
4484
5017
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
4485
5018
|
}
|
|
4486
|
-
function reachesBase(facts, classKey, bases) {
|
|
4487
|
-
askResolution2(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4488
|
-
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4489
|
-
}
|
|
4490
|
-
function classBehind(facts, file, constant2) {
|
|
4491
|
-
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
4492
|
-
const bound = new Set(
|
|
4493
|
-
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
4494
|
-
);
|
|
4495
|
-
return bound.size === 1 ? [...bound][0] : void 0;
|
|
4496
|
-
}
|
|
4497
5019
|
function modelPattern(constant2, file, options) {
|
|
4498
5020
|
const classKey = classBehind(options.facts, file, constant2);
|
|
4499
5021
|
if (classKey === void 0) {
|
|
@@ -4508,7 +5030,7 @@ function chainLinks(call) {
|
|
|
4508
5030
|
let current = call;
|
|
4509
5031
|
while (current !== null && current.type === "call") {
|
|
4510
5032
|
links.push(current);
|
|
4511
|
-
current =
|
|
5033
|
+
current = receiverOf2(current);
|
|
4512
5034
|
}
|
|
4513
5035
|
return links;
|
|
4514
5036
|
}
|
|
@@ -4521,9 +5043,17 @@ function kindOfCall(pattern, method) {
|
|
|
4521
5043
|
}
|
|
4522
5044
|
return void 0;
|
|
4523
5045
|
}
|
|
5046
|
+
function libraryCallOf(pattern, method) {
|
|
5047
|
+
const place = pattern.statements?.[method];
|
|
5048
|
+
if (place !== void 0) {
|
|
5049
|
+
return { how: "statement", place };
|
|
5050
|
+
}
|
|
5051
|
+
const kind = kindOfCall(pattern, method);
|
|
5052
|
+
return kind === void 0 ? void 0 : { how: "rows", kind };
|
|
5053
|
+
}
|
|
4524
5054
|
function someLibraryDefines(options, method) {
|
|
4525
5055
|
return options.patterns.some(
|
|
4526
|
-
(pattern) =>
|
|
5056
|
+
(pattern) => libraryCallOf(pattern, method) !== void 0
|
|
4527
5057
|
);
|
|
4528
5058
|
}
|
|
4529
5059
|
function argumentsOf2(call) {
|
|
@@ -4558,7 +5088,7 @@ function hasPositionalArgument(call, facts) {
|
|
|
4558
5088
|
);
|
|
4559
5089
|
}
|
|
4560
5090
|
function columnsAskedFor(call, pattern, facts) {
|
|
4561
|
-
if (!(pattern.columnArguments ?? []).includes(
|
|
5091
|
+
if (!(pattern.columnArguments ?? []).includes(methodOf2(call))) {
|
|
4562
5092
|
return [];
|
|
4563
5093
|
}
|
|
4564
5094
|
return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
|
|
@@ -4568,14 +5098,14 @@ function primaryKeySelector(call, pattern, facts) {
|
|
|
4568
5098
|
if (byPrimaryKey === void 0) {
|
|
4569
5099
|
return [];
|
|
4570
5100
|
}
|
|
4571
|
-
const picks = byPrimaryKey.methods.includes(
|
|
5101
|
+
const picks = byPrimaryKey.methods.includes(methodOf2(call)) && hasPositionalArgument(call, facts);
|
|
4572
5102
|
return picks ? [byPrimaryKey.column] : [];
|
|
4573
5103
|
}
|
|
4574
5104
|
function selectorOf(call, pattern, facts) {
|
|
4575
5105
|
const picked = [];
|
|
4576
5106
|
for (const link of chainLinks(call)) {
|
|
4577
5107
|
picked.push(...primaryKeySelector(link, pattern, facts));
|
|
4578
|
-
if (pattern.reads.includes(
|
|
5108
|
+
if (pattern.reads.includes(methodOf2(link))) {
|
|
4579
5109
|
picked.push(...keywordKeys(link, facts));
|
|
4580
5110
|
}
|
|
4581
5111
|
}
|
|
@@ -4592,10 +5122,10 @@ function fieldsOf(call, pattern, kind, facts) {
|
|
|
4592
5122
|
];
|
|
4593
5123
|
}
|
|
4594
5124
|
function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
4595
|
-
const operation =
|
|
5125
|
+
const operation = methodOf2(call);
|
|
4596
5126
|
return {
|
|
4597
5127
|
type: "interaction",
|
|
4598
|
-
binding:
|
|
5128
|
+
binding: storageBinding2({
|
|
4599
5129
|
recognition: "ruby-storage",
|
|
4600
5130
|
storageSystem: pattern.storageSystem,
|
|
4601
5131
|
scope: "default",
|
|
@@ -4612,7 +5142,7 @@ function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
|
4612
5142
|
};
|
|
4613
5143
|
}
|
|
4614
5144
|
function classSettledOn(facts, key) {
|
|
4615
|
-
|
|
5145
|
+
askResolution3(facts, [key], "wanted", RUBY_PROGRAM);
|
|
4616
5146
|
const objects = new Set(
|
|
4617
5147
|
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
4618
5148
|
);
|
|
@@ -4644,7 +5174,7 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4644
5174
|
if (constant2 !== null) {
|
|
4645
5175
|
return constantReceiver(constant2, file, options.facts);
|
|
4646
5176
|
}
|
|
4647
|
-
const receiver =
|
|
5177
|
+
const receiver = receiverOf2(call);
|
|
4648
5178
|
if (receiver === null) {
|
|
4649
5179
|
return void 0;
|
|
4650
5180
|
}
|
|
@@ -4652,60 +5182,91 @@ function receiverClass(call, file, options, enclosing) {
|
|
|
4652
5182
|
}
|
|
4653
5183
|
function libraryCallIn(call, options) {
|
|
4654
5184
|
return chainLinks(call).find(
|
|
4655
|
-
(link) => someLibraryDefines(options,
|
|
5185
|
+
(link) => someLibraryDefines(options, methodOf2(link))
|
|
4656
5186
|
) ?? null;
|
|
4657
5187
|
}
|
|
5188
|
+
function modelStatementEffects(call, place, pattern, options) {
|
|
5189
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
5190
|
+
return statementEffects(
|
|
5191
|
+
call,
|
|
5192
|
+
statementAt(args, place, options.facts, pattern.bindPlaceholder),
|
|
5193
|
+
NOWHERE,
|
|
5194
|
+
{ storageSystem: pattern.storageSystem, dialect: pattern.storageSystem }
|
|
5195
|
+
);
|
|
5196
|
+
}
|
|
5197
|
+
function callbacksRunBy(facts, classKey, pattern, writeMethod) {
|
|
5198
|
+
const events = pattern.callbacks?.eventOf[writeMethod] ?? [];
|
|
5199
|
+
if (events.length === 0) {
|
|
5200
|
+
return [];
|
|
5201
|
+
}
|
|
5202
|
+
askResolution3(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
5203
|
+
const found = /* @__PURE__ */ new Map();
|
|
5204
|
+
for (const row of facts.lookup("wantedCallbackMethod", 0, classKey)) {
|
|
5205
|
+
if (events.includes(String(row[1]))) {
|
|
5206
|
+
const key = String(row[3]);
|
|
5207
|
+
found.set(key, { name: String(row[2]), key });
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
return [...found.values()];
|
|
5211
|
+
}
|
|
5212
|
+
function callbackEffect(callback) {
|
|
5213
|
+
return {
|
|
5214
|
+
type: "invocation",
|
|
5215
|
+
callee: callback.name,
|
|
5216
|
+
args: [],
|
|
5217
|
+
async: false
|
|
5218
|
+
};
|
|
5219
|
+
}
|
|
4658
5220
|
function modelCallEffects(call, file, options, enclosing) {
|
|
4659
5221
|
const worked = libraryCallIn(call, options);
|
|
4660
5222
|
if (worked === null) {
|
|
4661
5223
|
return [];
|
|
4662
5224
|
}
|
|
4663
|
-
const method =
|
|
5225
|
+
const method = methodOf2(worked);
|
|
4664
5226
|
const target = receiverClass(worked, file, options, enclosing);
|
|
4665
5227
|
if (target === void 0) {
|
|
4666
5228
|
return [];
|
|
4667
5229
|
}
|
|
4668
5230
|
for (const pattern of options.patterns) {
|
|
4669
|
-
const
|
|
4670
|
-
if (
|
|
5231
|
+
const library = libraryCallOf(pattern, method);
|
|
5232
|
+
if (library === void 0 || !reachesBase(options.facts, target.classKey, pattern.baseClasses)) {
|
|
4671
5233
|
continue;
|
|
4672
5234
|
}
|
|
4673
5235
|
if (projectDeclares(options.facts, target.classKey, method)) {
|
|
4674
5236
|
return [];
|
|
4675
5237
|
}
|
|
5238
|
+
if (library.how === "statement") {
|
|
5239
|
+
return modelStatementEffects(worked, library.place, pattern, options);
|
|
5240
|
+
}
|
|
5241
|
+
const ran = library.kind === "write" ? callbacksRunBy(options.facts, target.classKey, pattern, method) : [];
|
|
4676
5242
|
return [
|
|
4677
5243
|
storageEffect(
|
|
4678
5244
|
worked,
|
|
4679
5245
|
target.container,
|
|
4680
5246
|
pattern,
|
|
4681
|
-
kind,
|
|
5247
|
+
library.kind,
|
|
4682
5248
|
selectorOf(worked, pattern, options.facts),
|
|
4683
|
-
fieldsOf(worked, pattern, kind, options.facts)
|
|
4684
|
-
)
|
|
5249
|
+
fieldsOf(worked, pattern, library.kind, options.facts)
|
|
5250
|
+
),
|
|
5251
|
+
...ran.map(callbackEffect)
|
|
4685
5252
|
];
|
|
4686
5253
|
}
|
|
4687
5254
|
return [];
|
|
4688
5255
|
}
|
|
4689
|
-
function
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
return node.text === loader.loader;
|
|
4695
|
-
}
|
|
4696
|
-
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
4697
|
-
}
|
|
4698
|
-
function loaderPick(call, loader) {
|
|
4699
|
-
const method = methodOf(call);
|
|
4700
|
-
const receiver = receiverOf(call);
|
|
4701
|
-
if (receiver === null) {
|
|
4702
|
-
return loader.shortcuts.includes(method) ? call : null;
|
|
5256
|
+
function callbacksReached(call, file, options, enclosing = null) {
|
|
5257
|
+
const worked = libraryCallIn(call, options);
|
|
5258
|
+
const target = worked === null ? void 0 : receiverClass(worked, file, options, enclosing);
|
|
5259
|
+
if (worked === null || target === void 0) {
|
|
5260
|
+
return [];
|
|
4703
5261
|
}
|
|
4704
|
-
|
|
4705
|
-
|
|
5262
|
+
const method = methodOf2(worked);
|
|
5263
|
+
for (const pattern of options.patterns) {
|
|
5264
|
+
if (libraryCallOf(pattern, method)?.how !== "rows" || kindOfCall(pattern, method) !== "write" || !reachesBase(options.facts, target.classKey, pattern.baseClasses) || projectDeclares(options.facts, target.classKey, method)) {
|
|
5265
|
+
continue;
|
|
5266
|
+
}
|
|
5267
|
+
return callbacksRunBy(options.facts, target.classKey, pattern, method);
|
|
4706
5268
|
}
|
|
4707
|
-
|
|
4708
|
-
return picks ? receiver : null;
|
|
5269
|
+
return [];
|
|
4709
5270
|
}
|
|
4710
5271
|
function loaderCallEffects(call, file, options) {
|
|
4711
5272
|
const effects = [];
|
|
@@ -4731,10 +5292,24 @@ function loaderCallEffects(call, file, options) {
|
|
|
4731
5292
|
}
|
|
4732
5293
|
function effectsOfCall(call, file, options, enclosing) {
|
|
4733
5294
|
const onModel = modelCallEffects(call, file, options, enclosing);
|
|
4734
|
-
|
|
5295
|
+
if (onModel.length > 0) {
|
|
5296
|
+
return onModel;
|
|
5297
|
+
}
|
|
5298
|
+
const throughLoader = loaderCallEffects(call, file, options);
|
|
5299
|
+
if (throughLoader.length > 0) {
|
|
5300
|
+
return throughLoader;
|
|
5301
|
+
}
|
|
5302
|
+
return rawSqlEffects(call, {
|
|
5303
|
+
facts: options.facts,
|
|
5304
|
+
patterns: options.rawSql ?? [],
|
|
5305
|
+
file
|
|
5306
|
+
});
|
|
4735
5307
|
}
|
|
4736
5308
|
function storageClaims(call, file, options, enclosing = null) {
|
|
4737
|
-
return options
|
|
5309
|
+
return saysAnything(options) && effectsOfCall(call, file, options, enclosing).length > 0;
|
|
5310
|
+
}
|
|
5311
|
+
function saysAnything(options) {
|
|
5312
|
+
return options.patterns.length > 0 || (options.rawSql ?? []).length > 0;
|
|
4738
5313
|
}
|
|
4739
5314
|
function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
4740
5315
|
const keys = [];
|
|
@@ -4743,7 +5318,7 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4743
5318
|
if (worked === null || rootConstant(worked) !== null) {
|
|
4744
5319
|
continue;
|
|
4745
5320
|
}
|
|
4746
|
-
const receiver =
|
|
5321
|
+
const receiver = receiverOf2(worked);
|
|
4747
5322
|
if (receiver !== null) {
|
|
4748
5323
|
keys.push(readKey(file, receiver, enclosing));
|
|
4749
5324
|
}
|
|
@@ -4751,18 +5326,18 @@ function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
|
4751
5326
|
return keys;
|
|
4752
5327
|
}
|
|
4753
5328
|
function storageEffects(calls, file, options, enclosing = null) {
|
|
4754
|
-
if (options
|
|
5329
|
+
if (!saysAnything(options)) {
|
|
4755
5330
|
return [];
|
|
4756
5331
|
}
|
|
4757
5332
|
const partOfOne = /* @__PURE__ */ new Set();
|
|
4758
5333
|
for (const call of calls) {
|
|
4759
|
-
const receiver =
|
|
5334
|
+
const receiver = receiverOf2(call);
|
|
4760
5335
|
if (receiver !== null) {
|
|
4761
5336
|
partOfOne.add(receiver.id);
|
|
4762
5337
|
}
|
|
4763
5338
|
}
|
|
4764
5339
|
const chains = calls.filter((call) => !partOfOne.has(call.id));
|
|
4765
|
-
|
|
5340
|
+
askResolution3(
|
|
4766
5341
|
options.facts,
|
|
4767
5342
|
receiverKeysToAsk(chains, file, options, enclosing),
|
|
4768
5343
|
"wanted",
|
|
@@ -4887,14 +5462,18 @@ async function discoverUnits(root, options) {
|
|
|
4887
5462
|
file: options.absoluteFile ?? options.filePath
|
|
4888
5463
|
}));
|
|
4889
5464
|
const units = [];
|
|
5465
|
+
const clientOptions = {
|
|
5466
|
+
filePath: options.filePath,
|
|
5467
|
+
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
5468
|
+
};
|
|
5469
|
+
askClientCallReads(
|
|
5470
|
+
root,
|
|
5471
|
+
options.packs.flatMap((pack) => pack.clients ?? []),
|
|
5472
|
+
clientOptions
|
|
5473
|
+
);
|
|
4890
5474
|
for (const pack of options.packs) {
|
|
4891
5475
|
for (const pattern of pack.clients ?? []) {
|
|
4892
|
-
units.push(
|
|
4893
|
-
...clientCallUnits(root, pack, pattern, {
|
|
4894
|
-
filePath: options.filePath,
|
|
4895
|
-
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
4896
|
-
})
|
|
4897
|
-
);
|
|
5476
|
+
units.push(...clientCallUnits(root, pack, pattern, clientOptions));
|
|
4898
5477
|
}
|
|
4899
5478
|
}
|
|
4900
5479
|
for (const info of classes) {
|
|
@@ -6008,6 +6587,47 @@ function buildRubyExtractionReport(args) {
|
|
|
6008
6587
|
});
|
|
6009
6588
|
}
|
|
6010
6589
|
|
|
6590
|
+
// src/facts/callbacks.ts
|
|
6591
|
+
function callbacksIn(patterns) {
|
|
6592
|
+
return patterns.flatMap(
|
|
6593
|
+
(pattern) => pattern.callbacks === void 0 ? [] : [pattern.callbacks]
|
|
6594
|
+
);
|
|
6595
|
+
}
|
|
6596
|
+
function emitClassCallbacks(db, file, classNode, declared) {
|
|
6597
|
+
const body = field(classNode, "body");
|
|
6598
|
+
if (body === null || declared.length === 0) {
|
|
6599
|
+
return;
|
|
6600
|
+
}
|
|
6601
|
+
const classKey = nodeId(file, classNode);
|
|
6602
|
+
for (const statement of runStatements(body)) {
|
|
6603
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
6604
|
+
continue;
|
|
6605
|
+
}
|
|
6606
|
+
const called = field(statement, "method")?.text ?? "";
|
|
6607
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
6608
|
+
for (const callbacks of declared) {
|
|
6609
|
+
const events = eventsOf(args, called, callbacks, db);
|
|
6610
|
+
for (const event of events) {
|
|
6611
|
+
for (const method of symbolArgumentNames(args, db)) {
|
|
6612
|
+
db.add("classCallback", [classKey, event, method]);
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
function eventsOf(args, called, callbacks, db) {
|
|
6619
|
+
const covered = callbacks.registeredBy[called];
|
|
6620
|
+
if (covered === void 0) {
|
|
6621
|
+
return [];
|
|
6622
|
+
}
|
|
6623
|
+
const written = args.keyword[callbacks.eventKeyword];
|
|
6624
|
+
const narrowed = written === void 0 ? null : stringValueOf(written, db);
|
|
6625
|
+
if (narrowed === null) {
|
|
6626
|
+
return covered;
|
|
6627
|
+
}
|
|
6628
|
+
return covered.filter((event) => event === narrowed);
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6011
6631
|
// src/facts.ts
|
|
6012
6632
|
import path3 from "path";
|
|
6013
6633
|
import { SKIP_CHILDREN as SKIP_CHILDREN3, walkDescendants as walkDescendants3 } from "@suss/extractor";
|
|
@@ -6124,6 +6744,10 @@ function calleeSpellings(calls, ctx) {
|
|
|
6124
6744
|
};
|
|
6125
6745
|
}
|
|
6126
6746
|
function resolveCallee(call, site, ctx, read) {
|
|
6747
|
+
const throughLoader = loaderSourceCallee(call, site, ctx);
|
|
6748
|
+
if (throughLoader !== null) {
|
|
6749
|
+
return throughLoader;
|
|
6750
|
+
}
|
|
6127
6751
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
6128
6752
|
if (spelling.kind === "stopped") {
|
|
6129
6753
|
return spelling;
|
|
@@ -6133,6 +6757,21 @@ function resolveCallee(call, site, ctx, read) {
|
|
|
6133
6757
|
}
|
|
6134
6758
|
return asCallee(spelling, outcomeFor(spelling.key, ctx, read), site, ctx);
|
|
6135
6759
|
}
|
|
6760
|
+
function loaderSourceCallee(call, site, ctx) {
|
|
6761
|
+
for (const loader of ctx.loaders) {
|
|
6762
|
+
const constant2 = pickedSource(call, loader);
|
|
6763
|
+
const source = loader.source;
|
|
6764
|
+
if (constant2 === null || source === void 0) {
|
|
6765
|
+
continue;
|
|
6766
|
+
}
|
|
6767
|
+
const classKey = classBehind(ctx.facts, site.file, constant2);
|
|
6768
|
+
const qualifiedName = classKey === void 0 ? void 0 : ctx.classNames.get(classKey);
|
|
6769
|
+
if (qualifiedName !== void 0) {
|
|
6770
|
+
return methodOnAncestryOf(qualifiedName, source.method, ctx);
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
return null;
|
|
6774
|
+
}
|
|
6136
6775
|
function outcomeFor(key, ctx, read) {
|
|
6137
6776
|
return read?.outcomes.get(key) ?? calleeOutcomeOf(ctx.facts, key, RUBY_PROGRAM);
|
|
6138
6777
|
}
|
|
@@ -6314,6 +6953,7 @@ async function reachedFunctions(seeds, options) {
|
|
|
6314
6953
|
const parameterCallsByKey = /* @__PURE__ */ new Map();
|
|
6315
6954
|
const propertyReadsByKey = /* @__PURE__ */ new Map();
|
|
6316
6955
|
const passedPositions = /* @__PURE__ */ new Set();
|
|
6956
|
+
const followedKeys = /* @__PURE__ */ new Set();
|
|
6317
6957
|
for (const seed of seeds) {
|
|
6318
6958
|
seedKeys.add(seed.key);
|
|
6319
6959
|
functionByKey.set(seed.key, {
|
|
@@ -6361,6 +7001,9 @@ async function reachedFunctions(seeds, options) {
|
|
|
6361
7001
|
for (const position of scan.passedPositions) {
|
|
6362
7002
|
passedPositions.add(position);
|
|
6363
7003
|
}
|
|
7004
|
+
if (scan.followed.length > 0) {
|
|
7005
|
+
followedKeys.add(key);
|
|
7006
|
+
}
|
|
6364
7007
|
for (const target of scan.followed) {
|
|
6365
7008
|
const calleeKey = keyOf(target);
|
|
6366
7009
|
if (!functionByKey.has(calleeKey)) {
|
|
@@ -6397,7 +7040,8 @@ async function reachedFunctions(seeds, options) {
|
|
|
6397
7040
|
argTargetsByKey,
|
|
6398
7041
|
parameterCallsByKey,
|
|
6399
7042
|
propertyReadsByKey,
|
|
6400
|
-
passedPositions
|
|
7043
|
+
passedPositions,
|
|
7044
|
+
followedKeys
|
|
6401
7045
|
};
|
|
6402
7046
|
}
|
|
6403
7047
|
function keyOf(target) {
|
|
@@ -6436,13 +7080,9 @@ function methodReferenceSymbol(node) {
|
|
|
6436
7080
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
6437
7081
|
}
|
|
6438
7082
|
function bodyOf2(source, options) {
|
|
6439
|
-
const site =
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
owner: keyOf(source),
|
|
6443
|
-
enclosingQualifiedName: source.enclosingQualifiedName
|
|
6444
|
-
};
|
|
6445
|
-
const written = field(source.node, "body") === null ? [] : bodyCalls(source.node, options.inheritedMethods);
|
|
7083
|
+
const site = siteOf2(source);
|
|
7084
|
+
const read = readableBodyOf(source.node);
|
|
7085
|
+
const written = read === null ? [] : bodyCalls(read, options.inheritedMethods);
|
|
6446
7086
|
const calls = callsReported(
|
|
6447
7087
|
written,
|
|
6448
7088
|
(call) => mightReadAsACall(call, site, options.context)
|
|
@@ -6454,6 +7094,17 @@ function bodyOf2(source, options) {
|
|
|
6454
7094
|
written: calls.map((call) => ({ call, site }))
|
|
6455
7095
|
};
|
|
6456
7096
|
}
|
|
7097
|
+
function readableBodyOf(node) {
|
|
7098
|
+
return node.type === PROGRAM_TYPE ? moduleScopeBody(node) : methodBody(node);
|
|
7099
|
+
}
|
|
7100
|
+
function siteOf2(source) {
|
|
7101
|
+
return {
|
|
7102
|
+
file: source.file,
|
|
7103
|
+
method: source.node.type === PROGRAM_TYPE ? null : source.node,
|
|
7104
|
+
owner: keyOf(source),
|
|
7105
|
+
enclosingQualifiedName: source.enclosingQualifiedName
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
6457
7108
|
function callsMade(asked, site, ctx, spellings) {
|
|
6458
7109
|
return asked.filter(
|
|
6459
7110
|
(call) => !isArglessReceiverCall(call) || readsAsACall(call, site, ctx, spellings)
|
|
@@ -6461,7 +7112,7 @@ function callsMade(asked, site, ctx, spellings) {
|
|
|
6461
7112
|
}
|
|
6462
7113
|
function scanBody(source, ctx, options, read) {
|
|
6463
7114
|
const displayPathOf = options.displayPathOf;
|
|
6464
|
-
if (
|
|
7115
|
+
if (readableBodyOf(source.node) === null) {
|
|
6465
7116
|
return EMPTY_SCAN;
|
|
6466
7117
|
}
|
|
6467
7118
|
const site = read.site;
|
|
@@ -6504,9 +7155,34 @@ function scanBody(source, ctx, options, read) {
|
|
|
6504
7155
|
}
|
|
6505
7156
|
}
|
|
6506
7157
|
};
|
|
7158
|
+
const followCallback = (name, key) => {
|
|
7159
|
+
const target = ctx.definitions.get(key);
|
|
7160
|
+
if (target === void 0) {
|
|
7161
|
+
return;
|
|
7162
|
+
}
|
|
7163
|
+
const targetKey = keyOf(target);
|
|
7164
|
+
if (!seen.has(targetKey)) {
|
|
7165
|
+
seen.add(targetKey);
|
|
7166
|
+
followed2.push(target);
|
|
7167
|
+
}
|
|
7168
|
+
placements.place(name, {
|
|
7169
|
+
file: displayPathOf(target.file),
|
|
7170
|
+
span: spanOf(target.node)
|
|
7171
|
+
});
|
|
7172
|
+
};
|
|
6507
7173
|
for (const call of calls) {
|
|
6508
7174
|
const callee = calleeText(call);
|
|
6509
7175
|
const outcome = resolveCallee(call, site, ctx, read.spellings);
|
|
7176
|
+
if (options.storage !== void 0) {
|
|
7177
|
+
for (const callback of callbacksReached(
|
|
7178
|
+
call,
|
|
7179
|
+
source.file,
|
|
7180
|
+
options.storage,
|
|
7181
|
+
site.method
|
|
7182
|
+
)) {
|
|
7183
|
+
followCallback(callback.name, callback.key);
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
6510
7186
|
const placed = outcome.kind === "followed" ? {
|
|
6511
7187
|
file: displayPathOf(outcome.target.file),
|
|
6512
7188
|
span: spanOf(outcome.target.node)
|
|
@@ -6639,7 +7315,7 @@ function parameterName(param) {
|
|
|
6639
7315
|
|
|
6640
7316
|
// src/reach/context.ts
|
|
6641
7317
|
var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
6642
|
-
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
|
|
7318
|
+
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map(), loaders = []) {
|
|
6643
7319
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
6644
7320
|
const classes = [];
|
|
6645
7321
|
const classNames = /* @__PURE__ */ new Map();
|
|
@@ -6715,7 +7391,8 @@ async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dyna
|
|
|
6715
7391
|
classNames,
|
|
6716
7392
|
definitions,
|
|
6717
7393
|
bodyBlocks,
|
|
6718
|
-
dynamicNames
|
|
7394
|
+
dynamicNames,
|
|
7395
|
+
loaders
|
|
6719
7396
|
};
|
|
6720
7397
|
}
|
|
6721
7398
|
function namedChildren(node) {
|
|
@@ -6783,7 +7460,7 @@ function factsForFile(options) {
|
|
|
6783
7460
|
const db = new Database2();
|
|
6784
7461
|
const bodyBlocks = bodyBlocksIn(options.packs);
|
|
6785
7462
|
emitValueFacts(db, options.file, options.root, bodyBlocks);
|
|
6786
|
-
|
|
7463
|
+
emitEnvFacts(db, options.file, options.root);
|
|
6787
7464
|
emitConstantBindings(db, [
|
|
6788
7465
|
collectFileConstants(
|
|
6789
7466
|
options.file,
|
|
@@ -6800,6 +7477,19 @@ function factsForFile(options) {
|
|
|
6800
7477
|
addPackWords(db, packWordsOf(options.packs));
|
|
6801
7478
|
return db;
|
|
6802
7479
|
}
|
|
7480
|
+
function moduleInitUnit(options) {
|
|
7481
|
+
const raw = moduleInitStructure({
|
|
7482
|
+
name: options.name,
|
|
7483
|
+
file: options.file,
|
|
7484
|
+
range: options.range,
|
|
7485
|
+
effects: options.effects
|
|
7486
|
+
});
|
|
7487
|
+
const branch = raw.branches[0];
|
|
7488
|
+
if (branch !== void 0) {
|
|
7489
|
+
branch.effects = options.calls;
|
|
7490
|
+
}
|
|
7491
|
+
return raw;
|
|
7492
|
+
}
|
|
6803
7493
|
function alreadyDiscovered(seen, raw) {
|
|
6804
7494
|
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
6805
7495
|
const boundary = raw.boundaryBinding === null ? "" : boundaryKey(raw.boundaryBinding) ?? "";
|
|
@@ -6855,7 +7545,7 @@ async function extractRubyProject(options) {
|
|
|
6855
7545
|
}
|
|
6856
7546
|
parsed.push({ file, root });
|
|
6857
7547
|
emitValueFacts(db, file, root, bodyBlocks);
|
|
6858
|
-
|
|
7548
|
+
emitEnvFacts(db, file, root);
|
|
6859
7549
|
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
6860
7550
|
definitions.set(key, method);
|
|
6861
7551
|
}
|
|
@@ -6872,6 +7562,15 @@ async function extractRubyProject(options) {
|
|
|
6872
7562
|
}
|
|
6873
7563
|
bindEvaluator(db, { files: parsed, definitions });
|
|
6874
7564
|
addPackWords(db, packWordsOf(options.packs));
|
|
7565
|
+
const callbacks = callbacksIn(
|
|
7566
|
+
options.packs.flatMap((pack) => pack.storage ?? [])
|
|
7567
|
+
);
|
|
7568
|
+
for (const { file, root } of parsed) {
|
|
7569
|
+
walkDefinitions(
|
|
7570
|
+
root,
|
|
7571
|
+
(info) => emitClassCallbacks(db, file, info.node, callbacks)
|
|
7572
|
+
);
|
|
7573
|
+
}
|
|
6875
7574
|
return readDynamicNames(
|
|
6876
7575
|
db,
|
|
6877
7576
|
new Map(parsed.map(({ file, root }) => [file, root]))
|
|
@@ -6879,11 +7578,17 @@ async function extractRubyProject(options) {
|
|
|
6879
7578
|
});
|
|
6880
7579
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
6881
7580
|
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
6882
|
-
const
|
|
7581
|
+
const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
|
|
7582
|
+
const storage = storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
|
|
7583
|
+
facts: db,
|
|
7584
|
+
patterns: storagePatterns,
|
|
7585
|
+
loaders: loaderPatterns,
|
|
7586
|
+
rawSql: rawSqlPatterns
|
|
7587
|
+
} : void 0;
|
|
6883
7588
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
6884
7589
|
const reachContext = await timer.timeAsync(
|
|
6885
7590
|
"discover",
|
|
6886
|
-
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames)
|
|
7591
|
+
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames, loaderPatterns)
|
|
6887
7592
|
);
|
|
6888
7593
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file) : file;
|
|
6889
7594
|
const seeds = [];
|
|
@@ -6933,26 +7638,34 @@ async function extractRubyProject(options) {
|
|
|
6933
7638
|
});
|
|
6934
7639
|
}
|
|
6935
7640
|
}
|
|
6936
|
-
const
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
7641
|
+
const moduleKey = nodeId(file, root);
|
|
7642
|
+
seedKeys.add(moduleKey);
|
|
7643
|
+
seeds.push({
|
|
7644
|
+
key: moduleKey,
|
|
7645
|
+
file,
|
|
7646
|
+
node: root,
|
|
7647
|
+
enclosingQualifiedName: null
|
|
7648
|
+
});
|
|
7649
|
+
const loadTime = timer.time("discover", () => ({
|
|
7650
|
+
reads: envReadEffects(root, { db, file }),
|
|
7651
|
+
calls: moduleScopeInvocationEffects(
|
|
7652
|
+
root,
|
|
7653
|
+
inheritedMethods,
|
|
7654
|
+
EVERY_ARGLESS_CALL,
|
|
7655
|
+
db
|
|
7656
|
+
)
|
|
7657
|
+
}));
|
|
7658
|
+
found.push({
|
|
7659
|
+
raw: moduleInitUnit({
|
|
7660
|
+
name: path4.basename(displayPath),
|
|
7661
|
+
file: displayPath,
|
|
7662
|
+
range: rangeOf(root),
|
|
7663
|
+
effects: loadTime.reads,
|
|
7664
|
+
calls: loadTime.calls
|
|
7665
|
+
}),
|
|
7666
|
+
seedKey: moduleKey,
|
|
7667
|
+
onlyIfItReaches: loadTime.reads.length === 0
|
|
7668
|
+
});
|
|
6956
7669
|
}
|
|
6957
7670
|
for (const pack of options.packs) {
|
|
6958
7671
|
for (const pattern of pack.discovery) {
|
|
@@ -6990,6 +7703,9 @@ async function extractRubyProject(options) {
|
|
|
6990
7703
|
continue;
|
|
6991
7704
|
}
|
|
6992
7705
|
const { raw, seedKey } = entry;
|
|
7706
|
+
if (entry.onlyIfItReaches === true && (seedKey === null || !reached.followedKeys.has(seedKey))) {
|
|
7707
|
+
continue;
|
|
7708
|
+
}
|
|
6993
7709
|
if (seedKey !== null) {
|
|
6994
7710
|
dropPropertyReads(raw, reached.propertyReadsByKey.get(seedKey));
|
|
6995
7711
|
}
|
|
@@ -7137,7 +7853,7 @@ function width(node) {
|
|
|
7137
7853
|
return node.endIndex - node.startIndex;
|
|
7138
7854
|
}
|
|
7139
7855
|
function askedKey(value) {
|
|
7140
|
-
const enclosing =
|
|
7856
|
+
const enclosing = enclosingDefinition(value.node);
|
|
7141
7857
|
return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
|
|
7142
7858
|
}
|
|
7143
7859
|
var RubyWhySession = class {
|
|
@@ -7352,6 +8068,8 @@ export {
|
|
|
7352
8068
|
resolveConstantFile,
|
|
7353
8069
|
rubyStubEvidence,
|
|
7354
8070
|
runStatements,
|
|
8071
|
+
storageClaims,
|
|
8072
|
+
storageEffects,
|
|
7355
8073
|
stringLiteralValue,
|
|
7356
8074
|
stringValueOf,
|
|
7357
8075
|
symbolValue,
|