@suss/adapter-ruby 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +761 -223
- package/dist/index.d.cts +164 -13
- package/dist/index.d.ts +164 -13
- package/dist/index.js +745 -207
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -143,7 +143,52 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
|
143
143
|
"singleton_class"
|
|
144
144
|
]);
|
|
145
145
|
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
146
|
+
var INCLUDE_CALL = "include";
|
|
147
|
+
var PREPEND_CALL = "prepend";
|
|
146
148
|
var ARGUMENT_LIST_TYPE = "argument_list";
|
|
149
|
+
var BLOCK_TYPES = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
150
|
+
var NO_BODY_BLOCKS = /* @__PURE__ */ new Map();
|
|
151
|
+
function isModuleBody(body) {
|
|
152
|
+
return body.parent?.type === "module";
|
|
153
|
+
}
|
|
154
|
+
function declaredBlockAt(statement, isModule, blocks) {
|
|
155
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const method = field(statement, "method");
|
|
159
|
+
const kind = method === null ? void 0 : blocks.get(method.text);
|
|
160
|
+
if (kind === void 0 || kind.moduleOnly && !isModule) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return field(statement, "block") === null ? null : kind;
|
|
164
|
+
}
|
|
165
|
+
function bodyStatementsRun(body, isModule, blocks) {
|
|
166
|
+
return bodyStatements(body).flatMap((statement) => {
|
|
167
|
+
if (declaredBlockAt(statement, isModule, blocks) === null) {
|
|
168
|
+
return [statement];
|
|
169
|
+
}
|
|
170
|
+
const block = field(statement, "block");
|
|
171
|
+
const inner = block === null ? null : field(block, "body");
|
|
172
|
+
return inner === null ? [] : bodyStatementsRun(inner, isModule, blocks);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function definedAtClassLevel(node, body, blocks) {
|
|
176
|
+
if (blocks.size === 0) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const isModule = isModuleBody(body);
|
|
180
|
+
let current = node.parent;
|
|
181
|
+
while (current !== null && current.id !== body.id) {
|
|
182
|
+
const parent = current.parent;
|
|
183
|
+
if (BLOCK_TYPES.has(current.type) && parent !== null) {
|
|
184
|
+
if (declaredBlockAt(parent, isModule, blocks)?.definesClassMethods) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
current = parent;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
147
192
|
function runStatements(body, blockConfigures = () => false) {
|
|
148
193
|
const found = [];
|
|
149
194
|
const readsItsChildren = (node) => {
|
|
@@ -179,10 +224,10 @@ function stringLiteralValue(node) {
|
|
|
179
224
|
function symbolValue(node) {
|
|
180
225
|
return node.type === "simple_symbol" ? node.text.slice(1) : null;
|
|
181
226
|
}
|
|
182
|
-
function instanceMethodsByName(body) {
|
|
227
|
+
function instanceMethodsByName(body, blocks = NO_BODY_BLOCKS) {
|
|
183
228
|
const methods = /* @__PURE__ */ new Map();
|
|
184
229
|
for (const stmt of runStatements(body)) {
|
|
185
|
-
if (stmt.type !== "method") {
|
|
230
|
+
if (stmt.type !== "method" || definedAtClassLevel(stmt, body, blocks)) {
|
|
186
231
|
continue;
|
|
187
232
|
}
|
|
188
233
|
const name = field(stmt, "name")?.text;
|
|
@@ -244,10 +289,11 @@ function instanceMethodVisibility(body) {
|
|
|
244
289
|
}
|
|
245
290
|
return visibility;
|
|
246
291
|
}
|
|
247
|
-
function singletonMethodsByName(body) {
|
|
292
|
+
function singletonMethodsByName(body, blocks = NO_BODY_BLOCKS) {
|
|
248
293
|
const methods = /* @__PURE__ */ new Map();
|
|
249
294
|
for (const stmt of runStatements(body)) {
|
|
250
|
-
|
|
295
|
+
const classLevel = stmt.type === "method" && definedAtClassLevel(stmt, body, blocks);
|
|
296
|
+
if (stmt.type !== "singleton_method" && !classLevel) {
|
|
251
297
|
continue;
|
|
252
298
|
}
|
|
253
299
|
const name = field(stmt, "name")?.text;
|
|
@@ -360,6 +406,7 @@ function autoloadDirectories(root) {
|
|
|
360
406
|
}
|
|
361
407
|
|
|
362
408
|
// src/defineMethod.ts
|
|
409
|
+
var import_resolution5 = require("@suss/resolution");
|
|
363
410
|
var import_values4 = require("@suss/values");
|
|
364
411
|
|
|
365
412
|
// src/values/evaluator.ts
|
|
@@ -396,6 +443,29 @@ var RUBY_RULES = [
|
|
|
396
443
|
(0, import_datalog.lit)("givesBackOne", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("m"))
|
|
397
444
|
],
|
|
398
445
|
"declared finder"
|
|
446
|
+
),
|
|
447
|
+
// Ruby cannot refer to a method without running it, so
|
|
448
|
+
// `Settings.filters` is worth what the method returns. The shared
|
|
449
|
+
// rules hang that step on a `call` fact this spelling never gets.
|
|
450
|
+
(0, import_datalog.rule)(
|
|
451
|
+
"invokes",
|
|
452
|
+
[(0, import_datalog.variable)("x"), (0, import_datalog.variable)("f")],
|
|
453
|
+
[
|
|
454
|
+
(0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("m")),
|
|
455
|
+
(0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("f")),
|
|
456
|
+
(0, import_datalog.lit)("func", (0, import_datalog.variable)("f"))
|
|
457
|
+
],
|
|
458
|
+
"property read runs a method"
|
|
459
|
+
),
|
|
460
|
+
// `%i[a b].freeze` is worth the list it was written as. The evaluator
|
|
461
|
+
// says the same in its row table, for a value it reads in one file.
|
|
462
|
+
...["freeze", "dup"].map(
|
|
463
|
+
(method) => (0, import_datalog.rule)(
|
|
464
|
+
"stepsTo",
|
|
465
|
+
[(0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), import_resolution.VALUE_STEP],
|
|
466
|
+
[(0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.constant)(method))],
|
|
467
|
+
"hands back the receiver"
|
|
468
|
+
)
|
|
399
469
|
)
|
|
400
470
|
];
|
|
401
471
|
var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
|
|
@@ -415,8 +485,8 @@ var import_resolution3 = require("@suss/resolution");
|
|
|
415
485
|
|
|
416
486
|
// src/facts/locals.ts
|
|
417
487
|
var import_resolution2 = require("@suss/resolution");
|
|
418
|
-
var
|
|
419
|
-
var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...
|
|
488
|
+
var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
489
|
+
var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES2]);
|
|
420
490
|
var RUBY_NAME_TYPES = /* @__PURE__ */ new Set([
|
|
421
491
|
"identifier",
|
|
422
492
|
"instance_variable"
|
|
@@ -569,7 +639,7 @@ function ownerOfName(node, name, enclosing) {
|
|
|
569
639
|
function blockDeclaring(node, name) {
|
|
570
640
|
let current = node.parent;
|
|
571
641
|
while (current !== null) {
|
|
572
|
-
if (
|
|
642
|
+
if (BLOCK_TYPES2.has(current.type) && parameterNames(current).has(name)) {
|
|
573
643
|
return current;
|
|
574
644
|
}
|
|
575
645
|
if (OWN_BODY_TYPES.has(current.type)) {
|
|
@@ -819,17 +889,70 @@ function emitHash(emitter, hash) {
|
|
|
819
889
|
add(emitter, "holdsProperty", objectKey, keyText, valueKey(emitter, value));
|
|
820
890
|
}
|
|
821
891
|
}
|
|
822
|
-
|
|
892
|
+
var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
893
|
+
var LOOP_METHODS = /* @__PURE__ */ new Set(["each", "each_with_index", "map"]);
|
|
894
|
+
var DEFINE_METHOD_CALL = "define_method";
|
|
895
|
+
function loopTurnAt(emitter, block) {
|
|
896
|
+
const call = block.parent;
|
|
897
|
+
if (call?.type !== "call") {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
const receiver = field(call, "receiver");
|
|
901
|
+
const method = field(call, "method");
|
|
902
|
+
if (receiver === null || method === null || !LOOP_METHODS.has(method.text)) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
const parameters = field(block, "parameters");
|
|
906
|
+
const names = parameters === null ? [] : children(parameters).map((p) => p.text);
|
|
907
|
+
const element = names[0];
|
|
908
|
+
if (element === void 0) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
element,
|
|
913
|
+
index: names[1] ?? "",
|
|
914
|
+
overKey: nodeId(emitter.filePath, receiver)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
function emitDynamicDefinition(emitter, call, turns) {
|
|
918
|
+
if (emitter.selfKey === null || // A call inside a method runs when that method does, not at load time.
|
|
919
|
+
emitter.enclosing !== null || call.type !== "call" || field(call, "receiver") !== null || field(call, "method")?.text !== DEFINE_METHOD_CALL) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const args = field(call, "arguments");
|
|
923
|
+
const first = args === null ? void 0 : children(args)[0];
|
|
924
|
+
const nameKey2 = nodeId(emitter.filePath, first ?? call);
|
|
925
|
+
add(emitter, "definesMethodFrom", emitter.selfKey, nameKey2);
|
|
926
|
+
for (const turn of turns) {
|
|
927
|
+
add(
|
|
928
|
+
emitter,
|
|
929
|
+
"nameTurnsOn",
|
|
930
|
+
nameKey2,
|
|
931
|
+
turn.element,
|
|
932
|
+
turn.index,
|
|
933
|
+
turn.overKey
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function walkExpressions(node, emitter, visit, turns = []) {
|
|
823
938
|
for (const child of children(node)) {
|
|
824
939
|
if (OWN_BODY_TYPES.has(child.type)) {
|
|
825
940
|
continue;
|
|
826
941
|
}
|
|
827
|
-
visit(child);
|
|
828
|
-
|
|
942
|
+
visit(child, turns);
|
|
943
|
+
const opened = BLOCK_TYPES3.has(child.type) ? loopTurnAt(emitter, child) : null;
|
|
944
|
+
walkExpressions(
|
|
945
|
+
child,
|
|
946
|
+
emitter,
|
|
947
|
+
visit,
|
|
948
|
+
opened === null ? turns : [...turns, opened]
|
|
949
|
+
);
|
|
829
950
|
}
|
|
830
951
|
}
|
|
831
952
|
function emitExpressionFacts(emitter, node) {
|
|
832
|
-
|
|
953
|
+
emitDynamicDefinition(emitter, node, []);
|
|
954
|
+
walkExpressions(node, emitter, (child, turns) => {
|
|
955
|
+
emitDynamicDefinition(emitter, child, turns);
|
|
833
956
|
if (isPropertyRead(child)) {
|
|
834
957
|
emitPropertyRead(emitter, child);
|
|
835
958
|
} else if (child.type === "call") {
|
|
@@ -918,7 +1041,20 @@ function implicitReturn(body) {
|
|
|
918
1041
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
919
1042
|
);
|
|
920
1043
|
const last = statements[statements.length - 1];
|
|
921
|
-
|
|
1044
|
+
if (last === void 0 || last.type === "return") {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
return assignedValueOf(last) ?? last;
|
|
1048
|
+
}
|
|
1049
|
+
function assignedValueOf(node) {
|
|
1050
|
+
if (!ASSIGNMENT_TYPES.has(node.type)) {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
const operator = field(node, "operator")?.text;
|
|
1054
|
+
if (node.type !== "assignment" && !WHOLE_VALUE_OPERATORS.has(operator ?? "")) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
return field(node, "right");
|
|
922
1058
|
}
|
|
923
1059
|
function emitMethodFacts(emitter, method) {
|
|
924
1060
|
const funcKey = nodeId(emitter.filePath, method);
|
|
@@ -948,7 +1084,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
948
1084
|
}
|
|
949
1085
|
};
|
|
950
1086
|
recordNested(body);
|
|
951
|
-
walkExpressions(body, (child) => {
|
|
1087
|
+
walkExpressions(body, inside, (child) => {
|
|
952
1088
|
if (child.type === "return") {
|
|
953
1089
|
const first = children(child)[0];
|
|
954
1090
|
const returned = first?.type === "argument_list" ? children(first)[0] : first;
|
|
@@ -1038,23 +1174,37 @@ function emitCandidates(emitter, key, group) {
|
|
|
1038
1174
|
}
|
|
1039
1175
|
}
|
|
1040
1176
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1177
|
+
var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
|
|
1178
|
+
function mixedInConstants(body, callName) {
|
|
1179
|
+
return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
|
|
1180
|
+
}
|
|
1181
|
+
function emitMixinFacts(emitter, classKey, body, callName) {
|
|
1182
|
+
for (const mixin of mixedInConstants(body, callName)) {
|
|
1183
|
+
add(emitter, "extends", classKey, valueKey(emitter, mixin));
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1041
1186
|
function emitClassFacts(emitter, cls) {
|
|
1042
1187
|
const classKey = nodeId(emitter.filePath, cls);
|
|
1043
1188
|
add(emitter, "objectValue", classKey);
|
|
1189
|
+
const body = field(cls, "body");
|
|
1190
|
+
if (body !== null) {
|
|
1191
|
+
emitMixinFacts(emitter, classKey, body, PREPEND_CALL);
|
|
1192
|
+
emitMixinFacts(emitter, classKey, body, INCLUDE_CALL);
|
|
1193
|
+
}
|
|
1044
1194
|
const superclass = field(cls, "superclass");
|
|
1045
1195
|
const base = superclass === null ? null : children(superclass)[0] ?? null;
|
|
1046
1196
|
if (base !== null) {
|
|
1047
1197
|
add(emitter, "extends", classKey, valueKey(emitter, base));
|
|
1048
1198
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1049
1199
|
}
|
|
1050
|
-
const body = field(cls, "body");
|
|
1051
1200
|
const collected = /* @__PURE__ */ new Map();
|
|
1052
1201
|
const within = {
|
|
1053
1202
|
...emitter,
|
|
1054
1203
|
selfKey: classKey,
|
|
1055
1204
|
instanceWrites: collected
|
|
1056
1205
|
};
|
|
1057
|
-
|
|
1206
|
+
const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
|
|
1207
|
+
for (const statement of statements) {
|
|
1058
1208
|
if (statement.type === "assignment") {
|
|
1059
1209
|
const left = field(statement, "left");
|
|
1060
1210
|
const right = field(statement, "right");
|
|
@@ -1075,6 +1225,7 @@ function emitClassFacts(emitter, cls) {
|
|
|
1075
1225
|
continue;
|
|
1076
1226
|
}
|
|
1077
1227
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
1228
|
+
emitExpressionFacts(within, statement);
|
|
1078
1229
|
continue;
|
|
1079
1230
|
}
|
|
1080
1231
|
const funcKey = emitMethodFacts(within, statement);
|
|
@@ -1086,13 +1237,14 @@ function emitClassFacts(emitter, cls) {
|
|
|
1086
1237
|
emitInstanceWrites(within, classKey, collected);
|
|
1087
1238
|
return classKey;
|
|
1088
1239
|
}
|
|
1089
|
-
function emitValueFacts(db, filePath, root) {
|
|
1240
|
+
function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
|
|
1090
1241
|
const emitter = {
|
|
1091
1242
|
db,
|
|
1092
1243
|
filePath,
|
|
1093
1244
|
enclosing: null,
|
|
1094
1245
|
selfKey: null,
|
|
1095
|
-
instanceWrites: null
|
|
1246
|
+
instanceWrites: null,
|
|
1247
|
+
bodyBlocks
|
|
1096
1248
|
};
|
|
1097
1249
|
const declaresName = (child, key) => {
|
|
1098
1250
|
const name = field(child, "name");
|
|
@@ -1132,7 +1284,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1132
1284
|
"module",
|
|
1133
1285
|
"singleton_class"
|
|
1134
1286
|
]);
|
|
1135
|
-
var
|
|
1287
|
+
var BLOCK_TYPES4 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1136
1288
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1137
1289
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1138
1290
|
"identifier",
|
|
@@ -1641,7 +1793,7 @@ function isTailExpression(node) {
|
|
|
1641
1793
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
1642
1794
|
return true;
|
|
1643
1795
|
}
|
|
1644
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
1796
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES4.has(owner.type)) {
|
|
1645
1797
|
return true;
|
|
1646
1798
|
}
|
|
1647
1799
|
if (owner.type === "when") {
|
|
@@ -1660,7 +1812,7 @@ function isRoot(node) {
|
|
|
1660
1812
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
1661
1813
|
return true;
|
|
1662
1814
|
}
|
|
1663
|
-
return
|
|
1815
|
+
return BLOCK_TYPES4.has(node.type) && node.parent?.type === "call";
|
|
1664
1816
|
}
|
|
1665
1817
|
function isPathStatement(node, parent) {
|
|
1666
1818
|
if (node.type === "elsif") {
|
|
@@ -1714,7 +1866,7 @@ function functionOf(node) {
|
|
|
1714
1866
|
body: bodyOf(block)
|
|
1715
1867
|
};
|
|
1716
1868
|
}
|
|
1717
|
-
if (
|
|
1869
|
+
if (BLOCK_TYPES4.has(node.type)) {
|
|
1718
1870
|
return {
|
|
1719
1871
|
parameters: parametersOf2(field(node, "parameters")),
|
|
1720
1872
|
body: bodyOf(node)
|
|
@@ -2185,113 +2337,119 @@ function contextOver(db, nodes) {
|
|
|
2185
2337
|
}
|
|
2186
2338
|
|
|
2187
2339
|
// src/defineMethod.ts
|
|
2188
|
-
var
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
function
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
return NOTHING;
|
|
2340
|
+
var NOTHING = {
|
|
2341
|
+
names: /* @__PURE__ */ new Set(),
|
|
2342
|
+
patterns: [],
|
|
2343
|
+
unreadable: false
|
|
2344
|
+
};
|
|
2345
|
+
function couldBeDefined(defined, name) {
|
|
2346
|
+
return defined.unreadable || defined.patterns.some((pattern) => pattern.test(name));
|
|
2347
|
+
}
|
|
2348
|
+
function definedNamesOf(dynamic, classKey) {
|
|
2349
|
+
return dynamic?.get(classKey) ?? NOTHING;
|
|
2350
|
+
}
|
|
2351
|
+
function readDynamicNames(db, rootsByFile) {
|
|
2352
|
+
const byClass = /* @__PURE__ */ new Map();
|
|
2353
|
+
for (const [classKey, nameKey2] of db.facts("definesMethodFrom")) {
|
|
2354
|
+
const key = String(classKey);
|
|
2355
|
+
byClass.set(
|
|
2356
|
+
key,
|
|
2357
|
+
foldReading(
|
|
2358
|
+
byClass.get(key) ?? NOTHING,
|
|
2359
|
+
readOne(db, rootsByFile, String(nameKey2))
|
|
2360
|
+
)
|
|
2361
|
+
);
|
|
2211
2362
|
}
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
const defined = namesDefinedBy(call);
|
|
2216
|
-
if (defined === null) {
|
|
2217
|
-
unreadable = true;
|
|
2218
|
-
continue;
|
|
2219
|
-
}
|
|
2220
|
-
for (const name of defined) {
|
|
2221
|
-
names.add(name);
|
|
2363
|
+
for (const [classKey, reading] of byClass) {
|
|
2364
|
+
for (const name of reading.names) {
|
|
2365
|
+
db.add("declaresName", [classKey, name]);
|
|
2222
2366
|
}
|
|
2223
2367
|
}
|
|
2224
|
-
return
|
|
2368
|
+
return byClass;
|
|
2225
2369
|
}
|
|
2226
|
-
function
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
if (first === void 0) {
|
|
2230
|
-
return null;
|
|
2370
|
+
function foldReading(soFar, reading) {
|
|
2371
|
+
if (reading === null) {
|
|
2372
|
+
return { ...soFar, unreadable: true };
|
|
2231
2373
|
}
|
|
2374
|
+
return {
|
|
2375
|
+
names: /* @__PURE__ */ new Set([...soFar.names, ...reading.names]),
|
|
2376
|
+
patterns: [...soFar.patterns, ...reading.patterns],
|
|
2377
|
+
unreadable: soFar.unreadable
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
function readOne(db, rootsByFile, nameKey2) {
|
|
2381
|
+
const node = (0, import_resolution5.nodeOfKey)(rootsByFile, nameKey2);
|
|
2232
2382
|
const names = [];
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2383
|
+
const patterns = [];
|
|
2384
|
+
for (const bindings of loopTurns(db, rootsByFile, nameKey2)) {
|
|
2385
|
+
const value = node === null ? (0, import_values4.hole)(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
|
|
2386
|
+
const name = (0, import_values4.literalOf)(value);
|
|
2387
|
+
if (name !== null) {
|
|
2388
|
+
names.push(name);
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
const pattern = patternOf(value);
|
|
2392
|
+
if (pattern === null) {
|
|
2240
2393
|
return null;
|
|
2241
2394
|
}
|
|
2242
|
-
|
|
2395
|
+
patterns.push(pattern);
|
|
2243
2396
|
}
|
|
2244
|
-
return names;
|
|
2397
|
+
return { names, patterns };
|
|
2245
2398
|
}
|
|
2246
|
-
function
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2399
|
+
function patternOf(value) {
|
|
2400
|
+
const pieces = (0, import_values4.piecesOf)(value);
|
|
2401
|
+
let source = "^";
|
|
2402
|
+
let readSomething = false;
|
|
2403
|
+
for (const piece of pieces) {
|
|
2404
|
+
if (piece.kind === "hole") {
|
|
2405
|
+
source += "[\\s\\S]*";
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
const options = piece.options.filter((option) => option.length > 0);
|
|
2409
|
+
if (options.length === 0) {
|
|
2251
2410
|
continue;
|
|
2252
2411
|
}
|
|
2412
|
+
readSomething = true;
|
|
2413
|
+
source += `(?:${options.map(escapeForPattern).join("|")})`;
|
|
2414
|
+
}
|
|
2415
|
+
return readSomething ? new RegExp(`${source}$`) : null;
|
|
2416
|
+
}
|
|
2417
|
+
function escapeForPattern(text2) {
|
|
2418
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2419
|
+
}
|
|
2420
|
+
function loopTurns(db, rootsByFile, nameKey2) {
|
|
2421
|
+
let turns = [/* @__PURE__ */ new Map()];
|
|
2422
|
+
for (const over of listsBehind(db, rootsByFile, nameKey2)) {
|
|
2253
2423
|
turns = turns.flatMap(
|
|
2254
|
-
(base) =>
|
|
2424
|
+
(base) => over.elements.map((value, position) => {
|
|
2425
|
+
const binding = new Map([...base, [over.element, value]]);
|
|
2426
|
+
if (over.index !== "") {
|
|
2427
|
+
binding.set(over.index, String(position));
|
|
2428
|
+
}
|
|
2429
|
+
return binding;
|
|
2430
|
+
})
|
|
2255
2431
|
);
|
|
2256
2432
|
}
|
|
2257
2433
|
return turns;
|
|
2258
2434
|
}
|
|
2259
|
-
function
|
|
2260
|
-
const
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2435
|
+
function listsBehind(db, rootsByFile, nameKey2) {
|
|
2436
|
+
const found = [];
|
|
2437
|
+
for (const [, element, index, overKey] of db.lookup(
|
|
2438
|
+
"nameTurnsOn",
|
|
2439
|
+
0,
|
|
2440
|
+
nameKey2
|
|
2441
|
+
)) {
|
|
2442
|
+
const elements = literalElementsOf(rootsByFile, String(overKey), db);
|
|
2443
|
+
if (elements !== null) {
|
|
2444
|
+
found.push({ element: String(element), index: String(index), elements });
|
|
2266
2445
|
}
|
|
2267
|
-
current = parent;
|
|
2268
2446
|
}
|
|
2269
|
-
return
|
|
2270
|
-
}
|
|
2271
|
-
function elementBindings(taken) {
|
|
2272
|
-
const { block, call } = taken;
|
|
2273
|
-
const receiver = field(call, "receiver");
|
|
2274
|
-
const method = field(call, "method")?.text;
|
|
2275
|
-
if (receiver === null || method === void 0 || !LOOP_METHODS.has(method)) {
|
|
2276
|
-
return null;
|
|
2277
|
-
}
|
|
2278
|
-
const parameters = field(block, "parameters");
|
|
2279
|
-
const [element, index] = parameters === null ? [] : bodyStatements(parameters).map((parameter) => parameter.text);
|
|
2280
|
-
const elements = element === void 0 ? null : literalElementsOf(receiver);
|
|
2281
|
-
if (element === void 0 || elements === null) {
|
|
2282
|
-
return null;
|
|
2283
|
-
}
|
|
2284
|
-
return elements.map((value, position) => {
|
|
2285
|
-
const binding = /* @__PURE__ */ new Map([[element, value]]);
|
|
2286
|
-
if (index !== void 0) {
|
|
2287
|
-
binding.set(index, String(position));
|
|
2288
|
-
}
|
|
2289
|
-
return binding;
|
|
2290
|
-
});
|
|
2447
|
+
return found;
|
|
2291
2448
|
}
|
|
2292
|
-
function literalElementsOf(
|
|
2293
|
-
const
|
|
2294
|
-
|
|
2449
|
+
function literalElementsOf(rootsByFile, key, db) {
|
|
2450
|
+
const node = (0, import_resolution5.nodeOfKey)(rootsByFile, key);
|
|
2451
|
+
const value = node === null ? null : evaluatedValue(node, db);
|
|
2452
|
+
if (value === null || value.kind !== "sequence") {
|
|
2295
2453
|
return null;
|
|
2296
2454
|
}
|
|
2297
2455
|
const elements = [];
|
|
@@ -2431,8 +2589,6 @@ function graphqlTypeNameFromQualified(qualifiedName, convention) {
|
|
|
2431
2589
|
}
|
|
2432
2590
|
|
|
2433
2591
|
// src/ancestry.ts
|
|
2434
|
-
var INCLUDE_CALL = "include";
|
|
2435
|
-
var PREPEND_CALL = "prepend";
|
|
2436
2592
|
async function reachDefinition(qualifiedName, lookup) {
|
|
2437
2593
|
const filePath = resolveConstantFile(
|
|
2438
2594
|
lookup.root,
|
|
@@ -2559,7 +2715,7 @@ function superclassCandidatesOf(blocks) {
|
|
|
2559
2715
|
}
|
|
2560
2716
|
return [];
|
|
2561
2717
|
}
|
|
2562
|
-
function methodInAncestry(ancestry, name) {
|
|
2718
|
+
function methodInAncestry(ancestry, name, read = {}) {
|
|
2563
2719
|
for (const entry of ancestry) {
|
|
2564
2720
|
if (entry.type === "root") {
|
|
2565
2721
|
return { type: "none" };
|
|
@@ -2571,7 +2727,7 @@ function methodInAncestry(ancestry, name) {
|
|
|
2571
2727
|
cause: "unreadAncestor"
|
|
2572
2728
|
};
|
|
2573
2729
|
}
|
|
2574
|
-
const found = definitionIn(entry.blocks, name);
|
|
2730
|
+
const found = definitionIn(entry.blocks, name, read);
|
|
2575
2731
|
if (found.method !== null && found.block !== null) {
|
|
2576
2732
|
return { type: "found", method: found.method, block: found.block };
|
|
2577
2733
|
}
|
|
@@ -2585,7 +2741,7 @@ function methodInAncestry(ancestry, name) {
|
|
|
2585
2741
|
}
|
|
2586
2742
|
return { type: "none" };
|
|
2587
2743
|
}
|
|
2588
|
-
function definitionIn(blocks, name) {
|
|
2744
|
+
function definitionIn(blocks, name, read) {
|
|
2589
2745
|
let method = null;
|
|
2590
2746
|
let block = null;
|
|
2591
2747
|
let definedDynamically = false;
|
|
@@ -2595,14 +2751,17 @@ function definitionIn(blocks, name) {
|
|
|
2595
2751
|
if (body === null) {
|
|
2596
2752
|
continue;
|
|
2597
2753
|
}
|
|
2598
|
-
const found = instanceMethodsByName(body).get(name);
|
|
2754
|
+
const found = instanceMethodsByName(body, read.bodyBlocks).get(name);
|
|
2599
2755
|
if (found !== void 0) {
|
|
2600
2756
|
method = found;
|
|
2601
2757
|
block = candidate;
|
|
2602
2758
|
}
|
|
2603
|
-
const defined =
|
|
2759
|
+
const defined = definedNamesOf(
|
|
2760
|
+
read.dynamicNames,
|
|
2761
|
+
nodeId(candidate.file, candidate.info.node)
|
|
2762
|
+
);
|
|
2604
2763
|
definedDynamically ||= defined.names.has(name);
|
|
2605
|
-
unreadableDefine ||= defined
|
|
2764
|
+
unreadableDefine ||= couldBeDefined(defined, name);
|
|
2606
2765
|
}
|
|
2607
2766
|
return { method, block, definedDynamically, unreadableDefine };
|
|
2608
2767
|
}
|
|
@@ -2631,7 +2790,7 @@ var import_extractor5 = require("@suss/extractor");
|
|
|
2631
2790
|
|
|
2632
2791
|
// src/clientCalls.ts
|
|
2633
2792
|
var import_behavioral_ir = require("@suss/behavioral-ir");
|
|
2634
|
-
var
|
|
2793
|
+
var import_values7 = require("@suss/values");
|
|
2635
2794
|
|
|
2636
2795
|
// src/paths/effects.ts
|
|
2637
2796
|
var import_extractor2 = require("@suss/extractor");
|
|
@@ -3012,7 +3171,7 @@ function lowerRubyBody(body, terminals, responseCalls = []) {
|
|
|
3012
3171
|
|
|
3013
3172
|
// src/paths/effects.ts
|
|
3014
3173
|
var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
|
|
3015
|
-
var
|
|
3174
|
+
var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
|
|
3016
3175
|
"body_statement",
|
|
3017
3176
|
"then",
|
|
3018
3177
|
"else",
|
|
@@ -3141,7 +3300,7 @@ function argsOf(call) {
|
|
|
3141
3300
|
function enclosingStatement(call, body) {
|
|
3142
3301
|
let current = call;
|
|
3143
3302
|
while (current.parent !== null) {
|
|
3144
|
-
if (
|
|
3303
|
+
if (BLOCK_TYPES5.has(current.parent.type) || current.parent.id === body.id) {
|
|
3145
3304
|
return current;
|
|
3146
3305
|
}
|
|
3147
3306
|
current = current.parent;
|
|
@@ -3197,7 +3356,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
|
|
|
3197
3356
|
|
|
3198
3357
|
// src/responseStatus.ts
|
|
3199
3358
|
var import_extractor3 = require("@suss/extractor");
|
|
3200
|
-
var
|
|
3359
|
+
var import_values6 = require("@suss/values");
|
|
3201
3360
|
|
|
3202
3361
|
// src/paths/predicates.ts
|
|
3203
3362
|
var OPERATORS = {
|
|
@@ -3324,11 +3483,11 @@ function statusArgumentOf(call, declaration) {
|
|
|
3324
3483
|
}
|
|
3325
3484
|
function statusNumberOf(node, names) {
|
|
3326
3485
|
const value = evaluatedValue(node);
|
|
3327
|
-
const constant2 = (0,
|
|
3486
|
+
const constant2 = (0, import_values6.constantOf)(value);
|
|
3328
3487
|
if (typeof constant2 === "number") {
|
|
3329
3488
|
return constant2;
|
|
3330
3489
|
}
|
|
3331
|
-
const name = (0,
|
|
3490
|
+
const name = (0, import_values6.literalOf)(value);
|
|
3332
3491
|
return name === null ? null : names[name] ?? null;
|
|
3333
3492
|
}
|
|
3334
3493
|
function declarationsByName(declarations) {
|
|
@@ -3649,7 +3808,7 @@ function receiverPrefix(receiver, method, root, pattern, options) {
|
|
|
3649
3808
|
const args = readCallArgs(field(built, "arguments"));
|
|
3650
3809
|
const keyword = pattern.builderUrlKeyword;
|
|
3651
3810
|
const base = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[0];
|
|
3652
|
-
const path7 = base === void 0 ? null : (0,
|
|
3811
|
+
const path7 = base === void 0 ? null : (0, import_values7.pathOf)(evaluatedValue(base, options.facts));
|
|
3653
3812
|
return path7 === void 0 || path7 === null ? "" : trimmed(path7);
|
|
3654
3813
|
}
|
|
3655
3814
|
function trimmed(path7) {
|
|
@@ -3715,7 +3874,7 @@ function pathAt(written, options) {
|
|
|
3715
3874
|
if (written === void 0) {
|
|
3716
3875
|
return null;
|
|
3717
3876
|
}
|
|
3718
|
-
const path7 = (0,
|
|
3877
|
+
const path7 = (0, import_values7.pathOf)(evaluatedValue(written, options.facts));
|
|
3719
3878
|
return path7 === void 0 || (0, import_behavioral_ir.namesNothing)(path7) ? null : path7;
|
|
3720
3879
|
}
|
|
3721
3880
|
function clientUnit(method, name, request, pattern, pack, options) {
|
|
@@ -3884,7 +4043,7 @@ function isDefaultedAt(node) {
|
|
|
3884
4043
|
}
|
|
3885
4044
|
|
|
3886
4045
|
// src/filters.ts
|
|
3887
|
-
function controllerFilters(pattern, ancestry) {
|
|
4046
|
+
function controllerFilters(pattern, ancestry, read = {}) {
|
|
3888
4047
|
const forms = pattern.filters ?? [];
|
|
3889
4048
|
if (forms.length === 0) {
|
|
3890
4049
|
return [];
|
|
@@ -3919,7 +4078,7 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3919
4078
|
}
|
|
3920
4079
|
const resolved = [];
|
|
3921
4080
|
for (const declaration of declared) {
|
|
3922
|
-
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
4081
|
+
const found = methodInAncestry(ancestry, declaration.methodName, read);
|
|
3923
4082
|
if (found.type !== "found") {
|
|
3924
4083
|
continue;
|
|
3925
4084
|
}
|
|
@@ -4077,7 +4236,7 @@ function namesUnder(node) {
|
|
|
4077
4236
|
|
|
4078
4237
|
// src/storage.ts
|
|
4079
4238
|
var import_ir_core = require("@suss/ir-core");
|
|
4080
|
-
var
|
|
4239
|
+
var import_resolution6 = require("@suss/resolution");
|
|
4081
4240
|
function children3(node) {
|
|
4082
4241
|
return node.namedChildren.filter((child) => child !== null);
|
|
4083
4242
|
}
|
|
@@ -4104,7 +4263,7 @@ function constantName(constant2) {
|
|
|
4104
4263
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
4105
4264
|
}
|
|
4106
4265
|
function reachesBase(facts, classKey, bases) {
|
|
4107
|
-
(0,
|
|
4266
|
+
(0, import_resolution6.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
4108
4267
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
4109
4268
|
}
|
|
4110
4269
|
function classBehind(facts, file, constant2) {
|
|
@@ -4123,28 +4282,98 @@ function modelPattern(constant2, file, options) {
|
|
|
4123
4282
|
(candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
|
|
4124
4283
|
);
|
|
4125
4284
|
}
|
|
4126
|
-
function
|
|
4127
|
-
const
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4285
|
+
function chainLinks(call) {
|
|
4286
|
+
const links = [];
|
|
4287
|
+
let current = call;
|
|
4288
|
+
while (current !== null && current.type === "call") {
|
|
4289
|
+
links.push(current);
|
|
4290
|
+
current = receiverOf(current);
|
|
4291
|
+
}
|
|
4292
|
+
return links;
|
|
4293
|
+
}
|
|
4294
|
+
function kindOfCall(pattern, method) {
|
|
4295
|
+
if (pattern.writes.includes(method)) {
|
|
4296
|
+
return "write";
|
|
4297
|
+
}
|
|
4298
|
+
if (pattern.reads.includes(method)) {
|
|
4299
|
+
return "read";
|
|
4300
|
+
}
|
|
4301
|
+
return void 0;
|
|
4302
|
+
}
|
|
4303
|
+
function someLibraryDefines(options, method) {
|
|
4304
|
+
return options.patterns.some(
|
|
4305
|
+
(pattern) => kindOfCall(pattern, method) !== void 0
|
|
4306
|
+
);
|
|
4307
|
+
}
|
|
4308
|
+
function argumentsOf2(call) {
|
|
4309
|
+
const args = field(call, "arguments");
|
|
4310
|
+
return args === null ? [] : children3(args);
|
|
4311
|
+
}
|
|
4312
|
+
function isKeywordArgument(argument) {
|
|
4313
|
+
return argument.type === "pair" || argument.type === "hash";
|
|
4314
|
+
}
|
|
4315
|
+
function bareName(node) {
|
|
4316
|
+
return node.text.replace(/^:/, "").replace(/:$/, "");
|
|
4317
|
+
}
|
|
4318
|
+
function pairsIn(argument) {
|
|
4319
|
+
if (argument.type === "pair") {
|
|
4320
|
+
return [argument];
|
|
4321
|
+
}
|
|
4322
|
+
if (argument.type === "hash") {
|
|
4323
|
+
return children3(argument).filter((child) => child.type === "pair");
|
|
4324
|
+
}
|
|
4325
|
+
return [];
|
|
4326
|
+
}
|
|
4327
|
+
function keywordKeys(call) {
|
|
4328
|
+
const keys = [];
|
|
4329
|
+
for (const argument of argumentsOf2(call)) {
|
|
4330
|
+
for (const pair of pairsIn(argument)) {
|
|
4331
|
+
const key = field(pair, "key");
|
|
4135
4332
|
if (key !== null) {
|
|
4136
|
-
|
|
4333
|
+
keys.push(bareName(key));
|
|
4137
4334
|
}
|
|
4138
4335
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4336
|
+
}
|
|
4337
|
+
return keys;
|
|
4338
|
+
}
|
|
4339
|
+
function hasPositionalArgument(call) {
|
|
4340
|
+
return argumentsOf2(call).some((argument) => !isKeywordArgument(argument));
|
|
4341
|
+
}
|
|
4342
|
+
function columnsAskedFor(call, pattern) {
|
|
4343
|
+
if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
|
|
4344
|
+
return [];
|
|
4345
|
+
}
|
|
4346
|
+
return argumentsOf2(call).filter((argument) => argument.type === "simple_symbol").map(bareName);
|
|
4347
|
+
}
|
|
4348
|
+
function primaryKeySelector(call, pattern) {
|
|
4349
|
+
const byPrimaryKey = pattern.byPrimaryKey;
|
|
4350
|
+
if (byPrimaryKey === void 0) {
|
|
4351
|
+
return [];
|
|
4352
|
+
}
|
|
4353
|
+
const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call);
|
|
4354
|
+
return picks ? [byPrimaryKey.column] : [];
|
|
4355
|
+
}
|
|
4356
|
+
function selectorOf(call, pattern) {
|
|
4357
|
+
const picked = [];
|
|
4358
|
+
for (const link of chainLinks(call)) {
|
|
4359
|
+
picked.push(...primaryKeySelector(link, pattern));
|
|
4360
|
+
if (pattern.reads.includes(methodOf(link))) {
|
|
4361
|
+
picked.push(...keywordKeys(link));
|
|
4142
4362
|
}
|
|
4143
|
-
}
|
|
4144
|
-
walk(node);
|
|
4363
|
+
}
|
|
4145
4364
|
return [...new Set(picked)];
|
|
4146
4365
|
}
|
|
4147
|
-
function
|
|
4366
|
+
function fieldsOf(call, pattern, kind) {
|
|
4367
|
+
if (kind === "write") {
|
|
4368
|
+
return [...new Set(keywordKeys(call))];
|
|
4369
|
+
}
|
|
4370
|
+
return [
|
|
4371
|
+
...new Set(
|
|
4372
|
+
chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern))
|
|
4373
|
+
)
|
|
4374
|
+
];
|
|
4375
|
+
}
|
|
4376
|
+
function storageEffect(call, container, pattern, kind, selector, fields) {
|
|
4148
4377
|
const operation = methodOf(call);
|
|
4149
4378
|
return {
|
|
4150
4379
|
type: "interaction",
|
|
@@ -4158,37 +4387,14 @@ function storageEffect(call, container, pattern, kind, selector) {
|
|
|
4158
4387
|
interaction: {
|
|
4159
4388
|
class: "storage-access",
|
|
4160
4389
|
kind,
|
|
4161
|
-
fields
|
|
4390
|
+
fields,
|
|
4162
4391
|
operation,
|
|
4163
4392
|
...selector.length > 0 ? { selector } : {}
|
|
4164
4393
|
}
|
|
4165
4394
|
};
|
|
4166
4395
|
}
|
|
4167
|
-
function modelCallEffects(call, file, options) {
|
|
4168
|
-
const constant2 = rootConstant(call);
|
|
4169
|
-
if (constant2 === null) {
|
|
4170
|
-
return [];
|
|
4171
|
-
}
|
|
4172
|
-
const pattern = modelPattern(constant2, file, options);
|
|
4173
|
-
if (pattern === void 0) {
|
|
4174
|
-
return [];
|
|
4175
|
-
}
|
|
4176
|
-
const kind = pattern.writes.includes(methodOf(call)) ? "write" : "read";
|
|
4177
|
-
return [
|
|
4178
|
-
storageEffect(
|
|
4179
|
-
call,
|
|
4180
|
-
constantName(constant2),
|
|
4181
|
-
pattern,
|
|
4182
|
-
kind,
|
|
4183
|
-
selectorOf(call)
|
|
4184
|
-
)
|
|
4185
|
-
];
|
|
4186
|
-
}
|
|
4187
|
-
function writeMethods(patterns) {
|
|
4188
|
-
return new Set(patterns.flatMap((pattern) => pattern.writes));
|
|
4189
|
-
}
|
|
4190
4396
|
function classSettledOn(facts, key) {
|
|
4191
|
-
(0,
|
|
4397
|
+
(0, import_resolution6.askResolution)(facts, [key], "wanted", RUBY_PROGRAM);
|
|
4192
4398
|
const objects = new Set(
|
|
4193
4399
|
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
4194
4400
|
);
|
|
@@ -4203,27 +4409,64 @@ function declaredName(facts, classKey) {
|
|
|
4203
4409
|
function projectDeclares(facts, classKey, method) {
|
|
4204
4410
|
return facts.lookup("wantedDeclaredName", 0, classKey).some((row) => String(row[1]) === method);
|
|
4205
4411
|
}
|
|
4206
|
-
function
|
|
4412
|
+
function constantReceiver(constant2, file, facts) {
|
|
4413
|
+
const classKey = classBehind(facts, file, constant2);
|
|
4414
|
+
return classKey === void 0 ? void 0 : { classKey, container: constantName(constant2) };
|
|
4415
|
+
}
|
|
4416
|
+
function settledReceiver(receiver, file, facts, enclosing) {
|
|
4417
|
+
const classKey = classSettledOn(facts, readKey(file, receiver, enclosing));
|
|
4418
|
+
if (classKey === void 0) {
|
|
4419
|
+
return void 0;
|
|
4420
|
+
}
|
|
4421
|
+
const container = declaredName(facts, classKey);
|
|
4422
|
+
return container === void 0 ? void 0 : { classKey, container };
|
|
4423
|
+
}
|
|
4424
|
+
function receiverClass(call, file, options, enclosing) {
|
|
4425
|
+
const constant2 = rootConstant(call);
|
|
4426
|
+
if (constant2 !== null) {
|
|
4427
|
+
return constantReceiver(constant2, file, options.facts);
|
|
4428
|
+
}
|
|
4207
4429
|
const receiver = receiverOf(call);
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
return [];
|
|
4430
|
+
if (receiver === null) {
|
|
4431
|
+
return void 0;
|
|
4211
4432
|
}
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
)
|
|
4216
|
-
|
|
4433
|
+
return settledReceiver(receiver, file, options.facts, enclosing);
|
|
4434
|
+
}
|
|
4435
|
+
function libraryCallIn(call, options) {
|
|
4436
|
+
return chainLinks(call).find(
|
|
4437
|
+
(link) => someLibraryDefines(options, methodOf(link))
|
|
4438
|
+
) ?? null;
|
|
4439
|
+
}
|
|
4440
|
+
function modelCallEffects(call, file, options, enclosing) {
|
|
4441
|
+
const worked = libraryCallIn(call, options);
|
|
4442
|
+
if (worked === null) {
|
|
4217
4443
|
return [];
|
|
4218
4444
|
}
|
|
4219
|
-
const
|
|
4220
|
-
|
|
4221
|
-
)
|
|
4222
|
-
if (pattern === void 0 || projectDeclares(options.facts, classKey, method)) {
|
|
4445
|
+
const method = methodOf(worked);
|
|
4446
|
+
const target = receiverClass(worked, file, options, enclosing);
|
|
4447
|
+
if (target === void 0) {
|
|
4223
4448
|
return [];
|
|
4224
4449
|
}
|
|
4225
|
-
const
|
|
4226
|
-
|
|
4450
|
+
for (const pattern of options.patterns) {
|
|
4451
|
+
const kind = kindOfCall(pattern, method);
|
|
4452
|
+
if (kind === void 0 || !reachesBase(options.facts, target.classKey, pattern.baseClasses)) {
|
|
4453
|
+
continue;
|
|
4454
|
+
}
|
|
4455
|
+
if (projectDeclares(options.facts, target.classKey, method)) {
|
|
4456
|
+
return [];
|
|
4457
|
+
}
|
|
4458
|
+
return [
|
|
4459
|
+
storageEffect(
|
|
4460
|
+
worked,
|
|
4461
|
+
target.container,
|
|
4462
|
+
pattern,
|
|
4463
|
+
kind,
|
|
4464
|
+
selectorOf(worked, pattern),
|
|
4465
|
+
fieldsOf(worked, pattern, kind)
|
|
4466
|
+
)
|
|
4467
|
+
];
|
|
4468
|
+
}
|
|
4469
|
+
return [];
|
|
4227
4470
|
}
|
|
4228
4471
|
function isLoader(node, loader) {
|
|
4229
4472
|
if (node === null) {
|
|
@@ -4261,7 +4504,7 @@ function loaderCallEffects(call, file, options) {
|
|
|
4261
4504
|
const pattern = modelPattern(argument, file, options);
|
|
4262
4505
|
if (pattern !== void 0) {
|
|
4263
4506
|
effects.push(
|
|
4264
|
-
storageEffect(call, constantName(argument), pattern, "read", [])
|
|
4507
|
+
storageEffect(call, constantName(argument), pattern, "read", [], [])
|
|
4265
4508
|
);
|
|
4266
4509
|
}
|
|
4267
4510
|
}
|
|
@@ -4269,22 +4512,21 @@ function loaderCallEffects(call, file, options) {
|
|
|
4269
4512
|
return effects;
|
|
4270
4513
|
}
|
|
4271
4514
|
function effectsOfCall(call, file, options, enclosing) {
|
|
4272
|
-
const onModel = modelCallEffects(call, file, options);
|
|
4273
|
-
|
|
4274
|
-
return onModel;
|
|
4275
|
-
}
|
|
4276
|
-
const onInstance = rootConstant(call) === null ? instanceWriteEffects(call, file, options, enclosing) : [];
|
|
4277
|
-
return onInstance.length > 0 ? onInstance : loaderCallEffects(call, file, options);
|
|
4515
|
+
const onModel = modelCallEffects(call, file, options, enclosing);
|
|
4516
|
+
return onModel.length > 0 ? onModel : loaderCallEffects(call, file, options);
|
|
4278
4517
|
}
|
|
4279
4518
|
function storageClaims(call, file, options, enclosing = null) {
|
|
4280
4519
|
return options.patterns.length > 0 && effectsOfCall(call, file, options, enclosing).length > 0;
|
|
4281
4520
|
}
|
|
4282
4521
|
function receiverKeysToAsk(chains, file, options, enclosing) {
|
|
4283
|
-
const writes = writeMethods(options.patterns);
|
|
4284
4522
|
const keys = [];
|
|
4285
4523
|
for (const call of chains) {
|
|
4286
|
-
const
|
|
4287
|
-
if (
|
|
4524
|
+
const worked = libraryCallIn(call, options);
|
|
4525
|
+
if (worked === null || rootConstant(worked) !== null) {
|
|
4526
|
+
continue;
|
|
4527
|
+
}
|
|
4528
|
+
const receiver = receiverOf(worked);
|
|
4529
|
+
if (receiver !== null) {
|
|
4288
4530
|
keys.push(readKey(file, receiver, enclosing));
|
|
4289
4531
|
}
|
|
4290
4532
|
}
|
|
@@ -4302,7 +4544,7 @@ function storageEffects(calls, file, options, enclosing = null) {
|
|
|
4302
4544
|
}
|
|
4303
4545
|
}
|
|
4304
4546
|
const chains = calls.filter((call) => !partOfOne.has(call.id));
|
|
4305
|
-
(0,
|
|
4547
|
+
(0, import_resolution6.askResolution)(
|
|
4306
4548
|
options.facts,
|
|
4307
4549
|
receiverKeysToAsk(chains, file, options, enclosing),
|
|
4308
4550
|
"wanted",
|
|
@@ -4381,11 +4623,17 @@ function createFileCache(parse, readFile) {
|
|
|
4381
4623
|
}
|
|
4382
4624
|
};
|
|
4383
4625
|
}
|
|
4384
|
-
function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
|
|
4626
|
+
function fieldReadContext(pattern, cache, fileBlocks, bodyRead, facts) {
|
|
4385
4627
|
return {
|
|
4386
4628
|
pattern,
|
|
4387
4629
|
cache,
|
|
4388
4630
|
bodyRead,
|
|
4631
|
+
facts,
|
|
4632
|
+
read: {
|
|
4633
|
+
facts,
|
|
4634
|
+
bodyBlocks: bodyRead.bodyBlocks,
|
|
4635
|
+
dynamicNames: bodyRead.dynamicNames
|
|
4636
|
+
},
|
|
4389
4637
|
lookup: {
|
|
4390
4638
|
root: pattern.root,
|
|
4391
4639
|
pathConvention: pattern.pathConvention,
|
|
@@ -4468,7 +4716,13 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
|
|
|
4468
4716
|
if (info.bodyNode === null || pattern.baseClassNames.includes(info.qualifiedName)) {
|
|
4469
4717
|
return [];
|
|
4470
4718
|
}
|
|
4471
|
-
const ctx = fieldReadContext(
|
|
4719
|
+
const ctx = fieldReadContext(
|
|
4720
|
+
pattern,
|
|
4721
|
+
options.cache,
|
|
4722
|
+
fileBlocks,
|
|
4723
|
+
options,
|
|
4724
|
+
options.facts
|
|
4725
|
+
);
|
|
4472
4726
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, ctx.lookup);
|
|
4473
4727
|
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
4474
4728
|
return [];
|
|
@@ -4516,7 +4770,11 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
4516
4770
|
])) {
|
|
4517
4771
|
return [];
|
|
4518
4772
|
}
|
|
4519
|
-
const filters = controllerFilters(pattern, ancestry
|
|
4773
|
+
const filters = controllerFilters(pattern, ancestry, {
|
|
4774
|
+
facts: options.facts,
|
|
4775
|
+
bodyBlocks: options.bodyBlocks,
|
|
4776
|
+
dynamicNames: options.dynamicNames
|
|
4777
|
+
});
|
|
4520
4778
|
const units = [];
|
|
4521
4779
|
for (const filter of filters) {
|
|
4522
4780
|
const displayPath = options.displayPathOf?.(filter.file) ?? filter.file;
|
|
@@ -4561,7 +4819,10 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
4561
4819
|
});
|
|
4562
4820
|
};
|
|
4563
4821
|
const own = /* @__PURE__ */ new Set();
|
|
4564
|
-
for (const [actionName, method, block] of publicInstanceMethods(
|
|
4822
|
+
for (const [actionName, method, block] of publicInstanceMethods(
|
|
4823
|
+
ownBlocks,
|
|
4824
|
+
options.bodyBlocks
|
|
4825
|
+
)) {
|
|
4565
4826
|
own.add(actionName);
|
|
4566
4827
|
emitAction(actionName, method, block);
|
|
4567
4828
|
}
|
|
@@ -4571,7 +4832,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
4571
4832
|
continue;
|
|
4572
4833
|
}
|
|
4573
4834
|
for (const [actionName, method, block] of publicInstanceMethods(
|
|
4574
|
-
entry.blocks
|
|
4835
|
+
entry.blocks,
|
|
4836
|
+
options.bodyBlocks
|
|
4575
4837
|
)) {
|
|
4576
4838
|
if (seen.has(actionName)) {
|
|
4577
4839
|
continue;
|
|
@@ -4585,14 +4847,17 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
4585
4847
|
}
|
|
4586
4848
|
return units;
|
|
4587
4849
|
}
|
|
4588
|
-
function publicInstanceMethods(blocks) {
|
|
4850
|
+
function publicInstanceMethods(blocks, bodyBlocks) {
|
|
4589
4851
|
const found = [];
|
|
4590
4852
|
for (const block of blocks) {
|
|
4591
4853
|
if (block.info.bodyNode === null) {
|
|
4592
4854
|
continue;
|
|
4593
4855
|
}
|
|
4594
4856
|
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
4595
|
-
for (const [name, method] of instanceMethodsByName(
|
|
4857
|
+
for (const [name, method] of instanceMethodsByName(
|
|
4858
|
+
block.info.bodyNode,
|
|
4859
|
+
bodyBlocks
|
|
4860
|
+
)) {
|
|
4596
4861
|
if ((visibility.get(name) ?? "public") === "public") {
|
|
4597
4862
|
found.push([name, method, block]);
|
|
4598
4863
|
}
|
|
@@ -4855,7 +5120,7 @@ async function readFieldShape(symbol, callArgs, scope, ctx, ancestry, range) {
|
|
|
4855
5120
|
return {
|
|
4856
5121
|
contract: literalContract(callArgs, scope, ctx),
|
|
4857
5122
|
body: bodyFromLookup(
|
|
4858
|
-
methodInAncestry(ancestry, symbol),
|
|
5123
|
+
methodInAncestry(ancestry, symbol, ctx.read),
|
|
4859
5124
|
range,
|
|
4860
5125
|
"This field",
|
|
4861
5126
|
NO_METHOD_BEHIND_IT,
|
|
@@ -4900,7 +5165,7 @@ async function readWiredClass(ref, scope, ctx) {
|
|
|
4900
5165
|
return {
|
|
4901
5166
|
contract: readClassContract(ancestry, ctx.pattern),
|
|
4902
5167
|
body: bodyFromLookup(
|
|
4903
|
-
methodInAncestry(ancestry, ctx.pattern.resolverMethodName),
|
|
5168
|
+
methodInAncestry(ancestry, ctx.pattern.resolverMethodName, ctx.read),
|
|
4904
5169
|
range,
|
|
4905
5170
|
`This field's ${targetQualifiedName}`,
|
|
4906
5171
|
methodNotSettled(
|
|
@@ -5072,6 +5337,202 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
|
|
|
5072
5337
|
};
|
|
5073
5338
|
}
|
|
5074
5339
|
|
|
5340
|
+
// src/inflect.ts
|
|
5341
|
+
var SINGULAR_RULES = [
|
|
5342
|
+
[/s$/i, ""],
|
|
5343
|
+
[/(ss)$/i, "$1"],
|
|
5344
|
+
[/(n)ews$/i, "$1ews"],
|
|
5345
|
+
[/([ti])a$/i, "$1um"],
|
|
5346
|
+
[
|
|
5347
|
+
/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i,
|
|
5348
|
+
"$1sis"
|
|
5349
|
+
],
|
|
5350
|
+
[/(^analy)(sis|ses)$/i, "$1sis"],
|
|
5351
|
+
[/([^f])ves$/i, "$1fe"],
|
|
5352
|
+
[/(hive)s$/i, "$1"],
|
|
5353
|
+
[/(tive)s$/i, "$1"],
|
|
5354
|
+
[/([lr])ves$/i, "$1f"],
|
|
5355
|
+
[/([^aeiouy]|qu)ies$/i, "$1y"],
|
|
5356
|
+
[/(s)eries$/i, "$1eries"],
|
|
5357
|
+
[/(m)ovies$/i, "$1ovie"],
|
|
5358
|
+
[/(x|ch|ss|sh)es$/i, "$1"],
|
|
5359
|
+
[/^(m|l)ice$/i, "$1ouse"],
|
|
5360
|
+
[/(bus)(es)?$/i, "$1"],
|
|
5361
|
+
[/(o)es$/i, "$1"],
|
|
5362
|
+
[/(shoe)s$/i, "$1"],
|
|
5363
|
+
[/(cris|test)(is|es)$/i, "$1is"],
|
|
5364
|
+
[/^(a)x[ie]s$/i, "$1xis"],
|
|
5365
|
+
[/(octop|vir)(us|i)$/i, "$1us"],
|
|
5366
|
+
[/(alias|status)(es)?$/i, "$1"],
|
|
5367
|
+
[/^(ox)en/i, "$1"],
|
|
5368
|
+
[/(vert|ind)ices$/i, "$1ex"],
|
|
5369
|
+
[/(matr)ices$/i, "$1ix"],
|
|
5370
|
+
[/(quiz)zes$/i, "$1"],
|
|
5371
|
+
[/(database)s$/i, "$1"],
|
|
5372
|
+
// The words ActiveSupport calls irregular. It declares them after the
|
|
5373
|
+
// rules above, which is what puts them ahead in the search.
|
|
5374
|
+
[/(p)erson$/i, "$1erson"],
|
|
5375
|
+
[/(p)eople$/i, "$1erson"],
|
|
5376
|
+
[/(m)an$/i, "$1an"],
|
|
5377
|
+
[/(m)en$/i, "$1an"],
|
|
5378
|
+
[/(c)hild$/i, "$1hild"],
|
|
5379
|
+
[/(c)hildren$/i, "$1hild"],
|
|
5380
|
+
[/(s)ex$/i, "$1ex"],
|
|
5381
|
+
[/(s)exes$/i, "$1ex"],
|
|
5382
|
+
[/(m)ove$/i, "$1ove"],
|
|
5383
|
+
[/(m)oves$/i, "$1ove"],
|
|
5384
|
+
[/(z)ombie$/i, "$1ombie"],
|
|
5385
|
+
[/(z)ombies$/i, "$1ombie"]
|
|
5386
|
+
];
|
|
5387
|
+
var UNCOUNTABLE = /* @__PURE__ */ new Set([
|
|
5388
|
+
"equipment",
|
|
5389
|
+
"information",
|
|
5390
|
+
"rice",
|
|
5391
|
+
"money",
|
|
5392
|
+
"species",
|
|
5393
|
+
"series",
|
|
5394
|
+
"fish",
|
|
5395
|
+
"sheep",
|
|
5396
|
+
"jeans",
|
|
5397
|
+
"police"
|
|
5398
|
+
]);
|
|
5399
|
+
function singularize(word, project) {
|
|
5400
|
+
if (UNCOUNTABLE.has(word.toLowerCase()) || (project?.uncountable ?? []).some(
|
|
5401
|
+
(uncountable) => uncountable.toLowerCase() === word.toLowerCase()
|
|
5402
|
+
)) {
|
|
5403
|
+
return word;
|
|
5404
|
+
}
|
|
5405
|
+
const own = projectSingular(word, project);
|
|
5406
|
+
if (own !== null) {
|
|
5407
|
+
return own;
|
|
5408
|
+
}
|
|
5409
|
+
for (let at = SINGULAR_RULES.length - 1; at >= 0; at--) {
|
|
5410
|
+
const [pattern, replacement] = SINGULAR_RULES[at];
|
|
5411
|
+
if (pattern.test(word)) {
|
|
5412
|
+
return word.replace(pattern, replacement);
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
return word;
|
|
5416
|
+
}
|
|
5417
|
+
function projectSingular(word, project) {
|
|
5418
|
+
const irregular = [...project?.irregular ?? []].reverse();
|
|
5419
|
+
for (const [plural, singular] of irregular) {
|
|
5420
|
+
if (plural.toLowerCase() === word.toLowerCase()) {
|
|
5421
|
+
return matchingCase(word, singular);
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const rules = [...project?.singular ?? []].reverse();
|
|
5425
|
+
for (const [rule3, replacement] of rules) {
|
|
5426
|
+
const applied = applyRule(word, rule3, replacement);
|
|
5427
|
+
if (applied !== null) {
|
|
5428
|
+
return applied;
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
return null;
|
|
5432
|
+
}
|
|
5433
|
+
function applyRule(word, rule3, replacement) {
|
|
5434
|
+
const written = /^\/(.*)\/([a-z]*)$/s.exec(rule3);
|
|
5435
|
+
if (written === null) {
|
|
5436
|
+
return word.includes(rule3) ? word.replace(rule3, replacement) : null;
|
|
5437
|
+
}
|
|
5438
|
+
const flags = (written[2] ?? "").replace("m", "s").replace(/[^is]/g, "");
|
|
5439
|
+
const pattern = new RegExp(written[1] ?? "", flags);
|
|
5440
|
+
return pattern.test(word) ? word.replace(pattern, replacement.replace(/\\(\d)/g, "$$$1")) : null;
|
|
5441
|
+
}
|
|
5442
|
+
function matchingCase(word, replacement) {
|
|
5443
|
+
const first = word[0];
|
|
5444
|
+
if (first === void 0 || first !== first.toUpperCase()) {
|
|
5445
|
+
return replacement;
|
|
5446
|
+
}
|
|
5447
|
+
return replacement.charAt(0).toUpperCase() + replacement.slice(1);
|
|
5448
|
+
}
|
|
5449
|
+
function camelize(word, acronyms = []) {
|
|
5450
|
+
return word.split("_").map((part) => capitalize(part, acronyms)).join("");
|
|
5451
|
+
}
|
|
5452
|
+
function capitalize(part, acronyms) {
|
|
5453
|
+
if (part === "") {
|
|
5454
|
+
return "";
|
|
5455
|
+
}
|
|
5456
|
+
const acronym = acronyms.find(
|
|
5457
|
+
(known) => known.toLowerCase() === part.toLowerCase()
|
|
5458
|
+
);
|
|
5459
|
+
return acronym ?? part[0]?.toUpperCase() + part.slice(1);
|
|
5460
|
+
}
|
|
5461
|
+
function associationTargetName(name, plural, project) {
|
|
5462
|
+
return camelize(
|
|
5463
|
+
plural ? singularize(name, project) : name,
|
|
5464
|
+
project?.acronyms ?? []
|
|
5465
|
+
);
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
// src/facts/associations.ts
|
|
5469
|
+
function declaredName2(argument) {
|
|
5470
|
+
if (argument === void 0) {
|
|
5471
|
+
return null;
|
|
5472
|
+
}
|
|
5473
|
+
if (argument.type === "simple_symbol") {
|
|
5474
|
+
return argument.text.slice(1);
|
|
5475
|
+
}
|
|
5476
|
+
return stringLiteralValue(argument);
|
|
5477
|
+
}
|
|
5478
|
+
function targetReference(key, written, nesting) {
|
|
5479
|
+
if (written.startsWith("::")) {
|
|
5480
|
+
return { key, written: written.slice(2), nesting: [] };
|
|
5481
|
+
}
|
|
5482
|
+
return { key, written, nesting };
|
|
5483
|
+
}
|
|
5484
|
+
function targetName(call, calls, name, plural, inflections) {
|
|
5485
|
+
const override = readCallArgs(field(call, "arguments")).keyword[calls.classNameKeyword];
|
|
5486
|
+
if (override === void 0) {
|
|
5487
|
+
return associationTargetName(name, plural, inflections);
|
|
5488
|
+
}
|
|
5489
|
+
return stringLiteralValue(override);
|
|
5490
|
+
}
|
|
5491
|
+
function associationsDeclaredIn(cls, classKey, nesting, calls, inflections) {
|
|
5492
|
+
const body = field(cls, "body");
|
|
5493
|
+
if (body === null || calls.length === 0) {
|
|
5494
|
+
return [];
|
|
5495
|
+
}
|
|
5496
|
+
const found = [];
|
|
5497
|
+
for (const statement of runStatements(body)) {
|
|
5498
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5501
|
+
const method = field(statement, "method")?.text ?? "";
|
|
5502
|
+
for (const declaration of calls) {
|
|
5503
|
+
const plural = declaration.plural.includes(method);
|
|
5504
|
+
if (!plural && !declaration.singular.includes(method)) {
|
|
5505
|
+
continue;
|
|
5506
|
+
}
|
|
5507
|
+
const positional = readCallArgs(field(statement, "arguments")).positional;
|
|
5508
|
+
const name = declaredName2(positional[0]);
|
|
5509
|
+
if (name === null) {
|
|
5510
|
+
continue;
|
|
5511
|
+
}
|
|
5512
|
+
const written = targetName(
|
|
5513
|
+
statement,
|
|
5514
|
+
declaration,
|
|
5515
|
+
name,
|
|
5516
|
+
plural,
|
|
5517
|
+
inflections
|
|
5518
|
+
);
|
|
5519
|
+
if (written === null) {
|
|
5520
|
+
continue;
|
|
5521
|
+
}
|
|
5522
|
+
found.push({
|
|
5523
|
+
classKey,
|
|
5524
|
+
name,
|
|
5525
|
+
target: targetReference(
|
|
5526
|
+
`${classKey}#association:${name}`,
|
|
5527
|
+
written,
|
|
5528
|
+
nesting
|
|
5529
|
+
)
|
|
5530
|
+
});
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
return found;
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5075
5536
|
// src/facts/constants.ts
|
|
5076
5537
|
function children4(node) {
|
|
5077
5538
|
return node.namedChildren.filter((child) => child !== null);
|
|
@@ -5106,20 +5567,32 @@ function isDeclaration(node, parent) {
|
|
|
5106
5567
|
}
|
|
5107
5568
|
return parent.type === "scope_resolution";
|
|
5108
5569
|
}
|
|
5109
|
-
function collectFileConstants(filePath, root) {
|
|
5570
|
+
function collectFileConstants(filePath, root, associationCalls = [], inflections) {
|
|
5110
5571
|
const definitions = [];
|
|
5111
5572
|
const references = [];
|
|
5573
|
+
const associations = [];
|
|
5112
5574
|
const walk = (node, parent, nesting) => {
|
|
5113
5575
|
if (NESTING_TYPES.has(node.type)) {
|
|
5114
5576
|
const name = field(node, "name");
|
|
5115
5577
|
const written = name === null ? null : writtenName(name);
|
|
5116
5578
|
if (written !== null) {
|
|
5579
|
+
const classKey = nodeId(filePath, node);
|
|
5117
5580
|
definitions.push({
|
|
5118
5581
|
qualifiedName: qualify(nesting, written),
|
|
5119
|
-
key:
|
|
5582
|
+
key: classKey,
|
|
5120
5583
|
kind: "declaration"
|
|
5121
5584
|
});
|
|
5122
5585
|
const inside = [...nesting, ...written.split("::")];
|
|
5586
|
+
for (const declared of associationsDeclaredIn(
|
|
5587
|
+
node,
|
|
5588
|
+
classKey,
|
|
5589
|
+
inside,
|
|
5590
|
+
associationCalls,
|
|
5591
|
+
inflections
|
|
5592
|
+
)) {
|
|
5593
|
+
associations.push(declared);
|
|
5594
|
+
references.push(declared.target);
|
|
5595
|
+
}
|
|
5123
5596
|
for (const child of children4(node)) {
|
|
5124
5597
|
walk(child, node, inside);
|
|
5125
5598
|
}
|
|
@@ -5151,7 +5624,7 @@ function collectFileConstants(filePath, root) {
|
|
|
5151
5624
|
}
|
|
5152
5625
|
};
|
|
5153
5626
|
walk(root, null, []);
|
|
5154
|
-
return { filePath, definitions, references };
|
|
5627
|
+
return { filePath, definitions, references, associations };
|
|
5155
5628
|
}
|
|
5156
5629
|
function emitConstantBindings(db, perFile) {
|
|
5157
5630
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -5175,6 +5648,13 @@ function emitConstantBindings(db, perFile) {
|
|
|
5175
5648
|
return found.length === 1 ? found[0].key : null;
|
|
5176
5649
|
};
|
|
5177
5650
|
for (const file of files) {
|
|
5651
|
+
for (const declared of file.associations) {
|
|
5652
|
+
db.add("declaresAssociation", [
|
|
5653
|
+
declared.classKey,
|
|
5654
|
+
declared.name,
|
|
5655
|
+
declared.target.key
|
|
5656
|
+
]);
|
|
5657
|
+
}
|
|
5178
5658
|
for (const reference of file.references) {
|
|
5179
5659
|
for (let depth = reference.nesting.length; depth >= 0; depth--) {
|
|
5180
5660
|
const candidate = qualify(
|
|
@@ -5199,7 +5679,7 @@ function emitConstantBindings(db, perFile) {
|
|
|
5199
5679
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
5200
5680
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
5201
5681
|
var import_datalog4 = require("@suss/datalog");
|
|
5202
|
-
var
|
|
5682
|
+
var import_resolution8 = require("@suss/resolution");
|
|
5203
5683
|
|
|
5204
5684
|
// src/parser.ts
|
|
5205
5685
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -5314,13 +5794,42 @@ function requiredRelativePath(node) {
|
|
|
5314
5794
|
return positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
5315
5795
|
}
|
|
5316
5796
|
|
|
5797
|
+
// src/pack.ts
|
|
5798
|
+
function inflectionsIn(packs) {
|
|
5799
|
+
const pooled = {
|
|
5800
|
+
acronyms: [],
|
|
5801
|
+
irregular: [],
|
|
5802
|
+
uncountable: [],
|
|
5803
|
+
singular: []
|
|
5804
|
+
};
|
|
5805
|
+
for (const pack of packs) {
|
|
5806
|
+
pooled.acronyms?.push(...pack.inflections?.acronyms ?? []);
|
|
5807
|
+
pooled.irregular?.push(...pack.inflections?.irregular ?? []);
|
|
5808
|
+
pooled.uncountable?.push(...pack.inflections?.uncountable ?? []);
|
|
5809
|
+
pooled.singular?.push(...pack.inflections?.singular ?? []);
|
|
5810
|
+
}
|
|
5811
|
+
return pooled;
|
|
5812
|
+
}
|
|
5813
|
+
function bodyBlocksIn(packs) {
|
|
5814
|
+
const pooled = /* @__PURE__ */ new Map();
|
|
5815
|
+
for (const pack of packs) {
|
|
5816
|
+
for (const declared of pack.bodyBlocks ?? []) {
|
|
5817
|
+
pooled.set(declared.name, {
|
|
5818
|
+
moduleOnly: declared.moduleOnly ?? false,
|
|
5819
|
+
definesClassMethods: declared.definesClassMethods ?? false
|
|
5820
|
+
});
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
return pooled;
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5317
5826
|
// src/reach/closure.ts
|
|
5318
5827
|
var import_behavioral_ir4 = require("@suss/behavioral-ir");
|
|
5319
5828
|
var import_datalog2 = require("@suss/datalog");
|
|
5320
5829
|
var import_extractor8 = require("@suss/extractor");
|
|
5321
5830
|
|
|
5322
5831
|
// src/reach/resolveCallee.ts
|
|
5323
|
-
var
|
|
5832
|
+
var import_resolution7 = require("@suss/resolution");
|
|
5324
5833
|
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
5325
5834
|
var INVOKES_RECEIVER = "call";
|
|
5326
5835
|
var STOP_FOR = {
|
|
@@ -5349,7 +5858,7 @@ function calleeSpellings(calls, ctx) {
|
|
|
5349
5858
|
}
|
|
5350
5859
|
return {
|
|
5351
5860
|
spellingOf,
|
|
5352
|
-
outcomes: (0,
|
|
5861
|
+
outcomes: (0, import_resolution7.calleeOutcomes)(ctx.facts, [...keys], RUBY_PROGRAM)
|
|
5353
5862
|
};
|
|
5354
5863
|
}
|
|
5355
5864
|
function resolveCallee(call, site, ctx, read) {
|
|
@@ -5363,11 +5872,11 @@ function resolveCallee(call, site, ctx, read) {
|
|
|
5363
5872
|
return asCallee(spelling, outcomeFor(spelling.key, ctx, read), site, ctx);
|
|
5364
5873
|
}
|
|
5365
5874
|
function outcomeFor(key, ctx, read) {
|
|
5366
|
-
return read?.outcomes.get(key) ?? (0,
|
|
5875
|
+
return read?.outcomes.get(key) ?? (0, import_resolution7.calleeOutcomeOf)(ctx.facts, key, RUBY_PROGRAM);
|
|
5367
5876
|
}
|
|
5368
5877
|
function mightReadAsACall(call, site, ctx) {
|
|
5369
5878
|
const spelling = spellingFor(call, site);
|
|
5370
|
-
return spelling.kind === "receiver" && (0,
|
|
5879
|
+
return spelling.kind === "receiver" && (0, import_resolution7.couldBeSettled)(ctx.facts, spelling.key);
|
|
5371
5880
|
}
|
|
5372
5881
|
function readsAsACall(call, site, ctx, read) {
|
|
5373
5882
|
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
@@ -5478,7 +5987,11 @@ function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
|
5478
5987
|
if (ancestry === void 0) {
|
|
5479
5988
|
return stop("outsideRun");
|
|
5480
5989
|
}
|
|
5481
|
-
const found = methodInAncestry(ancestry, methodName
|
|
5990
|
+
const found = methodInAncestry(ancestry, methodName, {
|
|
5991
|
+
facts: ctx.facts,
|
|
5992
|
+
bodyBlocks: ctx.bodyBlocks,
|
|
5993
|
+
dynamicNames: ctx.dynamicNames
|
|
5994
|
+
});
|
|
5482
5995
|
if (found.type === "found") {
|
|
5483
5996
|
return followed(reachedMethod(found.method, found.block, methodName));
|
|
5484
5997
|
}
|
|
@@ -5507,7 +6020,10 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
|
5507
6020
|
if (block.info.bodyNode === null) {
|
|
5508
6021
|
continue;
|
|
5509
6022
|
}
|
|
5510
|
-
const found = singletonMethodsByName(
|
|
6023
|
+
const found = singletonMethodsByName(
|
|
6024
|
+
block.info.bodyNode,
|
|
6025
|
+
ctx.bodyBlocks
|
|
6026
|
+
).get(methodName);
|
|
5511
6027
|
if (found !== void 0) {
|
|
5512
6028
|
return followed(reachedMethod(found, block, methodName));
|
|
5513
6029
|
}
|
|
@@ -5861,7 +6377,7 @@ function parameterName(param) {
|
|
|
5861
6377
|
|
|
5862
6378
|
// src/reach/context.ts
|
|
5863
6379
|
var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5864
|
-
async function buildReachContext(files, facts) {
|
|
6380
|
+
async function buildReachContext(files, facts, bodyBlocks = NO_BODY_BLOCKS, dynamicNames = /* @__PURE__ */ new Map()) {
|
|
5865
6381
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
5866
6382
|
const classes = [];
|
|
5867
6383
|
const classNames = /* @__PURE__ */ new Map();
|
|
@@ -5935,7 +6451,9 @@ async function buildReachContext(files, facts) {
|
|
|
5935
6451
|
topLevelMethods,
|
|
5936
6452
|
facts,
|
|
5937
6453
|
classNames,
|
|
5938
|
-
definitions
|
|
6454
|
+
definitions,
|
|
6455
|
+
bodyBlocks,
|
|
6456
|
+
dynamicNames
|
|
5939
6457
|
};
|
|
5940
6458
|
}
|
|
5941
6459
|
function namedChildren(node) {
|
|
@@ -5981,6 +6499,13 @@ function inheritedMethodsIn(packs) {
|
|
|
5981
6499
|
}
|
|
5982
6500
|
return found;
|
|
5983
6501
|
}
|
|
6502
|
+
function associationCallsIn(packs) {
|
|
6503
|
+
return packs.flatMap(
|
|
6504
|
+
(pack) => (pack.storage ?? []).flatMap(
|
|
6505
|
+
(pattern) => pattern.associations === void 0 ? [] : [pattern.associations]
|
|
6506
|
+
)
|
|
6507
|
+
);
|
|
6508
|
+
}
|
|
5984
6509
|
function emitStorageFacts(db, packs) {
|
|
5985
6510
|
for (const pack of packs) {
|
|
5986
6511
|
for (const pattern of pack.storage ?? []) {
|
|
@@ -6035,6 +6560,9 @@ async function extractRubyProject(options) {
|
|
|
6035
6560
|
);
|
|
6036
6561
|
const parsed = [];
|
|
6037
6562
|
const definitions = /* @__PURE__ */ new Map();
|
|
6563
|
+
const associationCalls = associationCallsIn(options.packs);
|
|
6564
|
+
const bodyBlocks = bodyBlocksIn(options.packs);
|
|
6565
|
+
const inflections = inflectionsIn(options.packs);
|
|
6038
6566
|
for (const file of options.files) {
|
|
6039
6567
|
await timer.timeAsync("parse", async () => {
|
|
6040
6568
|
const root = await cache.get(file);
|
|
@@ -6042,14 +6570,16 @@ async function extractRubyProject(options) {
|
|
|
6042
6570
|
return;
|
|
6043
6571
|
}
|
|
6044
6572
|
parsed.push({ file, root });
|
|
6045
|
-
emitValueFacts(db, file, root);
|
|
6573
|
+
emitValueFacts(db, file, root, bodyBlocks);
|
|
6046
6574
|
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
6047
6575
|
definitions.set(key, method);
|
|
6048
6576
|
}
|
|
6049
|
-
constants.push(
|
|
6577
|
+
constants.push(
|
|
6578
|
+
collectFileConstants(file, root, associationCalls, inflections)
|
|
6579
|
+
);
|
|
6050
6580
|
});
|
|
6051
6581
|
}
|
|
6052
|
-
timer.time("discover", () => {
|
|
6582
|
+
const dynamicNames = timer.time("discover", () => {
|
|
6053
6583
|
emitConstantBindings(db, constants);
|
|
6054
6584
|
const known = new Set(parsed.map(({ file }) => file));
|
|
6055
6585
|
for (const { file, root } of parsed) {
|
|
@@ -6057,6 +6587,10 @@ async function extractRubyProject(options) {
|
|
|
6057
6587
|
}
|
|
6058
6588
|
bindEvaluator(db, { files: parsed, definitions });
|
|
6059
6589
|
emitStorageFacts(db, options.packs);
|
|
6590
|
+
return readDynamicNames(
|
|
6591
|
+
db,
|
|
6592
|
+
new Map(parsed.map(({ file, root }) => [file, root]))
|
|
6593
|
+
);
|
|
6060
6594
|
});
|
|
6061
6595
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
6062
6596
|
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
@@ -6064,7 +6598,7 @@ async function extractRubyProject(options) {
|
|
|
6064
6598
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
6065
6599
|
const reachContext = await timer.timeAsync(
|
|
6066
6600
|
"discover",
|
|
6067
|
-
() => buildReachContext(parsed, db)
|
|
6601
|
+
() => buildReachContext(parsed, db, bodyBlocks, dynamicNames)
|
|
6068
6602
|
);
|
|
6069
6603
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? import_node_path4.default.relative(options.workspaceRoot, file) : file;
|
|
6070
6604
|
const seeds = [];
|
|
@@ -6084,6 +6618,8 @@ async function extractRubyProject(options) {
|
|
|
6084
6618
|
cache,
|
|
6085
6619
|
...storage === void 0 ? {} : { storage },
|
|
6086
6620
|
inheritedMethods,
|
|
6621
|
+
bodyBlocks,
|
|
6622
|
+
dynamicNames,
|
|
6087
6623
|
displayPathOf,
|
|
6088
6624
|
facts: db,
|
|
6089
6625
|
onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
|
|
@@ -6155,7 +6691,9 @@ async function extractRubyProject(options) {
|
|
|
6155
6691
|
context: reachContext,
|
|
6156
6692
|
displayPathOf,
|
|
6157
6693
|
...storage === void 0 ? {} : { storage },
|
|
6158
|
-
inheritedMethods
|
|
6694
|
+
inheritedMethods,
|
|
6695
|
+
bodyBlocks,
|
|
6696
|
+
dynamicNames
|
|
6159
6697
|
})
|
|
6160
6698
|
);
|
|
6161
6699
|
for (const entry of found) {
|
|
@@ -6272,7 +6810,7 @@ function findRubyFiles(root) {
|
|
|
6272
6810
|
}
|
|
6273
6811
|
|
|
6274
6812
|
// src/facts/why.ts
|
|
6275
|
-
var WITNESS_RULES = [...
|
|
6813
|
+
var WITNESS_RULES = [...import_resolution8.RESOLUTION_RULES, ...RUBY_RULES];
|
|
6276
6814
|
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
6277
6815
|
function namedChildrenOf(node) {
|
|
6278
6816
|
return node.namedChildren.filter((child) => child !== null);
|
|
@@ -6393,7 +6931,7 @@ var RubyWhySession = class {
|
|
|
6393
6931
|
* which the caller says in its own words.
|
|
6394
6932
|
*/
|
|
6395
6933
|
explain(value, options = {}) {
|
|
6396
|
-
return (0,
|
|
6934
|
+
return (0, import_resolution8.explainResolvedKey)({
|
|
6397
6935
|
db: this.db,
|
|
6398
6936
|
rules: WITNESS_RULES,
|
|
6399
6937
|
key: readKey(value.file, value.node, enclosingMethod2(value.node)),
|