@suss/adapter-ruby 0.26.0 → 0.28.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 +778 -250
- package/dist/index.d.cts +46 -14
- package/dist/index.d.ts +46 -14
- package/dist/index.js +768 -253
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -15,6 +15,45 @@ function field(node, name) {
|
|
|
15
15
|
function bodyStatements(body) {
|
|
16
16
|
return body.namedChildren.filter((child) => child !== null);
|
|
17
17
|
}
|
|
18
|
+
var BRANCH_FIELDS = {
|
|
19
|
+
if: ["consequence", "alternative"],
|
|
20
|
+
unless: ["consequence", "alternative"],
|
|
21
|
+
elsif: ["consequence", "alternative"],
|
|
22
|
+
if_modifier: ["body"],
|
|
23
|
+
unless_modifier: ["body"],
|
|
24
|
+
while: ["body"],
|
|
25
|
+
until: ["body"],
|
|
26
|
+
while_modifier: ["body"],
|
|
27
|
+
until_modifier: ["body"],
|
|
28
|
+
when: ["body"],
|
|
29
|
+
rescue: ["body"]
|
|
30
|
+
};
|
|
31
|
+
var STATEMENT_LISTS = /* @__PURE__ */ new Set(["then", "else", "do", "ensure", "begin"]);
|
|
32
|
+
var CLAUSE_TYPES = /* @__PURE__ */ new Set(["elsif", "else", "when", "rescue", "ensure"]);
|
|
33
|
+
function nestedStatements(stmt) {
|
|
34
|
+
const branchFields = BRANCH_FIELDS[stmt.type];
|
|
35
|
+
if (branchFields !== void 0) {
|
|
36
|
+
return branchFields.flatMap((name) => {
|
|
37
|
+
const branch = field(stmt, name);
|
|
38
|
+
return branch === null ? [] : statementsRunBy(branch);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (STATEMENT_LISTS.has(stmt.type)) {
|
|
42
|
+
return bodyStatements(stmt).flatMap(
|
|
43
|
+
(child) => CLAUSE_TYPES.has(child.type) ? nestedStatements(child) : [child]
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (stmt.type === "case") {
|
|
47
|
+
return bodyStatements(stmt).filter((child) => CLAUSE_TYPES.has(child.type)).flatMap(nestedStatements);
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
function statementsRunBy(branch) {
|
|
52
|
+
if (STATEMENT_LISTS.has(branch.type) || CLAUSE_TYPES.has(branch.type)) {
|
|
53
|
+
return nestedStatements(branch);
|
|
54
|
+
}
|
|
55
|
+
return [branch];
|
|
56
|
+
}
|
|
18
57
|
var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
19
58
|
"method",
|
|
20
59
|
"singleton_method",
|
|
@@ -78,8 +117,8 @@ var VISIBILITY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
78
117
|
"protected",
|
|
79
118
|
"public"
|
|
80
119
|
]);
|
|
81
|
-
function visibilityKeyword(
|
|
82
|
-
return VISIBILITY_KEYWORDS.has(
|
|
120
|
+
function visibilityKeyword(text2) {
|
|
121
|
+
return VISIBILITY_KEYWORDS.has(text2) ? text2 : null;
|
|
83
122
|
}
|
|
84
123
|
function setVisibility(visibility, name, keyword) {
|
|
85
124
|
if (keyword === "public") {
|
|
@@ -202,14 +241,31 @@ var NodeMap = class extends IdMap {
|
|
|
202
241
|
// src/constantPath.ts
|
|
203
242
|
import fs from "fs";
|
|
204
243
|
import path from "path";
|
|
205
|
-
function underscoreConstantPath(qualifiedName) {
|
|
206
|
-
return qualifiedName.replaceAll("::", "/").replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
244
|
+
function underscoreConstantPath(qualifiedName, acronyms = []) {
|
|
245
|
+
return lowerAcronyms(qualifiedName.replaceAll("::", "/"), acronyms).replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
246
|
+
}
|
|
247
|
+
function lowerAcronyms(word, acronyms) {
|
|
248
|
+
if (acronyms.length === 0) {
|
|
249
|
+
return word;
|
|
250
|
+
}
|
|
251
|
+
const alternatives = acronyms.map(escapeRegExp).join("|");
|
|
252
|
+
const acronymRegex = new RegExp(
|
|
253
|
+
`(?:(?<=([A-Za-z\\d]))|\\b)(${alternatives})(?=\\b|[^a-z])`,
|
|
254
|
+
"g"
|
|
255
|
+
);
|
|
256
|
+
return word.replace(
|
|
257
|
+
acronymRegex,
|
|
258
|
+
(_match, before, acronym) => `${before === void 0 ? "" : "_"}${acronym.toLowerCase()}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function escapeRegExp(text2) {
|
|
262
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
263
|
}
|
|
208
264
|
var PATH_CONVENTIONS = {
|
|
209
265
|
railsUnderscore: underscoreConstantPath
|
|
210
266
|
};
|
|
211
|
-
function resolveConstantFile(root, qualifiedName, convention) {
|
|
212
|
-
const relative = `${PATH_CONVENTIONS[convention](qualifiedName)}.rb`;
|
|
267
|
+
function resolveConstantFile(root, qualifiedName, convention, acronyms = []) {
|
|
268
|
+
const relative = `${PATH_CONVENTIONS[convention](qualifiedName, acronyms)}.rb`;
|
|
213
269
|
for (const candidate of [root, ...autoloadDirectories(root)]) {
|
|
214
270
|
const file = path.join(candidate, relative);
|
|
215
271
|
if (fs.existsSync(file)) {
|
|
@@ -283,12 +339,19 @@ function walkBody(statements, nesting, visit) {
|
|
|
283
339
|
}
|
|
284
340
|
}
|
|
285
341
|
function ownIdentity(nameNode, outerNesting) {
|
|
286
|
-
const qualifiedName =
|
|
342
|
+
const qualifiedName = definedName(nameNode, outerNesting);
|
|
287
343
|
if (qualifiedName === null) {
|
|
288
344
|
return null;
|
|
289
345
|
}
|
|
290
346
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
291
347
|
}
|
|
348
|
+
function definedName(nameNode, outerNesting) {
|
|
349
|
+
const innermost = outerNesting[0] ?? null;
|
|
350
|
+
if (nameNode.type === "scope_resolution" && innermost !== null && !nameNode.text.startsWith("::")) {
|
|
351
|
+
return `${innermost}::${nameNode.text}`;
|
|
352
|
+
}
|
|
353
|
+
return qualifyConstantRef(nameNode, outerNesting);
|
|
354
|
+
}
|
|
292
355
|
function visitClass(node, nesting, visit) {
|
|
293
356
|
const nameNode = field(node, "name");
|
|
294
357
|
const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
|
|
@@ -352,7 +415,8 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
352
415
|
const filePath = resolveConstantFile(
|
|
353
416
|
lookup.root,
|
|
354
417
|
qualifiedName,
|
|
355
|
-
lookup.pathConvention
|
|
418
|
+
lookup.pathConvention,
|
|
419
|
+
lookup.acronyms
|
|
356
420
|
);
|
|
357
421
|
if (filePath === null) {
|
|
358
422
|
return null;
|
|
@@ -394,7 +458,10 @@ async function chainOf(self, lookup, active) {
|
|
|
394
458
|
async function superclassChain(self, lookup, active) {
|
|
395
459
|
const candidates = superclassCandidatesOf(self.blocks);
|
|
396
460
|
for (const candidate of candidates) {
|
|
397
|
-
if (lookup.ancestryRootClassNames.includes(candidate)
|
|
461
|
+
if (lookup.ancestryRootClassNames.includes(candidate)) {
|
|
462
|
+
return [{ type: "root", name: candidate }];
|
|
463
|
+
}
|
|
464
|
+
if (active.has(candidate)) {
|
|
398
465
|
return [];
|
|
399
466
|
}
|
|
400
467
|
const blocks = await definitionOf(candidate, lookup);
|
|
@@ -472,6 +539,9 @@ function superclassCandidatesOf(blocks) {
|
|
|
472
539
|
}
|
|
473
540
|
function methodInAncestry(ancestry, name) {
|
|
474
541
|
for (const entry of ancestry) {
|
|
542
|
+
if (entry.type === "root") {
|
|
543
|
+
return { type: "none" };
|
|
544
|
+
}
|
|
475
545
|
if (entry.type === "unfollowed") {
|
|
476
546
|
return {
|
|
477
547
|
type: "unsettled",
|
|
@@ -1207,21 +1277,13 @@ function calledMethod(node) {
|
|
|
1207
1277
|
|
|
1208
1278
|
// src/values/evaluator.ts
|
|
1209
1279
|
import { nodeOfKey } from "@suss/resolution";
|
|
1210
|
-
import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
|
|
1280
|
+
import { Evaluator, force, literalOf as literalOf2, text } from "@suss/values";
|
|
1211
1281
|
|
|
1212
1282
|
// src/facts/resolve.ts
|
|
1283
|
+
import { constant, lit, rule, variable as v } from "@suss/datalog";
|
|
1213
1284
|
import {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
evaluate,
|
|
1217
|
-
lit,
|
|
1218
|
-
rule,
|
|
1219
|
-
variable as v
|
|
1220
|
-
} from "@suss/datalog";
|
|
1221
|
-
import {
|
|
1222
|
-
ANSWER_RELATIONS,
|
|
1223
|
-
RESOLUTION_QUESTIONS,
|
|
1224
|
-
RESOLUTION_RULES,
|
|
1285
|
+
askResolution,
|
|
1286
|
+
resolutionProgram,
|
|
1225
1287
|
writtenValueOf as sharedWrittenValueOf,
|
|
1226
1288
|
VALUE_STEP
|
|
1227
1289
|
} from "@suss/resolution";
|
|
@@ -1239,18 +1301,9 @@ var RUBY_RULES = [
|
|
|
1239
1301
|
]
|
|
1240
1302
|
)
|
|
1241
1303
|
];
|
|
1242
|
-
var
|
|
1243
|
-
[...RESOLUTION_RULES, ...RUBY_RULES, ...RESOLUTION_QUESTIONS],
|
|
1244
|
-
ANSWER_RELATIONS
|
|
1245
|
-
);
|
|
1304
|
+
var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
|
|
1246
1305
|
function resolveValues(db, keys) {
|
|
1247
|
-
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
for (const key of keys) {
|
|
1251
|
-
db.add("wanted", [key]);
|
|
1252
|
-
}
|
|
1253
|
-
evaluate(db, RESOLUTION_PROGRAM.rules);
|
|
1306
|
+
askResolution(db, keys, "wanted", RUBY_PROGRAM);
|
|
1254
1307
|
}
|
|
1255
1308
|
function resolvedFunctions(db, key) {
|
|
1256
1309
|
return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
|
|
@@ -1261,27 +1314,262 @@ function writtenValueOf(db, key) {
|
|
|
1261
1314
|
}
|
|
1262
1315
|
|
|
1263
1316
|
// src/facts/values.ts
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1317
|
+
import { startsAtName, valueLeftByWrites } from "@suss/resolution";
|
|
1318
|
+
|
|
1319
|
+
// src/facts/locals.ts
|
|
1320
|
+
import { writesRunInOrder } from "@suss/resolution";
|
|
1321
|
+
var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1322
|
+
var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES2]);
|
|
1323
|
+
var RUBY_NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1324
|
+
"identifier",
|
|
1325
|
+
"instance_variable"
|
|
1326
|
+
]);
|
|
1327
|
+
var TARGET_LIST_TYPES = /* @__PURE__ */ new Set([
|
|
1328
|
+
"left_assignment_list",
|
|
1329
|
+
"destructured_left_assignment",
|
|
1330
|
+
"rest_assignment"
|
|
1331
|
+
]);
|
|
1332
|
+
var WHOLE_VALUE_OPERATORS = /* @__PURE__ */ new Set([
|
|
1333
|
+
"||=",
|
|
1334
|
+
"&&="
|
|
1335
|
+
]);
|
|
1270
1336
|
function paramNameOf(param) {
|
|
1271
1337
|
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
1272
1338
|
}
|
|
1273
|
-
function
|
|
1274
|
-
const params = field(
|
|
1275
|
-
|
|
1276
|
-
|
|
1339
|
+
function parametersOf(owner) {
|
|
1340
|
+
const params = field(owner, "parameters");
|
|
1341
|
+
return params === null ? [] : bodyStatements(params);
|
|
1342
|
+
}
|
|
1343
|
+
function parameterNames2(owner) {
|
|
1344
|
+
const names = /* @__PURE__ */ new Set();
|
|
1345
|
+
for (const param of parametersOf(owner)) {
|
|
1277
1346
|
const name = paramNameOf(param);
|
|
1278
1347
|
if (name !== null) {
|
|
1279
|
-
|
|
1348
|
+
names.add(name.text);
|
|
1280
1349
|
}
|
|
1281
1350
|
}
|
|
1282
|
-
return
|
|
1351
|
+
return names;
|
|
1352
|
+
}
|
|
1353
|
+
function targetsOf(left) {
|
|
1354
|
+
if (left.type === "identifier" || left.type === "constant") {
|
|
1355
|
+
return [left];
|
|
1356
|
+
}
|
|
1357
|
+
if (TARGET_LIST_TYPES.has(left.type)) {
|
|
1358
|
+
return bodyStatements(left).flatMap(targetsOf);
|
|
1359
|
+
}
|
|
1360
|
+
return [];
|
|
1361
|
+
}
|
|
1362
|
+
function writeOf(target, value, at, fromParameter) {
|
|
1363
|
+
return { name: target.text, target, value, at, fromParameter };
|
|
1364
|
+
}
|
|
1365
|
+
function readAssignment(node) {
|
|
1366
|
+
const left = field(node, "left");
|
|
1367
|
+
const right = field(node, "right");
|
|
1368
|
+
if (left === null || right === null) {
|
|
1369
|
+
return [];
|
|
1370
|
+
}
|
|
1371
|
+
const value = left.type === "left_assignment_list" ? null : right;
|
|
1372
|
+
return targetsOf(left).map((target) => writeOf(target, value, node, false));
|
|
1373
|
+
}
|
|
1374
|
+
function readOperatorAssignment(node) {
|
|
1375
|
+
const left = field(node, "left");
|
|
1376
|
+
const right = field(node, "right");
|
|
1377
|
+
const operator = field(node, "operator");
|
|
1378
|
+
if (left === null || right === null || left.type !== "identifier") {
|
|
1379
|
+
return [];
|
|
1380
|
+
}
|
|
1381
|
+
const whole = operator !== null && WHOLE_VALUE_OPERATORS.has(operator.text);
|
|
1382
|
+
return [writeOf(left, whole ? right : null, node, false)];
|
|
1383
|
+
}
|
|
1384
|
+
function readForVariable(node) {
|
|
1385
|
+
const pattern = field(node, "pattern");
|
|
1386
|
+
if (pattern === null) {
|
|
1387
|
+
return [];
|
|
1388
|
+
}
|
|
1389
|
+
return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
|
|
1390
|
+
}
|
|
1391
|
+
function readBlockParameters(node) {
|
|
1392
|
+
const found = [];
|
|
1393
|
+
for (const param of parametersOf(node)) {
|
|
1394
|
+
const name = paramNameOf(param);
|
|
1395
|
+
if (name !== null) {
|
|
1396
|
+
found.push(writeOf(name, null, param, true));
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return found;
|
|
1400
|
+
}
|
|
1401
|
+
var WRITE_READERS = {
|
|
1402
|
+
assignment: readAssignment,
|
|
1403
|
+
operator_assignment: readOperatorAssignment,
|
|
1404
|
+
for: readForVariable,
|
|
1405
|
+
block: readBlockParameters,
|
|
1406
|
+
do_block: readBlockParameters
|
|
1407
|
+
};
|
|
1408
|
+
function writesUnder(body) {
|
|
1409
|
+
const found = [];
|
|
1410
|
+
const visit = (node) => {
|
|
1411
|
+
for (const child of bodyStatements(node)) {
|
|
1412
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
found.push(...WRITE_READERS[child.type]?.(child) ?? []);
|
|
1416
|
+
visit(child);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
visit(body);
|
|
1420
|
+
return found;
|
|
1421
|
+
}
|
|
1422
|
+
var namesByTree = /* @__PURE__ */ new WeakMap();
|
|
1423
|
+
function cacheFor(tree) {
|
|
1424
|
+
const known = namesByTree.get(tree);
|
|
1425
|
+
if (known !== void 0) {
|
|
1426
|
+
return known;
|
|
1427
|
+
}
|
|
1428
|
+
const fresh = { file: null, byMethod: new NodeMap() };
|
|
1429
|
+
namesByTree.set(tree, fresh);
|
|
1430
|
+
return fresh;
|
|
1431
|
+
}
|
|
1432
|
+
function scopeNames(parameters, body) {
|
|
1433
|
+
const locals = new Set(parameters);
|
|
1434
|
+
const blockParams = /* @__PURE__ */ new Set();
|
|
1435
|
+
for (const write of body === null ? [] : writesUnder(body)) {
|
|
1436
|
+
if (write.fromParameter) {
|
|
1437
|
+
blockParams.add(write.name);
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
locals.add(write.name);
|
|
1441
|
+
}
|
|
1442
|
+
return { locals, blockParams };
|
|
1443
|
+
}
|
|
1444
|
+
function namesOfMethod(method) {
|
|
1445
|
+
const cache = cacheFor(method.tree);
|
|
1446
|
+
const remembered = cache.byMethod.get(method);
|
|
1447
|
+
if (remembered !== void 0) {
|
|
1448
|
+
return remembered;
|
|
1449
|
+
}
|
|
1450
|
+
const names = scopeNames(parameterNames2(method), field(method, "body"));
|
|
1451
|
+
cache.byMethod.set(method, names);
|
|
1452
|
+
return names;
|
|
1453
|
+
}
|
|
1454
|
+
function namesOfFile(tree) {
|
|
1455
|
+
const cache = cacheFor(tree);
|
|
1456
|
+
cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
|
|
1457
|
+
return cache.file;
|
|
1283
1458
|
}
|
|
1284
|
-
function
|
|
1459
|
+
function ownerOfName(node, name, enclosing) {
|
|
1460
|
+
if (node.type === "constant") {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
|
|
1464
|
+
if (names.blockParams.has(name)) {
|
|
1465
|
+
const block = blockDeclaring(node, name);
|
|
1466
|
+
if (block !== null) {
|
|
1467
|
+
return block;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return enclosing !== null && names.locals.has(name) ? enclosing : null;
|
|
1471
|
+
}
|
|
1472
|
+
function blockDeclaring(node, name) {
|
|
1473
|
+
let current = node.parent;
|
|
1474
|
+
while (current !== null) {
|
|
1475
|
+
if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
|
|
1476
|
+
return current;
|
|
1477
|
+
}
|
|
1478
|
+
if (OWN_BODY_TYPES.has(current.type)) {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
current = current.parent;
|
|
1482
|
+
}
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
function parameterWrites(method) {
|
|
1486
|
+
const found = [];
|
|
1487
|
+
for (const param of method === null ? [] : parametersOf(method)) {
|
|
1488
|
+
const name = paramNameOf(param);
|
|
1489
|
+
if (name !== null) {
|
|
1490
|
+
found.push(writeOf(name, name, param, true));
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return found;
|
|
1494
|
+
}
|
|
1495
|
+
function collectWrites(method, body) {
|
|
1496
|
+
const writes = [...parameterWrites(method), ...writesUnder(body)].sort(
|
|
1497
|
+
(left, right) => left.target.startIndex - right.target.startIndex
|
|
1498
|
+
);
|
|
1499
|
+
const targetIds = new Set(writes.map((write) => write.target.id));
|
|
1500
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1501
|
+
for (const write of writes) {
|
|
1502
|
+
const owner = ownerOfName(write.target, write.name, method);
|
|
1503
|
+
const key = `${owner === null ? "" : owner.id} ${write.name}`;
|
|
1504
|
+
const group = groups.get(key);
|
|
1505
|
+
if (group === void 0) {
|
|
1506
|
+
groups.set(key, {
|
|
1507
|
+
owner,
|
|
1508
|
+
name: write.name,
|
|
1509
|
+
writes: [write],
|
|
1510
|
+
ordered: false
|
|
1511
|
+
});
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
group.writes.push(write);
|
|
1515
|
+
}
|
|
1516
|
+
const rules = nameReads(targetIds);
|
|
1517
|
+
for (const group of groups.values()) {
|
|
1518
|
+
const scope = group.owner === null ? body : field(group.owner, "body");
|
|
1519
|
+
if (scope === null) {
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
group.ordered = writesRunInOrder(
|
|
1523
|
+
scope,
|
|
1524
|
+
group.name,
|
|
1525
|
+
group.writes.map((write) => orderedWrite(write, scope)),
|
|
1526
|
+
rules
|
|
1527
|
+
);
|
|
1528
|
+
}
|
|
1529
|
+
return [...groups.values()];
|
|
1530
|
+
}
|
|
1531
|
+
function orderedWrite(write, scope) {
|
|
1532
|
+
return {
|
|
1533
|
+
at: write.at,
|
|
1534
|
+
direct: !write.fromParameter && write.at.parent?.id === scope.id
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
function nameReads(targetIds) {
|
|
1538
|
+
return {
|
|
1539
|
+
nameTypes: RUBY_NAME_TYPES,
|
|
1540
|
+
laterBodies: LATER_BODY_TYPES,
|
|
1541
|
+
childrenOf: bodyStatements,
|
|
1542
|
+
isRead: (node) => isNameRead(node, targetIds)
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
function isNameRead(node, targetIds) {
|
|
1546
|
+
if (targetIds.has(node.id)) {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
const parent = node.parent;
|
|
1550
|
+
return !(parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/facts/values.ts
|
|
1554
|
+
function nodeId(filePath, node) {
|
|
1555
|
+
return `${filePath}:${node.startIndex}-${node.endIndex}`;
|
|
1556
|
+
}
|
|
1557
|
+
function nameId(filePath, name) {
|
|
1558
|
+
return `${filePath}#${name}`;
|
|
1559
|
+
}
|
|
1560
|
+
function readThrough(node) {
|
|
1561
|
+
if (node.type !== "parenthesized_statements") {
|
|
1562
|
+
return node;
|
|
1563
|
+
}
|
|
1564
|
+
const inner = node.namedChildren.filter((child) => child !== null);
|
|
1565
|
+
return inner.length === 1 ? readThrough(inner[0]) : node;
|
|
1566
|
+
}
|
|
1567
|
+
function nameKey(filePath, node, enclosing) {
|
|
1568
|
+
const owner = ownerOfName(node, node.text, enclosing);
|
|
1569
|
+
return owner === null ? nameId(filePath, node.text) : `${nodeId(filePath, owner)}#${node.text}`;
|
|
1570
|
+
}
|
|
1571
|
+
function readKey(filePath, written, enclosing) {
|
|
1572
|
+
const node = readThrough(written);
|
|
1285
1573
|
if (node.type !== "identifier" && node.type !== "constant") {
|
|
1286
1574
|
return nodeId(filePath, node);
|
|
1287
1575
|
}
|
|
@@ -1289,10 +1577,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1289
1577
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1290
1578
|
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1291
1579
|
}
|
|
1292
|
-
|
|
1293
|
-
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
1294
|
-
}
|
|
1295
|
-
return nameId(filePath, node.text);
|
|
1580
|
+
return nameKey(filePath, node, enclosing);
|
|
1296
1581
|
}
|
|
1297
1582
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
1298
1583
|
"string",
|
|
@@ -1321,15 +1606,12 @@ function children2(node) {
|
|
|
1321
1606
|
function add(emitter, relation, ...tuple) {
|
|
1322
1607
|
emitter.db.add(relation, tuple);
|
|
1323
1608
|
}
|
|
1324
|
-
function valueKey(emitter,
|
|
1609
|
+
function valueKey(emitter, written) {
|
|
1610
|
+
const value = readThrough(written);
|
|
1325
1611
|
if (value.type !== "identifier" && value.type !== "constant") {
|
|
1326
1612
|
return nodeId(emitter.filePath, value);
|
|
1327
1613
|
}
|
|
1328
|
-
|
|
1329
|
-
if (enclosing !== null && enclosing.params.has(value.text)) {
|
|
1330
|
-
return `${enclosing.funcKey}#${value.text}`;
|
|
1331
|
-
}
|
|
1332
|
-
return nameId(emitter.filePath, value.text);
|
|
1614
|
+
return nameKey(emitter.filePath, value, emitter.enclosing);
|
|
1333
1615
|
}
|
|
1334
1616
|
function pairKeyText(key) {
|
|
1335
1617
|
if (key.type === "hash_key_symbol") {
|
|
@@ -1468,8 +1750,72 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1468
1750
|
if (child.type === "nil") {
|
|
1469
1751
|
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
1470
1752
|
}
|
|
1753
|
+
if (child.type === "self" && emitter.selfKey !== null) {
|
|
1754
|
+
add(emitter, "binds", nodeId(emitter.filePath, child), emitter.selfKey);
|
|
1755
|
+
}
|
|
1756
|
+
if (child.type === "instance_variable") {
|
|
1757
|
+
emitInstanceRead(emitter, child);
|
|
1758
|
+
}
|
|
1759
|
+
if (ASSIGNMENT_TYPES.has(child.type)) {
|
|
1760
|
+
collectInstanceWrite(emitter, child);
|
|
1761
|
+
}
|
|
1471
1762
|
});
|
|
1472
1763
|
}
|
|
1764
|
+
var ASSIGNMENT_TYPES = /* @__PURE__ */ new Set(["assignment", "operator_assignment"]);
|
|
1765
|
+
function isWriteTarget(node) {
|
|
1766
|
+
const parent = node.parent;
|
|
1767
|
+
return parent !== null && ASSIGNMENT_TYPES.has(parent.type) && field(parent, "left")?.id === node.id;
|
|
1768
|
+
}
|
|
1769
|
+
function emitInstanceRead(emitter, node) {
|
|
1770
|
+
if (emitter.selfKey === null || isWriteTarget(node)) {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
add(
|
|
1774
|
+
emitter,
|
|
1775
|
+
"readsProperty",
|
|
1776
|
+
nodeId(emitter.filePath, node),
|
|
1777
|
+
emitter.selfKey,
|
|
1778
|
+
node.text
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
function collectInstanceWrite(emitter, node) {
|
|
1782
|
+
const left = field(node, "left");
|
|
1783
|
+
const right = field(node, "right");
|
|
1784
|
+
const collector = emitter.instanceWrites;
|
|
1785
|
+
if (left === null || right === null || collector === null) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (left.type !== "instance_variable") {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
const operator = field(node, "operator")?.text;
|
|
1792
|
+
const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
|
|
1793
|
+
const written = collector.get(left.text) ?? [];
|
|
1794
|
+
written.push(
|
|
1795
|
+
describeWrite(emitter, {
|
|
1796
|
+
name: left.text,
|
|
1797
|
+
target: left,
|
|
1798
|
+
value,
|
|
1799
|
+
at: node,
|
|
1800
|
+
fromParameter: false
|
|
1801
|
+
})
|
|
1802
|
+
);
|
|
1803
|
+
collector.set(left.text, written);
|
|
1804
|
+
}
|
|
1805
|
+
function emitInstanceWrites(emitter, classKey, collected) {
|
|
1806
|
+
for (const [name, writes] of collected) {
|
|
1807
|
+
const settled = valueLeftByWrites(writes, false);
|
|
1808
|
+
if (settled !== null) {
|
|
1809
|
+
add(emitter, "holdsProperty", classKey, name, settled);
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
for (const write of writes) {
|
|
1813
|
+
if (write.value !== null && !write.narrowsName) {
|
|
1814
|
+
add(emitter, "holdsProperty", classKey, name, write.value);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1473
1819
|
function implicitReturn(body) {
|
|
1474
1820
|
const statements = children2(body).filter(
|
|
1475
1821
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
@@ -1480,23 +1826,17 @@ function implicitReturn(body) {
|
|
|
1480
1826
|
function emitMethodFacts(emitter, method) {
|
|
1481
1827
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1482
1828
|
add(emitter, "func", funcKey);
|
|
1483
|
-
const params = field(method, "parameters");
|
|
1484
|
-
const declared = /* @__PURE__ */ new Set();
|
|
1485
1829
|
let position = 0;
|
|
1486
|
-
for (const param of
|
|
1830
|
+
for (const param of parametersOf(method)) {
|
|
1487
1831
|
const paramName = paramNameOf(param);
|
|
1488
1832
|
if (paramName !== null) {
|
|
1489
|
-
declared.add(paramName.text);
|
|
1490
1833
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
1491
1834
|
add(emitter, "paramOf", funcKey, String(position), paramKey);
|
|
1492
1835
|
add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
|
|
1493
1836
|
}
|
|
1494
1837
|
position += 1;
|
|
1495
1838
|
}
|
|
1496
|
-
const inside = {
|
|
1497
|
-
...emitter,
|
|
1498
|
-
enclosing: { funcKey, params: declared }
|
|
1499
|
-
};
|
|
1839
|
+
const inside = { ...emitter, enclosing: method };
|
|
1500
1840
|
const body = field(method, "body");
|
|
1501
1841
|
if (body === null) {
|
|
1502
1842
|
return funcKey;
|
|
@@ -1528,31 +1868,78 @@ function emitMethodFacts(emitter, method) {
|
|
|
1528
1868
|
add(inside, "returnsValue", funcKey, valueKey(inside, implicit));
|
|
1529
1869
|
}
|
|
1530
1870
|
emitExpressionFacts(inside, body);
|
|
1871
|
+
emitScopeWrites(inside, method, body);
|
|
1531
1872
|
return funcKey;
|
|
1532
1873
|
}
|
|
1533
|
-
function
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1874
|
+
function isConstruction(value) {
|
|
1875
|
+
return value.type === "call" || ARRAY_TYPES.has(value.type) || value.type === "hash";
|
|
1876
|
+
}
|
|
1877
|
+
function sourceOf(node) {
|
|
1878
|
+
return node.text.replace(/\s+/g, " ").trim();
|
|
1879
|
+
}
|
|
1880
|
+
function readFirst(node) {
|
|
1881
|
+
const inner = readThrough(node);
|
|
1882
|
+
if (inner !== node) {
|
|
1883
|
+
return inner;
|
|
1538
1884
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1885
|
+
return node.type === "call" ? field(node, "receiver") : null;
|
|
1886
|
+
}
|
|
1887
|
+
var CHAIN_READS = {
|
|
1888
|
+
nameTypes: RUBY_NAME_TYPES,
|
|
1889
|
+
readFirst
|
|
1890
|
+
};
|
|
1891
|
+
function describeWrite(emitter, write) {
|
|
1892
|
+
const value = write.value === null ? null : readThrough(write.value);
|
|
1893
|
+
return {
|
|
1894
|
+
value: value === null ? null : valueKey(emitter, value),
|
|
1895
|
+
placeholder: value?.type === "nil",
|
|
1896
|
+
construction: value !== null && isConstruction(value) ? sourceOf(value) : null,
|
|
1897
|
+
narrowsName: value !== null && value.type === "call" && startsAtName(value, write.name, CHAIN_READS)
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
function settledValue(emitter, group) {
|
|
1901
|
+
const only = group.writes.length === 1 ? group.writes[0] : void 0;
|
|
1902
|
+
if (only !== void 0) {
|
|
1903
|
+
return only.fromParameter || only.value === null ? null : valueKey(emitter, only.value);
|
|
1541
1904
|
}
|
|
1542
|
-
|
|
1543
|
-
emitter,
|
|
1544
|
-
|
|
1545
|
-
nameId(emitter.filePath, left.text),
|
|
1546
|
-
valueKey(emitter, right)
|
|
1547
|
-
);
|
|
1548
|
-
add(
|
|
1549
|
-
emitter,
|
|
1550
|
-
"exportsAs",
|
|
1551
|
-
emitter.filePath,
|
|
1552
|
-
left.text,
|
|
1553
|
-
valueKey(emitter, right)
|
|
1905
|
+
return valueLeftByWrites(
|
|
1906
|
+
group.writes.map((write) => describeWrite(emitter, write)),
|
|
1907
|
+
group.ordered
|
|
1554
1908
|
);
|
|
1555
1909
|
}
|
|
1910
|
+
function emitScopeWrites(emitter, method, body) {
|
|
1911
|
+
for (const group of collectWrites(method, body)) {
|
|
1912
|
+
const settled = settledValue(emitter, group);
|
|
1913
|
+
const key = group.owner === null ? nameId(emitter.filePath, group.name) : `${nodeId(emitter.filePath, group.owner)}#${group.name}`;
|
|
1914
|
+
if (settled === null) {
|
|
1915
|
+
emitCandidates(emitter, key, group);
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
add(
|
|
1919
|
+
emitter,
|
|
1920
|
+
group.writes.length === 1 ? "binds" : "endsHolding",
|
|
1921
|
+
key,
|
|
1922
|
+
settled
|
|
1923
|
+
);
|
|
1924
|
+
if (group.owner === null) {
|
|
1925
|
+
add(emitter, "exportsAs", emitter.filePath, group.name, settled);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
function emitCandidates(emitter, key, group) {
|
|
1930
|
+
for (const write of group.writes) {
|
|
1931
|
+
if (write.value === null) {
|
|
1932
|
+
add(emitter, "writesUnstated", key);
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
if (write.fromParameter) {
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (!describeWrite(emitter, write).narrowsName) {
|
|
1939
|
+
add(emitter, "mayHold", key, valueKey(emitter, write.value));
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1556
1943
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1557
1944
|
function emitClassFacts(emitter, cls) {
|
|
1558
1945
|
const classKey = nodeId(emitter.filePath, cls);
|
|
@@ -1564,6 +1951,12 @@ function emitClassFacts(emitter, cls) {
|
|
|
1564
1951
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1565
1952
|
}
|
|
1566
1953
|
const body = field(cls, "body");
|
|
1954
|
+
const collected = /* @__PURE__ */ new Map();
|
|
1955
|
+
const within = {
|
|
1956
|
+
...emitter,
|
|
1957
|
+
selfKey: classKey,
|
|
1958
|
+
instanceWrites: collected
|
|
1959
|
+
};
|
|
1567
1960
|
for (const statement of body === null ? [] : children2(body)) {
|
|
1568
1961
|
if (statement.type === "assignment") {
|
|
1569
1962
|
const left = field(statement, "left");
|
|
@@ -1587,16 +1980,23 @@ function emitClassFacts(emitter, cls) {
|
|
|
1587
1980
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
1588
1981
|
continue;
|
|
1589
1982
|
}
|
|
1590
|
-
const funcKey = emitMethodFacts(
|
|
1983
|
+
const funcKey = emitMethodFacts(within, statement);
|
|
1591
1984
|
const name = field(statement, "name");
|
|
1592
1985
|
if (name !== null) {
|
|
1593
1986
|
add(emitter, "holdsProperty", classKey, name.text, funcKey);
|
|
1594
1987
|
}
|
|
1595
1988
|
}
|
|
1989
|
+
emitInstanceWrites(within, classKey, collected);
|
|
1596
1990
|
return classKey;
|
|
1597
1991
|
}
|
|
1598
1992
|
function emitValueFacts(db, filePath, root) {
|
|
1599
|
-
const emitter = {
|
|
1993
|
+
const emitter = {
|
|
1994
|
+
db,
|
|
1995
|
+
filePath,
|
|
1996
|
+
enclosing: null,
|
|
1997
|
+
selfKey: null,
|
|
1998
|
+
instanceWrites: null
|
|
1999
|
+
};
|
|
1600
2000
|
const declaresName = (child, key) => {
|
|
1601
2001
|
const name = field(child, "name");
|
|
1602
2002
|
if (name === null) {
|
|
@@ -1614,14 +2014,12 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1614
2014
|
if (METHOD_TYPES.has(child.type)) {
|
|
1615
2015
|
declaresName(child, emitMethodFacts(emitter, child));
|
|
1616
2016
|
}
|
|
1617
|
-
if (child.type === "assignment") {
|
|
1618
|
-
emitAssignment(emitter, child);
|
|
1619
|
-
}
|
|
1620
2017
|
walk(child);
|
|
1621
2018
|
}
|
|
1622
2019
|
};
|
|
1623
2020
|
walk(root);
|
|
1624
2021
|
emitExpressionFacts(emitter, root);
|
|
2022
|
+
emitScopeWrites(emitter, null, root);
|
|
1625
2023
|
}
|
|
1626
2024
|
|
|
1627
2025
|
// src/values/lowering.ts
|
|
@@ -1637,7 +2035,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1637
2035
|
"module",
|
|
1638
2036
|
"singleton_class"
|
|
1639
2037
|
]);
|
|
1640
|
-
var
|
|
2038
|
+
var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1641
2039
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1642
2040
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1643
2041
|
"identifier",
|
|
@@ -2143,7 +2541,7 @@ function isTailExpression(node) {
|
|
|
2143
2541
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
2144
2542
|
return true;
|
|
2145
2543
|
}
|
|
2146
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
2544
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES3.has(owner.type)) {
|
|
2147
2545
|
return true;
|
|
2148
2546
|
}
|
|
2149
2547
|
if (owner.type === "when") {
|
|
@@ -2162,7 +2560,7 @@ function isRoot(node) {
|
|
|
2162
2560
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
2163
2561
|
return true;
|
|
2164
2562
|
}
|
|
2165
|
-
return
|
|
2563
|
+
return BLOCK_TYPES3.has(node.type) && node.parent?.type === "call";
|
|
2166
2564
|
}
|
|
2167
2565
|
function isPathStatement(node, parent) {
|
|
2168
2566
|
if (node.type === "elsif") {
|
|
@@ -2190,7 +2588,7 @@ function siteOf(node) {
|
|
|
2190
2588
|
current = parent;
|
|
2191
2589
|
}
|
|
2192
2590
|
}
|
|
2193
|
-
function
|
|
2591
|
+
function parametersOf2(parameters) {
|
|
2194
2592
|
if (parameters === null) {
|
|
2195
2593
|
return [];
|
|
2196
2594
|
}
|
|
@@ -2212,19 +2610,19 @@ function functionOf(node) {
|
|
|
2212
2610
|
return null;
|
|
2213
2611
|
}
|
|
2214
2612
|
return {
|
|
2215
|
-
parameters:
|
|
2613
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2216
2614
|
body: bodyOf(block)
|
|
2217
2615
|
};
|
|
2218
2616
|
}
|
|
2219
|
-
if (
|
|
2617
|
+
if (BLOCK_TYPES3.has(node.type)) {
|
|
2220
2618
|
return {
|
|
2221
|
-
parameters:
|
|
2619
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2222
2620
|
body: bodyOf(node)
|
|
2223
2621
|
};
|
|
2224
2622
|
}
|
|
2225
2623
|
if (METHOD_TYPES2.has(node.type)) {
|
|
2226
2624
|
const body = field(node, "body");
|
|
2227
|
-
const parameters =
|
|
2625
|
+
const parameters = parametersOf2(field(node, "parameters"));
|
|
2228
2626
|
if (body === null) {
|
|
2229
2627
|
return { parameters, body: [] };
|
|
2230
2628
|
}
|
|
@@ -2585,11 +2983,18 @@ function bindEvaluator(db, nodes) {
|
|
|
2585
2983
|
)
|
|
2586
2984
|
);
|
|
2587
2985
|
}
|
|
2588
|
-
function evaluatedValue(node, db) {
|
|
2589
|
-
|
|
2986
|
+
function evaluatedValue(node, db, bindings) {
|
|
2987
|
+
const evaluator = evaluatorFor(node, db);
|
|
2988
|
+
if (bindings === void 0) {
|
|
2989
|
+
return force(evaluator.evaluate(node));
|
|
2990
|
+
}
|
|
2991
|
+
const supplied = new Map(
|
|
2992
|
+
[...bindings].map(([name, value]) => [name, text(value)])
|
|
2993
|
+
);
|
|
2994
|
+
return force(evaluator.evaluate(node, { bindings: supplied }));
|
|
2590
2995
|
}
|
|
2591
|
-
function stringValueOf(node, db) {
|
|
2592
|
-
return literalOf2(evaluatedValue(node, db));
|
|
2996
|
+
function stringValueOf(node, db, bindings) {
|
|
2997
|
+
return literalOf2(evaluatedValue(node, db, bindings));
|
|
2593
2998
|
}
|
|
2594
2999
|
function methodDefinitionsIn(file, root) {
|
|
2595
3000
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -3250,8 +3655,7 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3250
3655
|
skipNames.set(form.skippedBy, form.name);
|
|
3251
3656
|
}
|
|
3252
3657
|
}
|
|
3253
|
-
|
|
3254
|
-
const skips = [];
|
|
3658
|
+
let declared = [];
|
|
3255
3659
|
for (const { statement } of inheritedStatements(ancestry)) {
|
|
3256
3660
|
const called = calledName(statement);
|
|
3257
3661
|
if (called === null) {
|
|
@@ -3259,16 +3663,21 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3259
3663
|
}
|
|
3260
3664
|
const form = byName.get(called);
|
|
3261
3665
|
if (form !== void 0) {
|
|
3262
|
-
|
|
3666
|
+
for (const declaration of declarationsOf(statement, form)) {
|
|
3667
|
+
declared = [
|
|
3668
|
+
...declared.filter((earlier) => !sameFilter(earlier, declaration)),
|
|
3669
|
+
declaration
|
|
3670
|
+
];
|
|
3671
|
+
}
|
|
3263
3672
|
continue;
|
|
3264
3673
|
}
|
|
3265
3674
|
const skipped = skipNames.get(called);
|
|
3266
3675
|
if (skipped !== void 0) {
|
|
3267
|
-
|
|
3676
|
+
declared = applySkips(declared, skipsOf(statement, skipped));
|
|
3268
3677
|
}
|
|
3269
3678
|
}
|
|
3270
3679
|
const resolved = [];
|
|
3271
|
-
for (const declaration of
|
|
3680
|
+
for (const declaration of declared) {
|
|
3272
3681
|
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
3273
3682
|
if (found.type !== "found") {
|
|
3274
3683
|
continue;
|
|
@@ -3381,6 +3790,9 @@ function skipsOf(statement, filterName) {
|
|
|
3381
3790
|
const actions = namesUnder(args.keyword.only);
|
|
3382
3791
|
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
3383
3792
|
}
|
|
3793
|
+
function sameFilter(one, other) {
|
|
3794
|
+
return one.filter.name === other.filter.name && one.methodName === other.methodName;
|
|
3795
|
+
}
|
|
3384
3796
|
function applySkips(declared, skips) {
|
|
3385
3797
|
const left = [];
|
|
3386
3798
|
for (const declaration of declared) {
|
|
@@ -3423,13 +3835,8 @@ function namesUnder(node) {
|
|
|
3423
3835
|
}
|
|
3424
3836
|
|
|
3425
3837
|
// src/storage.ts
|
|
3426
|
-
import { deriveOnDemand as deriveOnDemand2, evaluate as evaluate2 } from "@suss/datalog";
|
|
3427
3838
|
import { storageBinding } from "@suss/ir-core";
|
|
3428
|
-
import {
|
|
3429
|
-
ANSWER_RELATIONS as ANSWER_RELATIONS2,
|
|
3430
|
-
RESOLUTION_QUESTIONS as RESOLUTION_QUESTIONS2,
|
|
3431
|
-
RESOLUTION_RULES as RESOLUTION_RULES2
|
|
3432
|
-
} from "@suss/resolution";
|
|
3839
|
+
import { askResolution as askResolution2 } from "@suss/resolution";
|
|
3433
3840
|
function children3(node) {
|
|
3434
3841
|
return node.namedChildren.filter((child) => child !== null);
|
|
3435
3842
|
}
|
|
@@ -3455,13 +3862,8 @@ function isConstant(node) {
|
|
|
3455
3862
|
function constantName(constant2) {
|
|
3456
3863
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3457
3864
|
}
|
|
3458
|
-
var ANCESTRY_PROGRAM = deriveOnDemand2(
|
|
3459
|
-
[...RESOLUTION_RULES2, ...RESOLUTION_QUESTIONS2],
|
|
3460
|
-
ANSWER_RELATIONS2
|
|
3461
|
-
);
|
|
3462
3865
|
function reachesBase(facts, classKey, bases) {
|
|
3463
|
-
facts
|
|
3464
|
-
evaluate2(facts, ANCESTRY_PROGRAM.rules);
|
|
3866
|
+
askResolution2(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
3465
3867
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3466
3868
|
}
|
|
3467
3869
|
function classBehind(facts, file, constant2) {
|
|
@@ -3508,7 +3910,7 @@ function storageEffect(call, model, pattern, kind, selector) {
|
|
|
3508
3910
|
binding: storageBinding({
|
|
3509
3911
|
recognition: "ruby-storage",
|
|
3510
3912
|
storageSystem: pattern.storageSystem,
|
|
3511
|
-
scope:
|
|
3913
|
+
scope: "default",
|
|
3512
3914
|
container: constantName(model)
|
|
3513
3915
|
}),
|
|
3514
3916
|
callee: call.text,
|
|
@@ -3676,6 +4078,7 @@ function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
|
|
|
3676
4078
|
lookup: {
|
|
3677
4079
|
root: pattern.root,
|
|
3678
4080
|
pathConvention: pattern.pathConvention,
|
|
4081
|
+
acronyms: pattern.acronyms ?? [],
|
|
3679
4082
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3680
4083
|
parsedFile: (absPath) => cache.get(absPath),
|
|
3681
4084
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
@@ -3790,12 +4193,16 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3790
4193
|
const lookup = {
|
|
3791
4194
|
root: pattern.root,
|
|
3792
4195
|
pathConvention: pattern.pathConvention,
|
|
4196
|
+
acronyms: pattern.acronyms ?? [],
|
|
3793
4197
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3794
4198
|
parsedFile: (absPath) => options.cache.get(absPath),
|
|
3795
4199
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
3796
4200
|
};
|
|
3797
4201
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, lookup);
|
|
3798
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName,
|
|
4202
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, [
|
|
4203
|
+
...pattern.baseClassNames,
|
|
4204
|
+
...pattern.ancestryRootClassNames
|
|
4205
|
+
])) {
|
|
3799
4206
|
return [];
|
|
3800
4207
|
}
|
|
3801
4208
|
const filters = controllerFilters(pattern, ancestry);
|
|
@@ -3815,43 +4222,73 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3815
4222
|
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3816
4223
|
});
|
|
3817
4224
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
4225
|
+
const emitAction = (actionName, method, block) => {
|
|
4226
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
4227
|
+
(filter) => filterReference(
|
|
4228
|
+
filter,
|
|
4229
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
4230
|
+
)
|
|
4231
|
+
);
|
|
4232
|
+
const raw = buildControllerActionUnit(
|
|
4233
|
+
pack,
|
|
4234
|
+
pattern,
|
|
4235
|
+
info.qualifiedName,
|
|
4236
|
+
actionName,
|
|
4237
|
+
method,
|
|
4238
|
+
{
|
|
4239
|
+
display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
|
|
4240
|
+
absolute: block.file
|
|
4241
|
+
},
|
|
4242
|
+
options,
|
|
4243
|
+
around
|
|
4244
|
+
);
|
|
4245
|
+
units.push(raw);
|
|
4246
|
+
options.onReachSeed?.(raw, {
|
|
4247
|
+
file: block.file,
|
|
4248
|
+
node: method,
|
|
4249
|
+
enclosingQualifiedName: block.info.qualifiedName
|
|
4250
|
+
});
|
|
4251
|
+
};
|
|
4252
|
+
const own = /* @__PURE__ */ new Set();
|
|
4253
|
+
for (const [actionName, method, block] of publicInstanceMethods(ownBlocks)) {
|
|
4254
|
+
own.add(actionName);
|
|
4255
|
+
emitAction(actionName, method, block);
|
|
4256
|
+
}
|
|
4257
|
+
const seen = new Set(own);
|
|
4258
|
+
for (const entry of ancestry) {
|
|
4259
|
+
if (entry.type !== "bodies" || entry.name === info.qualifiedName) {
|
|
3820
4260
|
continue;
|
|
3821
4261
|
}
|
|
3822
|
-
const
|
|
3823
|
-
|
|
3824
|
-
block.info.bodyNode
|
|
4262
|
+
for (const [actionName, method, block] of publicInstanceMethods(
|
|
4263
|
+
entry.blocks
|
|
3825
4264
|
)) {
|
|
3826
|
-
if (
|
|
4265
|
+
if (seen.has(actionName)) {
|
|
3827
4266
|
continue;
|
|
3828
4267
|
}
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
);
|
|
3835
|
-
const raw = buildControllerActionUnit(
|
|
3836
|
-
pack,
|
|
3837
|
-
pattern,
|
|
3838
|
-
info.qualifiedName,
|
|
3839
|
-
actionName,
|
|
3840
|
-
method,
|
|
3841
|
-
{ display: options.filePath, absolute: block.file },
|
|
3842
|
-
options,
|
|
3843
|
-
around
|
|
3844
|
-
);
|
|
3845
|
-
units.push(raw);
|
|
3846
|
-
options.onReachSeed?.(raw, {
|
|
3847
|
-
file: block.file,
|
|
3848
|
-
node: method,
|
|
3849
|
-
enclosingQualifiedName: info.qualifiedName
|
|
3850
|
-
});
|
|
4268
|
+
seen.add(actionName);
|
|
4269
|
+
if (pattern.routeFor(info.qualifiedName, actionName) === null) {
|
|
4270
|
+
continue;
|
|
4271
|
+
}
|
|
4272
|
+
emitAction(actionName, method, block);
|
|
3851
4273
|
}
|
|
3852
4274
|
}
|
|
3853
4275
|
return units;
|
|
3854
4276
|
}
|
|
4277
|
+
function publicInstanceMethods(blocks) {
|
|
4278
|
+
const found = [];
|
|
4279
|
+
for (const block of blocks) {
|
|
4280
|
+
if (block.info.bodyNode === null) {
|
|
4281
|
+
continue;
|
|
4282
|
+
}
|
|
4283
|
+
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
4284
|
+
for (const [name, method] of instanceMethodsByName(block.info.bodyNode)) {
|
|
4285
|
+
if ((visibility.get(name) ?? "public") === "public") {
|
|
4286
|
+
found.push([name, method, block]);
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
return found;
|
|
4291
|
+
}
|
|
3855
4292
|
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, file, bodyRead, wrappers = []) {
|
|
3856
4293
|
const range = rangeOf(method);
|
|
3857
4294
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
@@ -4364,7 +4801,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4364
4801
|
if (written !== null) {
|
|
4365
4802
|
definitions.push({
|
|
4366
4803
|
qualifiedName: qualify(nesting, written),
|
|
4367
|
-
key: nodeId(filePath, node)
|
|
4804
|
+
key: nodeId(filePath, node),
|
|
4805
|
+
kind: "declaration"
|
|
4368
4806
|
});
|
|
4369
4807
|
const inside = [...nesting, ...written.split("::")];
|
|
4370
4808
|
for (const child of children4(node)) {
|
|
@@ -4379,7 +4817,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4379
4817
|
if (left !== null && left.type === "constant" && right !== null) {
|
|
4380
4818
|
definitions.push({
|
|
4381
4819
|
qualifiedName: qualify(nesting, left.text),
|
|
4382
|
-
key: nodeId(filePath, right)
|
|
4820
|
+
key: nodeId(filePath, right),
|
|
4821
|
+
kind: "assignment"
|
|
4383
4822
|
});
|
|
4384
4823
|
}
|
|
4385
4824
|
}
|
|
@@ -4406,14 +4845,18 @@ function emitConstantBindings(db, perFile) {
|
|
|
4406
4845
|
for (const file of files) {
|
|
4407
4846
|
for (const definition of file.definitions) {
|
|
4408
4847
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
4409
|
-
found.push(definition
|
|
4848
|
+
found.push(definition);
|
|
4410
4849
|
byName.set(definition.qualifiedName, found);
|
|
4411
4850
|
fileOfDefinition.set(definition.key, file.filePath);
|
|
4412
4851
|
}
|
|
4413
4852
|
}
|
|
4414
4853
|
const settles = (candidate) => {
|
|
4415
|
-
const found = byName.get(candidate);
|
|
4416
|
-
|
|
4854
|
+
const found = byName.get(candidate) ?? [];
|
|
4855
|
+
const declared = found.filter((one) => one.kind === "declaration");
|
|
4856
|
+
if (declared.length > 0) {
|
|
4857
|
+
return declared[0].key;
|
|
4858
|
+
}
|
|
4859
|
+
return found.length === 1 ? found[0].key : null;
|
|
4417
4860
|
};
|
|
4418
4861
|
for (const file of files) {
|
|
4419
4862
|
for (const reference of file.references) {
|
|
@@ -4440,7 +4883,7 @@ function emitConstantBindings(db, perFile) {
|
|
|
4440
4883
|
import fs4 from "fs";
|
|
4441
4884
|
import path5 from "path";
|
|
4442
4885
|
import { Database as Database3 } from "@suss/datalog";
|
|
4443
|
-
import { explainResolvedKey, RESOLUTION_RULES
|
|
4886
|
+
import { explainResolvedKey, RESOLUTION_RULES } from "@suss/resolution";
|
|
4444
4887
|
|
|
4445
4888
|
// src/parser.ts
|
|
4446
4889
|
import fs2 from "fs";
|
|
@@ -4588,34 +5031,118 @@ import {
|
|
|
4588
5031
|
unfollowedCallGap,
|
|
4589
5032
|
worthRecording
|
|
4590
5033
|
} from "@suss/behavioral-ir";
|
|
4591
|
-
import { Database, evaluate
|
|
5034
|
+
import { Database, evaluate, lit as lit2, rule as rule2, variable as v2 } from "@suss/datalog";
|
|
4592
5035
|
import { assembleSummary } from "@suss/extractor";
|
|
4593
5036
|
|
|
4594
5037
|
// src/reach/resolveCallee.ts
|
|
5038
|
+
import { calleeOutcomeOf, calleeOutcomes } from "@suss/resolution";
|
|
4595
5039
|
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
5040
|
+
var INVOKES_RECEIVER = "call";
|
|
5041
|
+
var STOP_FOR = {
|
|
5042
|
+
severalSources: "multipleSources",
|
|
5043
|
+
outsideRun: "outsideRun",
|
|
5044
|
+
unsettled: "unsettledValue",
|
|
5045
|
+
undeclared: "noDeclaration"
|
|
5046
|
+
};
|
|
4596
5047
|
var stop = (reason) => ({
|
|
4597
5048
|
kind: "stopped",
|
|
4598
5049
|
reason
|
|
4599
5050
|
});
|
|
5051
|
+
var NO_DECLARATION = stop("noDeclaration");
|
|
4600
5052
|
function followed(target) {
|
|
4601
5053
|
return { kind: "followed", target };
|
|
4602
5054
|
}
|
|
4603
|
-
|
|
5055
|
+
function calleeSpellings(calls, ctx) {
|
|
5056
|
+
const spellingOf = /* @__PURE__ */ new Map();
|
|
5057
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5058
|
+
for (const { call, site } of calls) {
|
|
5059
|
+
const spelling = spellingFor(call, site);
|
|
5060
|
+
spellingOf.set(call.id, spelling);
|
|
5061
|
+
if (spelling.kind === "receiver") {
|
|
5062
|
+
keys.add(spelling.key);
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
return {
|
|
5066
|
+
spellingOf,
|
|
5067
|
+
outcomes: calleeOutcomes(ctx.facts, [...keys], RUBY_PROGRAM)
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
async function resolveCallee(call, site, ctx, read) {
|
|
5071
|
+
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
5072
|
+
if (spelling.kind === "stopped") {
|
|
5073
|
+
return spelling;
|
|
5074
|
+
}
|
|
5075
|
+
if (spelling.kind === "implicitSelf") {
|
|
5076
|
+
return resolveImplicitSelf(spelling.name, site, ctx);
|
|
5077
|
+
}
|
|
5078
|
+
const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key, RUBY_PROGRAM);
|
|
5079
|
+
return asCallee(spelling, outcome, site, ctx);
|
|
5080
|
+
}
|
|
5081
|
+
function spellingFor(call, site) {
|
|
4604
5082
|
const methodName = calleeMethodName(call);
|
|
4605
5083
|
if (methodName === void 0) {
|
|
4606
|
-
return
|
|
5084
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
4607
5085
|
}
|
|
4608
5086
|
if (DYNAMIC_SEND_NAMES.has(methodName)) {
|
|
4609
|
-
return
|
|
5087
|
+
return { kind: "stopped", reason: "unsettledValue" };
|
|
4610
5088
|
}
|
|
4611
5089
|
const receiver = field(call, "receiver");
|
|
4612
5090
|
if (receiver === null || receiver.type === "self") {
|
|
4613
|
-
return
|
|
5091
|
+
return { kind: "implicitSelf", name: methodName };
|
|
5092
|
+
}
|
|
5093
|
+
return {
|
|
5094
|
+
kind: "receiver",
|
|
5095
|
+
key: readKey(site.file, receiver, site.method),
|
|
5096
|
+
method: methodName,
|
|
5097
|
+
onClassItself: receiver.type === "constant" || receiver.type === "scope_resolution"
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
async function asCallee(spelling, outcome, site, ctx) {
|
|
5101
|
+
if (outcome.kind === "severalSources") {
|
|
5102
|
+
return stop("multipleSources");
|
|
5103
|
+
}
|
|
5104
|
+
if (outcome.kind === "callerSupplied") {
|
|
5105
|
+
return fromCaller(spelling, outcome.key, site);
|
|
5106
|
+
}
|
|
5107
|
+
const objects = objectsBehind(ctx.facts, spelling.key);
|
|
5108
|
+
if (objects.length > 1) {
|
|
5109
|
+
return stop("multipleSources");
|
|
5110
|
+
}
|
|
5111
|
+
const objectKey = objects[0] ?? (outcome.kind === "object" ? outcome.key : void 0);
|
|
5112
|
+
if (objectKey !== void 0) {
|
|
5113
|
+
return methodOnObject(spelling, objectKey, ctx);
|
|
5114
|
+
}
|
|
5115
|
+
if (outcome.kind === "function") {
|
|
5116
|
+
return spelling.method === INVOKES_RECEIVER ? functionCallee(outcome.key, ctx) : NO_DECLARATION;
|
|
5117
|
+
}
|
|
5118
|
+
return stop(STOP_FOR[outcome.kind] ?? "noDeclaration");
|
|
5119
|
+
}
|
|
5120
|
+
function objectsBehind(facts, key) {
|
|
5121
|
+
return [
|
|
5122
|
+
...new Set(
|
|
5123
|
+
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
5124
|
+
)
|
|
5125
|
+
];
|
|
5126
|
+
}
|
|
5127
|
+
async function methodOnObject(spelling, objectKey, ctx) {
|
|
5128
|
+
const qualifiedName = ctx.classNames.get(objectKey);
|
|
5129
|
+
if (qualifiedName === void 0) {
|
|
5130
|
+
return NO_DECLARATION;
|
|
4614
5131
|
}
|
|
4615
|
-
if (
|
|
4616
|
-
return
|
|
5132
|
+
if (!spelling.onClassItself) {
|
|
5133
|
+
return methodOnAncestryOf(qualifiedName, spelling.method, ctx);
|
|
4617
5134
|
}
|
|
4618
|
-
return
|
|
5135
|
+
return spelling.method === "new" ? methodOnAncestryOf(qualifiedName, "initialize", ctx) : singletonMethodOn(qualifiedName, spelling.method, ctx);
|
|
5136
|
+
}
|
|
5137
|
+
function fromCaller(spelling, parameterKey, site) {
|
|
5138
|
+
if (!parameterKey.startsWith(`${site.owner}#`) || spelling.method !== INVOKES_RECEIVER) {
|
|
5139
|
+
return stop("unsettledValue");
|
|
5140
|
+
}
|
|
5141
|
+
return stop("callerSupplied");
|
|
5142
|
+
}
|
|
5143
|
+
function functionCallee(key, ctx) {
|
|
5144
|
+
const target = ctx.definitions.get(key);
|
|
5145
|
+
return target === void 0 ? NO_DECLARATION : followed(target);
|
|
4619
5146
|
}
|
|
4620
5147
|
async function resolveMethodReference(name, site, ctx) {
|
|
4621
5148
|
const resolved = await resolveImplicitSelf(name, site, ctx);
|
|
@@ -4640,58 +5167,13 @@ async function resolveImplicitSelf(methodName, site, ctx) {
|
|
|
4640
5167
|
function resolveTopLevelName(methodName, ctx) {
|
|
4641
5168
|
const candidates = ctx.topLevelMethods.get(methodName);
|
|
4642
5169
|
if (candidates === void 0 || candidates.length === 0) {
|
|
4643
|
-
return
|
|
5170
|
+
return NO_DECLARATION;
|
|
4644
5171
|
}
|
|
4645
5172
|
if (candidates.length > 1) {
|
|
4646
5173
|
return stop("multipleSources");
|
|
4647
5174
|
}
|
|
4648
5175
|
return followed(candidates[0]);
|
|
4649
5176
|
}
|
|
4650
|
-
async function resolveOnReceiver(receiver, methodName, site, ctx) {
|
|
4651
|
-
if (receiver.type === "call") {
|
|
4652
|
-
const innerName = calleeMethodName(receiver);
|
|
4653
|
-
const innerReceiver = field(receiver, "receiver");
|
|
4654
|
-
if (innerName === "new" && innerReceiver !== null) {
|
|
4655
|
-
const classRef = classRefOf(innerReceiver, site, ctx);
|
|
4656
|
-
return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
|
|
4657
|
-
}
|
|
4658
|
-
const root = rootConstantOf(receiver);
|
|
4659
|
-
if (root === null) {
|
|
4660
|
-
return stop("unsettledValue");
|
|
4661
|
-
}
|
|
4662
|
-
return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
|
|
4663
|
-
}
|
|
4664
|
-
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
4665
|
-
const classRef = classRefOf(receiver, site, ctx);
|
|
4666
|
-
return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
|
|
4667
|
-
}
|
|
4668
|
-
return stop("unsettledValue");
|
|
4669
|
-
}
|
|
4670
|
-
function rootConstantOf(node) {
|
|
4671
|
-
if (node.type === "constant" || node.type === "scope_resolution") {
|
|
4672
|
-
return node;
|
|
4673
|
-
}
|
|
4674
|
-
if (node.type !== "call") {
|
|
4675
|
-
return null;
|
|
4676
|
-
}
|
|
4677
|
-
const receiver = field(node, "receiver");
|
|
4678
|
-
return receiver === null ? null : rootConstantOf(receiver);
|
|
4679
|
-
}
|
|
4680
|
-
function classRefOf(node, site, ctx) {
|
|
4681
|
-
if (node.type === "constant") {
|
|
4682
|
-
const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
|
|
4683
|
-
if (shadow !== null) {
|
|
4684
|
-
return shadow;
|
|
4685
|
-
}
|
|
4686
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4687
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4688
|
-
}
|
|
4689
|
-
if (node.type === "scope_resolution") {
|
|
4690
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4691
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4692
|
-
}
|
|
4693
|
-
return null;
|
|
4694
|
-
}
|
|
4695
5177
|
async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
4696
5178
|
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
4697
5179
|
if (ownBlocks === null) {
|
|
@@ -4707,7 +5189,7 @@ async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
|
4707
5189
|
found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
|
|
4708
5190
|
);
|
|
4709
5191
|
}
|
|
4710
|
-
return
|
|
5192
|
+
return NO_DECLARATION;
|
|
4711
5193
|
}
|
|
4712
5194
|
function reachedMethod(node, block, name) {
|
|
4713
5195
|
return {
|
|
@@ -4732,7 +5214,7 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
|
4732
5214
|
return followed(reachedMethod(found, block, methodName));
|
|
4733
5215
|
}
|
|
4734
5216
|
}
|
|
4735
|
-
return
|
|
5217
|
+
return NO_DECLARATION;
|
|
4736
5218
|
}
|
|
4737
5219
|
|
|
4738
5220
|
// src/reach/closure.ts
|
|
@@ -4745,7 +5227,7 @@ var REACHABLE_RULES = [
|
|
|
4745
5227
|
)
|
|
4746
5228
|
];
|
|
4747
5229
|
async function reachedFunctions(seeds, options) {
|
|
4748
|
-
const ctx = buildReachContext(options.files);
|
|
5230
|
+
const ctx = buildReachContext(options.files, options.facts);
|
|
4749
5231
|
const db = new Database();
|
|
4750
5232
|
const functionByKey = /* @__PURE__ */ new Map();
|
|
4751
5233
|
const seedKeys = /* @__PURE__ */ new Set();
|
|
@@ -4767,18 +5249,26 @@ async function reachedFunctions(seeds, options) {
|
|
|
4767
5249
|
db.add("entry", [seed.key]);
|
|
4768
5250
|
}
|
|
4769
5251
|
for (; ; ) {
|
|
4770
|
-
|
|
5252
|
+
evaluate(db, REACHABLE_RULES);
|
|
4771
5253
|
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
4772
5254
|
if (frontier.length === 0) {
|
|
4773
5255
|
break;
|
|
4774
5256
|
}
|
|
4775
|
-
|
|
5257
|
+
const bodies = frontier.flatMap((key) => {
|
|
4776
5258
|
scanned.add(key);
|
|
4777
5259
|
const source = functionByKey.get(key);
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
5260
|
+
return source === void 0 ? [] : [{ key, source, ...bodyOf2(source, options) }];
|
|
5261
|
+
});
|
|
5262
|
+
const spellings = calleeSpellings(
|
|
5263
|
+
bodies.flatMap((body) => body.written),
|
|
5264
|
+
ctx
|
|
5265
|
+
);
|
|
5266
|
+
for (const { key, source, calls, site } of bodies) {
|
|
5267
|
+
const scan = await scanBody(source, ctx, options, {
|
|
5268
|
+
calls,
|
|
5269
|
+
site,
|
|
5270
|
+
spellings
|
|
5271
|
+
});
|
|
4782
5272
|
if (scan.stops.length > 0) {
|
|
4783
5273
|
stopsByKey.set(key, scan.stops);
|
|
4784
5274
|
}
|
|
@@ -4831,11 +5321,22 @@ async function reachedFunctions(seeds, options) {
|
|
|
4831
5321
|
function keyOf(target) {
|
|
4832
5322
|
return nodeId(target.file, target.node);
|
|
4833
5323
|
}
|
|
4834
|
-
|
|
5324
|
+
var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5325
|
+
function buildReachContext(files, facts) {
|
|
4835
5326
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
4836
5327
|
const classes = [];
|
|
5328
|
+
const classNames = /* @__PURE__ */ new Map();
|
|
5329
|
+
const classOfMethod = /* @__PURE__ */ new Map();
|
|
4837
5330
|
for (const { file, root } of files) {
|
|
4838
|
-
walkDefinitions(root, (info) =>
|
|
5331
|
+
walkDefinitions(root, (info) => {
|
|
5332
|
+
classes.push({ file, info });
|
|
5333
|
+
classNames.set(nodeId(file, info.node), info.qualifiedName);
|
|
5334
|
+
for (const statement of namedChildren(info.bodyNode)) {
|
|
5335
|
+
if (METHOD_TYPES4.has(statement.type)) {
|
|
5336
|
+
classOfMethod.set(nodeId(file, statement), info.qualifiedName);
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
});
|
|
4839
5340
|
}
|
|
4840
5341
|
const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
|
|
4841
5342
|
for (const { file, info } of classes) {
|
|
@@ -4843,22 +5344,33 @@ function buildReachContext(files) {
|
|
|
4843
5344
|
list.push({ info, knownClasses, file });
|
|
4844
5345
|
blocksByQualifiedName.set(info.qualifiedName, list);
|
|
4845
5346
|
}
|
|
4846
|
-
const
|
|
5347
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4847
5348
|
for (const { file, root } of files) {
|
|
4848
|
-
for (const method of
|
|
5349
|
+
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
4849
5350
|
const name = field(method, "name")?.text;
|
|
4850
5351
|
if (name === void 0) {
|
|
4851
5352
|
continue;
|
|
4852
5353
|
}
|
|
4853
|
-
const
|
|
4854
|
-
|
|
5354
|
+
const owner = classOfMethod.get(key) ?? null;
|
|
5355
|
+
definitions.set(key, {
|
|
4855
5356
|
file,
|
|
4856
5357
|
node: method,
|
|
4857
5358
|
name,
|
|
4858
|
-
exportPath: [name],
|
|
4859
|
-
enclosingQualifiedName:
|
|
5359
|
+
exportPath: owner === null ? [name] : [owner, name],
|
|
5360
|
+
enclosingQualifiedName: owner
|
|
4860
5361
|
});
|
|
4861
|
-
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
const topLevelMethods = /* @__PURE__ */ new Map();
|
|
5365
|
+
for (const { file, root } of files) {
|
|
5366
|
+
for (const method of topLevelMethodNodes(root)) {
|
|
5367
|
+
const found = definitions.get(nodeId(file, method));
|
|
5368
|
+
if (found === void 0) {
|
|
5369
|
+
continue;
|
|
5370
|
+
}
|
|
5371
|
+
const list = topLevelMethods.get(found.name) ?? [];
|
|
5372
|
+
list.push(found);
|
|
5373
|
+
topLevelMethods.set(found.name, list);
|
|
4862
5374
|
}
|
|
4863
5375
|
}
|
|
4864
5376
|
const lookup = {
|
|
@@ -4871,7 +5383,13 @@ function buildReachContext(files) {
|
|
|
4871
5383
|
parsedFile: async () => null,
|
|
4872
5384
|
localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
|
|
4873
5385
|
};
|
|
4874
|
-
return { lookup,
|
|
5386
|
+
return { lookup, topLevelMethods, facts, classNames, definitions };
|
|
5387
|
+
}
|
|
5388
|
+
function namedChildren(node) {
|
|
5389
|
+
if (node === null) {
|
|
5390
|
+
return [];
|
|
5391
|
+
}
|
|
5392
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
4875
5393
|
}
|
|
4876
5394
|
function topLevelMethodNodes(root, found = []) {
|
|
4877
5395
|
for (const child of bodyStatements(root)) {
|
|
@@ -4886,14 +5404,6 @@ function topLevelMethodNodes(root, found = []) {
|
|
|
4886
5404
|
}
|
|
4887
5405
|
return found;
|
|
4888
5406
|
}
|
|
4889
|
-
function nestingOf(qualifiedName) {
|
|
4890
|
-
const parts = qualifiedName.split("::");
|
|
4891
|
-
const out = [];
|
|
4892
|
-
for (let depth = parts.length; depth >= 1; depth--) {
|
|
4893
|
-
out.push(parts.slice(0, depth).join("::"));
|
|
4894
|
-
}
|
|
4895
|
-
return out;
|
|
4896
|
-
}
|
|
4897
5407
|
var EMPTY_SCAN = {
|
|
4898
5408
|
followed: [],
|
|
4899
5409
|
stops: [],
|
|
@@ -4911,21 +5421,23 @@ function methodReferenceSymbol(node) {
|
|
|
4911
5421
|
const argChildren = args === null ? [] : bodyStatements(args);
|
|
4912
5422
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
4913
5423
|
}
|
|
4914
|
-
|
|
5424
|
+
function bodyOf2(source, options) {
|
|
5425
|
+
const site = {
|
|
5426
|
+
file: source.file,
|
|
5427
|
+
method: source.node,
|
|
5428
|
+
owner: keyOf(source),
|
|
5429
|
+
enclosingQualifiedName: source.enclosingQualifiedName
|
|
5430
|
+
};
|
|
5431
|
+
const calls = field(source.node, "body") === null ? [] : withoutChainLinks(bodyCalls(source.node, options.inheritedMethods));
|
|
5432
|
+
return { calls, site, written: calls.map((call) => ({ call, site })) };
|
|
5433
|
+
}
|
|
5434
|
+
async function scanBody(source, ctx, options, read) {
|
|
4915
5435
|
const displayPathOf = options.displayPathOf;
|
|
4916
|
-
|
|
4917
|
-
if (body === null) {
|
|
5436
|
+
if (field(source.node, "body") === null) {
|
|
4918
5437
|
return EMPTY_SCAN;
|
|
4919
5438
|
}
|
|
4920
|
-
const calls =
|
|
4921
|
-
bodyCalls(source.node, options.inheritedMethods)
|
|
4922
|
-
);
|
|
5439
|
+
const { calls, site } = read;
|
|
4923
5440
|
const ownParameters = positionalParameters(source.node).map((p) => p.name);
|
|
4924
|
-
const site = {
|
|
4925
|
-
enclosingQualifiedName: source.enclosingQualifiedName,
|
|
4926
|
-
nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
|
|
4927
|
-
ownParameters
|
|
4928
|
-
};
|
|
4929
5441
|
const followed2 = [];
|
|
4930
5442
|
const stops = [];
|
|
4931
5443
|
const placements = new TargetPlacements();
|
|
@@ -4968,7 +5480,7 @@ async function scanBody(source, ctx, options) {
|
|
|
4968
5480
|
};
|
|
4969
5481
|
for (const call of calls) {
|
|
4970
5482
|
const callee = calleeText(call);
|
|
4971
|
-
const outcome = await resolveCallee(call, site, ctx);
|
|
5483
|
+
const outcome = await resolveCallee(call, site, ctx, read.spellings);
|
|
4972
5484
|
const placed = outcome.kind === "followed" ? {
|
|
4973
5485
|
file: displayPathOf(outcome.target.file),
|
|
4974
5486
|
span: spanOf(outcome.target.node)
|
|
@@ -5106,7 +5618,8 @@ function inheritedMethodsIn(packs) {
|
|
|
5106
5618
|
return found;
|
|
5107
5619
|
}
|
|
5108
5620
|
function alreadyDiscovered(seen, raw) {
|
|
5109
|
-
const
|
|
5621
|
+
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
5622
|
+
const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
|
|
5110
5623
|
if (seen.has(key)) {
|
|
5111
5624
|
return true;
|
|
5112
5625
|
}
|
|
@@ -5266,6 +5779,7 @@ async function extractRubyProject(options) {
|
|
|
5266
5779
|
() => reachedFunctions(seeds, {
|
|
5267
5780
|
files: parsed,
|
|
5268
5781
|
displayPathOf,
|
|
5782
|
+
facts: db,
|
|
5269
5783
|
...storage === void 0 ? {} : { storage },
|
|
5270
5784
|
inheritedMethods
|
|
5271
5785
|
})
|
|
@@ -5362,15 +5876,15 @@ function findRubyFiles(root) {
|
|
|
5362
5876
|
}
|
|
5363
5877
|
|
|
5364
5878
|
// src/facts/why.ts
|
|
5365
|
-
var WITNESS_RULES = [...
|
|
5366
|
-
var
|
|
5879
|
+
var WITNESS_RULES = [...RESOLUTION_RULES, ...RUBY_RULES];
|
|
5880
|
+
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5367
5881
|
function namedChildrenOf(node) {
|
|
5368
5882
|
return node.namedChildren.filter((child) => child !== null);
|
|
5369
5883
|
}
|
|
5370
5884
|
function enclosingMethod2(node) {
|
|
5371
5885
|
let current = node.parent;
|
|
5372
5886
|
while (current !== null) {
|
|
5373
|
-
if (
|
|
5887
|
+
if (METHOD_TYPES5.has(current.type)) {
|
|
5374
5888
|
return current;
|
|
5375
5889
|
}
|
|
5376
5890
|
current = current.parent;
|
|
@@ -5405,8 +5919,8 @@ function displayNameOf(node) {
|
|
|
5405
5919
|
if (node.type === "class" || node.type === "module") {
|
|
5406
5920
|
return field(node, "name")?.text ?? node.text;
|
|
5407
5921
|
}
|
|
5408
|
-
const
|
|
5409
|
-
return
|
|
5922
|
+
const text2 = node.text.split("\n")[0].trim();
|
|
5923
|
+
return text2.length > 40 ? `${text2.slice(0, 40)}...` : text2;
|
|
5410
5924
|
}
|
|
5411
5925
|
function width(node) {
|
|
5412
5926
|
return node.endIndex - node.startIndex;
|
|
@@ -5433,14 +5947,14 @@ var RubyWhySession = class {
|
|
|
5433
5947
|
* The smallest expression on `line` of `file` whose text is exactly
|
|
5434
5948
|
* `text`, or null.
|
|
5435
5949
|
*/
|
|
5436
|
-
findExpression(file, line,
|
|
5950
|
+
findExpression(file, line, text2) {
|
|
5437
5951
|
const root = this.rootOf(file);
|
|
5438
5952
|
if (root === null) {
|
|
5439
5953
|
return null;
|
|
5440
5954
|
}
|
|
5441
5955
|
let found = null;
|
|
5442
5956
|
const visit = (node) => {
|
|
5443
|
-
if (node.startPosition.row + 1 === line && node.text ===
|
|
5957
|
+
if (node.startPosition.row + 1 === line && node.text === text2) {
|
|
5444
5958
|
if (found === null || width(node) <= width(found)) {
|
|
5445
5959
|
found = node;
|
|
5446
5960
|
}
|
|
@@ -5610,6 +6124,7 @@ export {
|
|
|
5610
6124
|
inheritedStatements,
|
|
5611
6125
|
methodDefinitionsIn,
|
|
5612
6126
|
methodInAncestry,
|
|
6127
|
+
nestedStatements,
|
|
5613
6128
|
nodeId,
|
|
5614
6129
|
parseRuby,
|
|
5615
6130
|
parseRubySync,
|