@suss/adapter-ruby 0.27.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 +603 -197
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +593 -199
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -1280,18 +1280,10 @@ import { nodeOfKey } from "@suss/resolution";
|
|
|
1280
1280
|
import { Evaluator, force, literalOf as literalOf2, text } from "@suss/values";
|
|
1281
1281
|
|
|
1282
1282
|
// src/facts/resolve.ts
|
|
1283
|
+
import { constant, lit, rule, variable as v } from "@suss/datalog";
|
|
1283
1284
|
import {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
evaluate,
|
|
1287
|
-
lit,
|
|
1288
|
-
rule,
|
|
1289
|
-
variable as v
|
|
1290
|
-
} from "@suss/datalog";
|
|
1291
|
-
import {
|
|
1292
|
-
ANSWER_RELATIONS,
|
|
1293
|
-
RESOLUTION_QUESTIONS,
|
|
1294
|
-
RESOLUTION_RULES,
|
|
1285
|
+
askResolution,
|
|
1286
|
+
resolutionProgram,
|
|
1295
1287
|
writtenValueOf as sharedWrittenValueOf,
|
|
1296
1288
|
VALUE_STEP
|
|
1297
1289
|
} from "@suss/resolution";
|
|
@@ -1309,18 +1301,9 @@ var RUBY_RULES = [
|
|
|
1309
1301
|
]
|
|
1310
1302
|
)
|
|
1311
1303
|
];
|
|
1312
|
-
var
|
|
1313
|
-
[...RESOLUTION_RULES, ...RUBY_RULES, ...RESOLUTION_QUESTIONS],
|
|
1314
|
-
ANSWER_RELATIONS
|
|
1315
|
-
);
|
|
1304
|
+
var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
|
|
1316
1305
|
function resolveValues(db, keys) {
|
|
1317
|
-
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
for (const key of keys) {
|
|
1321
|
-
db.add("wanted", [key]);
|
|
1322
|
-
}
|
|
1323
|
-
evaluate(db, RESOLUTION_PROGRAM.rules);
|
|
1306
|
+
askResolution(db, keys, "wanted", RUBY_PROGRAM);
|
|
1324
1307
|
}
|
|
1325
1308
|
function resolvedFunctions(db, key) {
|
|
1326
1309
|
return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
|
|
@@ -1331,27 +1314,262 @@ function writtenValueOf(db, key) {
|
|
|
1331
1314
|
}
|
|
1332
1315
|
|
|
1333
1316
|
// src/facts/values.ts
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
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
|
+
]);
|
|
1340
1336
|
function paramNameOf(param) {
|
|
1341
1337
|
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
1342
1338
|
}
|
|
1343
|
-
function
|
|
1344
|
-
const params = field(
|
|
1345
|
-
|
|
1346
|
-
|
|
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)) {
|
|
1346
|
+
const name = paramNameOf(param);
|
|
1347
|
+
if (name !== null) {
|
|
1348
|
+
names.add(name.text);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
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;
|
|
1458
|
+
}
|
|
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)) {
|
|
1347
1488
|
const name = paramNameOf(param);
|
|
1348
1489
|
if (name !== null) {
|
|
1349
|
-
|
|
1490
|
+
found.push(writeOf(name, name, param, true));
|
|
1350
1491
|
}
|
|
1351
1492
|
}
|
|
1352
|
-
return
|
|
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}`;
|
|
1353
1570
|
}
|
|
1354
|
-
function readKey(filePath,
|
|
1571
|
+
function readKey(filePath, written, enclosing) {
|
|
1572
|
+
const node = readThrough(written);
|
|
1355
1573
|
if (node.type !== "identifier" && node.type !== "constant") {
|
|
1356
1574
|
return nodeId(filePath, node);
|
|
1357
1575
|
}
|
|
@@ -1359,10 +1577,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1359
1577
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1360
1578
|
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1361
1579
|
}
|
|
1362
|
-
|
|
1363
|
-
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
1364
|
-
}
|
|
1365
|
-
return nameId(filePath, node.text);
|
|
1580
|
+
return nameKey(filePath, node, enclosing);
|
|
1366
1581
|
}
|
|
1367
1582
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
1368
1583
|
"string",
|
|
@@ -1391,15 +1606,12 @@ function children2(node) {
|
|
|
1391
1606
|
function add(emitter, relation, ...tuple) {
|
|
1392
1607
|
emitter.db.add(relation, tuple);
|
|
1393
1608
|
}
|
|
1394
|
-
function valueKey(emitter,
|
|
1609
|
+
function valueKey(emitter, written) {
|
|
1610
|
+
const value = readThrough(written);
|
|
1395
1611
|
if (value.type !== "identifier" && value.type !== "constant") {
|
|
1396
1612
|
return nodeId(emitter.filePath, value);
|
|
1397
1613
|
}
|
|
1398
|
-
|
|
1399
|
-
if (enclosing !== null && enclosing.params.has(value.text)) {
|
|
1400
|
-
return `${enclosing.funcKey}#${value.text}`;
|
|
1401
|
-
}
|
|
1402
|
-
return nameId(emitter.filePath, value.text);
|
|
1614
|
+
return nameKey(emitter.filePath, value, emitter.enclosing);
|
|
1403
1615
|
}
|
|
1404
1616
|
function pairKeyText(key) {
|
|
1405
1617
|
if (key.type === "hash_key_symbol") {
|
|
@@ -1538,8 +1750,72 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1538
1750
|
if (child.type === "nil") {
|
|
1539
1751
|
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
1540
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
|
+
}
|
|
1541
1762
|
});
|
|
1542
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
|
+
}
|
|
1543
1819
|
function implicitReturn(body) {
|
|
1544
1820
|
const statements = children2(body).filter(
|
|
1545
1821
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
@@ -1550,23 +1826,17 @@ function implicitReturn(body) {
|
|
|
1550
1826
|
function emitMethodFacts(emitter, method) {
|
|
1551
1827
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1552
1828
|
add(emitter, "func", funcKey);
|
|
1553
|
-
const params = field(method, "parameters");
|
|
1554
|
-
const declared = /* @__PURE__ */ new Set();
|
|
1555
1829
|
let position = 0;
|
|
1556
|
-
for (const param of
|
|
1830
|
+
for (const param of parametersOf(method)) {
|
|
1557
1831
|
const paramName = paramNameOf(param);
|
|
1558
1832
|
if (paramName !== null) {
|
|
1559
|
-
declared.add(paramName.text);
|
|
1560
1833
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
1561
1834
|
add(emitter, "paramOf", funcKey, String(position), paramKey);
|
|
1562
1835
|
add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
|
|
1563
1836
|
}
|
|
1564
1837
|
position += 1;
|
|
1565
1838
|
}
|
|
1566
|
-
const inside = {
|
|
1567
|
-
...emitter,
|
|
1568
|
-
enclosing: { funcKey, params: declared }
|
|
1569
|
-
};
|
|
1839
|
+
const inside = { ...emitter, enclosing: method };
|
|
1570
1840
|
const body = field(method, "body");
|
|
1571
1841
|
if (body === null) {
|
|
1572
1842
|
return funcKey;
|
|
@@ -1598,31 +1868,78 @@ function emitMethodFacts(emitter, method) {
|
|
|
1598
1868
|
add(inside, "returnsValue", funcKey, valueKey(inside, implicit));
|
|
1599
1869
|
}
|
|
1600
1870
|
emitExpressionFacts(inside, body);
|
|
1871
|
+
emitScopeWrites(inside, method, body);
|
|
1601
1872
|
return funcKey;
|
|
1602
1873
|
}
|
|
1603
|
-
function
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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;
|
|
1608
1884
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
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);
|
|
1611
1904
|
}
|
|
1612
|
-
|
|
1613
|
-
emitter,
|
|
1614
|
-
|
|
1615
|
-
nameId(emitter.filePath, left.text),
|
|
1616
|
-
valueKey(emitter, right)
|
|
1617
|
-
);
|
|
1618
|
-
add(
|
|
1619
|
-
emitter,
|
|
1620
|
-
"exportsAs",
|
|
1621
|
-
emitter.filePath,
|
|
1622
|
-
left.text,
|
|
1623
|
-
valueKey(emitter, right)
|
|
1905
|
+
return valueLeftByWrites(
|
|
1906
|
+
group.writes.map((write) => describeWrite(emitter, write)),
|
|
1907
|
+
group.ordered
|
|
1624
1908
|
);
|
|
1625
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
|
+
}
|
|
1626
1943
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1627
1944
|
function emitClassFacts(emitter, cls) {
|
|
1628
1945
|
const classKey = nodeId(emitter.filePath, cls);
|
|
@@ -1634,6 +1951,12 @@ function emitClassFacts(emitter, cls) {
|
|
|
1634
1951
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1635
1952
|
}
|
|
1636
1953
|
const body = field(cls, "body");
|
|
1954
|
+
const collected = /* @__PURE__ */ new Map();
|
|
1955
|
+
const within = {
|
|
1956
|
+
...emitter,
|
|
1957
|
+
selfKey: classKey,
|
|
1958
|
+
instanceWrites: collected
|
|
1959
|
+
};
|
|
1637
1960
|
for (const statement of body === null ? [] : children2(body)) {
|
|
1638
1961
|
if (statement.type === "assignment") {
|
|
1639
1962
|
const left = field(statement, "left");
|
|
@@ -1657,16 +1980,23 @@ function emitClassFacts(emitter, cls) {
|
|
|
1657
1980
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
1658
1981
|
continue;
|
|
1659
1982
|
}
|
|
1660
|
-
const funcKey = emitMethodFacts(
|
|
1983
|
+
const funcKey = emitMethodFacts(within, statement);
|
|
1661
1984
|
const name = field(statement, "name");
|
|
1662
1985
|
if (name !== null) {
|
|
1663
1986
|
add(emitter, "holdsProperty", classKey, name.text, funcKey);
|
|
1664
1987
|
}
|
|
1665
1988
|
}
|
|
1989
|
+
emitInstanceWrites(within, classKey, collected);
|
|
1666
1990
|
return classKey;
|
|
1667
1991
|
}
|
|
1668
1992
|
function emitValueFacts(db, filePath, root) {
|
|
1669
|
-
const emitter = {
|
|
1993
|
+
const emitter = {
|
|
1994
|
+
db,
|
|
1995
|
+
filePath,
|
|
1996
|
+
enclosing: null,
|
|
1997
|
+
selfKey: null,
|
|
1998
|
+
instanceWrites: null
|
|
1999
|
+
};
|
|
1670
2000
|
const declaresName = (child, key) => {
|
|
1671
2001
|
const name = field(child, "name");
|
|
1672
2002
|
if (name === null) {
|
|
@@ -1684,14 +2014,12 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1684
2014
|
if (METHOD_TYPES.has(child.type)) {
|
|
1685
2015
|
declaresName(child, emitMethodFacts(emitter, child));
|
|
1686
2016
|
}
|
|
1687
|
-
if (child.type === "assignment") {
|
|
1688
|
-
emitAssignment(emitter, child);
|
|
1689
|
-
}
|
|
1690
2017
|
walk(child);
|
|
1691
2018
|
}
|
|
1692
2019
|
};
|
|
1693
2020
|
walk(root);
|
|
1694
2021
|
emitExpressionFacts(emitter, root);
|
|
2022
|
+
emitScopeWrites(emitter, null, root);
|
|
1695
2023
|
}
|
|
1696
2024
|
|
|
1697
2025
|
// src/values/lowering.ts
|
|
@@ -1707,7 +2035,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1707
2035
|
"module",
|
|
1708
2036
|
"singleton_class"
|
|
1709
2037
|
]);
|
|
1710
|
-
var
|
|
2038
|
+
var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1711
2039
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1712
2040
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1713
2041
|
"identifier",
|
|
@@ -2213,7 +2541,7 @@ function isTailExpression(node) {
|
|
|
2213
2541
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
2214
2542
|
return true;
|
|
2215
2543
|
}
|
|
2216
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
2544
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES3.has(owner.type)) {
|
|
2217
2545
|
return true;
|
|
2218
2546
|
}
|
|
2219
2547
|
if (owner.type === "when") {
|
|
@@ -2232,7 +2560,7 @@ function isRoot(node) {
|
|
|
2232
2560
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
2233
2561
|
return true;
|
|
2234
2562
|
}
|
|
2235
|
-
return
|
|
2563
|
+
return BLOCK_TYPES3.has(node.type) && node.parent?.type === "call";
|
|
2236
2564
|
}
|
|
2237
2565
|
function isPathStatement(node, parent) {
|
|
2238
2566
|
if (node.type === "elsif") {
|
|
@@ -2260,7 +2588,7 @@ function siteOf(node) {
|
|
|
2260
2588
|
current = parent;
|
|
2261
2589
|
}
|
|
2262
2590
|
}
|
|
2263
|
-
function
|
|
2591
|
+
function parametersOf2(parameters) {
|
|
2264
2592
|
if (parameters === null) {
|
|
2265
2593
|
return [];
|
|
2266
2594
|
}
|
|
@@ -2282,19 +2610,19 @@ function functionOf(node) {
|
|
|
2282
2610
|
return null;
|
|
2283
2611
|
}
|
|
2284
2612
|
return {
|
|
2285
|
-
parameters:
|
|
2613
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2286
2614
|
body: bodyOf(block)
|
|
2287
2615
|
};
|
|
2288
2616
|
}
|
|
2289
|
-
if (
|
|
2617
|
+
if (BLOCK_TYPES3.has(node.type)) {
|
|
2290
2618
|
return {
|
|
2291
|
-
parameters:
|
|
2619
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2292
2620
|
body: bodyOf(node)
|
|
2293
2621
|
};
|
|
2294
2622
|
}
|
|
2295
2623
|
if (METHOD_TYPES2.has(node.type)) {
|
|
2296
2624
|
const body = field(node, "body");
|
|
2297
|
-
const parameters =
|
|
2625
|
+
const parameters = parametersOf2(field(node, "parameters"));
|
|
2298
2626
|
if (body === null) {
|
|
2299
2627
|
return { parameters, body: [] };
|
|
2300
2628
|
}
|
|
@@ -3507,13 +3835,8 @@ function namesUnder(node) {
|
|
|
3507
3835
|
}
|
|
3508
3836
|
|
|
3509
3837
|
// src/storage.ts
|
|
3510
|
-
import { deriveOnDemand as deriveOnDemand2, evaluate as evaluate2 } from "@suss/datalog";
|
|
3511
3838
|
import { storageBinding } from "@suss/ir-core";
|
|
3512
|
-
import {
|
|
3513
|
-
ANSWER_RELATIONS as ANSWER_RELATIONS2,
|
|
3514
|
-
RESOLUTION_QUESTIONS as RESOLUTION_QUESTIONS2,
|
|
3515
|
-
RESOLUTION_RULES as RESOLUTION_RULES2
|
|
3516
|
-
} from "@suss/resolution";
|
|
3839
|
+
import { askResolution as askResolution2 } from "@suss/resolution";
|
|
3517
3840
|
function children3(node) {
|
|
3518
3841
|
return node.namedChildren.filter((child) => child !== null);
|
|
3519
3842
|
}
|
|
@@ -3539,13 +3862,8 @@ function isConstant(node) {
|
|
|
3539
3862
|
function constantName(constant2) {
|
|
3540
3863
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3541
3864
|
}
|
|
3542
|
-
var ANCESTRY_PROGRAM = deriveOnDemand2(
|
|
3543
|
-
[...RESOLUTION_RULES2, ...RESOLUTION_QUESTIONS2],
|
|
3544
|
-
ANSWER_RELATIONS2
|
|
3545
|
-
);
|
|
3546
3865
|
function reachesBase(facts, classKey, bases) {
|
|
3547
|
-
facts
|
|
3548
|
-
evaluate2(facts, ANCESTRY_PROGRAM.rules);
|
|
3866
|
+
askResolution2(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
3549
3867
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3550
3868
|
}
|
|
3551
3869
|
function classBehind(facts, file, constant2) {
|
|
@@ -4483,7 +4801,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4483
4801
|
if (written !== null) {
|
|
4484
4802
|
definitions.push({
|
|
4485
4803
|
qualifiedName: qualify(nesting, written),
|
|
4486
|
-
key: nodeId(filePath, node)
|
|
4804
|
+
key: nodeId(filePath, node),
|
|
4805
|
+
kind: "declaration"
|
|
4487
4806
|
});
|
|
4488
4807
|
const inside = [...nesting, ...written.split("::")];
|
|
4489
4808
|
for (const child of children4(node)) {
|
|
@@ -4498,7 +4817,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4498
4817
|
if (left !== null && left.type === "constant" && right !== null) {
|
|
4499
4818
|
definitions.push({
|
|
4500
4819
|
qualifiedName: qualify(nesting, left.text),
|
|
4501
|
-
key: nodeId(filePath, right)
|
|
4820
|
+
key: nodeId(filePath, right),
|
|
4821
|
+
kind: "assignment"
|
|
4502
4822
|
});
|
|
4503
4823
|
}
|
|
4504
4824
|
}
|
|
@@ -4525,14 +4845,18 @@ function emitConstantBindings(db, perFile) {
|
|
|
4525
4845
|
for (const file of files) {
|
|
4526
4846
|
for (const definition of file.definitions) {
|
|
4527
4847
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
4528
|
-
found.push(definition
|
|
4848
|
+
found.push(definition);
|
|
4529
4849
|
byName.set(definition.qualifiedName, found);
|
|
4530
4850
|
fileOfDefinition.set(definition.key, file.filePath);
|
|
4531
4851
|
}
|
|
4532
4852
|
}
|
|
4533
4853
|
const settles = (candidate) => {
|
|
4534
|
-
const found = byName.get(candidate);
|
|
4535
|
-
|
|
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;
|
|
4536
4860
|
};
|
|
4537
4861
|
for (const file of files) {
|
|
4538
4862
|
for (const reference of file.references) {
|
|
@@ -4559,7 +4883,7 @@ function emitConstantBindings(db, perFile) {
|
|
|
4559
4883
|
import fs4 from "fs";
|
|
4560
4884
|
import path5 from "path";
|
|
4561
4885
|
import { Database as Database3 } from "@suss/datalog";
|
|
4562
|
-
import { explainResolvedKey, RESOLUTION_RULES
|
|
4886
|
+
import { explainResolvedKey, RESOLUTION_RULES } from "@suss/resolution";
|
|
4563
4887
|
|
|
4564
4888
|
// src/parser.ts
|
|
4565
4889
|
import fs2 from "fs";
|
|
@@ -4707,34 +5031,118 @@ import {
|
|
|
4707
5031
|
unfollowedCallGap,
|
|
4708
5032
|
worthRecording
|
|
4709
5033
|
} from "@suss/behavioral-ir";
|
|
4710
|
-
import { Database, evaluate
|
|
5034
|
+
import { Database, evaluate, lit as lit2, rule as rule2, variable as v2 } from "@suss/datalog";
|
|
4711
5035
|
import { assembleSummary } from "@suss/extractor";
|
|
4712
5036
|
|
|
4713
5037
|
// src/reach/resolveCallee.ts
|
|
5038
|
+
import { calleeOutcomeOf, calleeOutcomes } from "@suss/resolution";
|
|
4714
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
|
+
};
|
|
4715
5047
|
var stop = (reason) => ({
|
|
4716
5048
|
kind: "stopped",
|
|
4717
5049
|
reason
|
|
4718
5050
|
});
|
|
5051
|
+
var NO_DECLARATION = stop("noDeclaration");
|
|
4719
5052
|
function followed(target) {
|
|
4720
5053
|
return { kind: "followed", target };
|
|
4721
5054
|
}
|
|
4722
|
-
|
|
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) {
|
|
4723
5082
|
const methodName = calleeMethodName(call);
|
|
4724
5083
|
if (methodName === void 0) {
|
|
4725
|
-
return
|
|
5084
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
4726
5085
|
}
|
|
4727
5086
|
if (DYNAMIC_SEND_NAMES.has(methodName)) {
|
|
4728
|
-
return
|
|
5087
|
+
return { kind: "stopped", reason: "unsettledValue" };
|
|
4729
5088
|
}
|
|
4730
5089
|
const receiver = field(call, "receiver");
|
|
4731
5090
|
if (receiver === null || receiver.type === "self") {
|
|
4732
|
-
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);
|
|
4733
5114
|
}
|
|
4734
|
-
if (
|
|
4735
|
-
return
|
|
5115
|
+
if (outcome.kind === "function") {
|
|
5116
|
+
return spelling.method === INVOKES_RECEIVER ? functionCallee(outcome.key, ctx) : NO_DECLARATION;
|
|
4736
5117
|
}
|
|
4737
|
-
return
|
|
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;
|
|
5131
|
+
}
|
|
5132
|
+
if (!spelling.onClassItself) {
|
|
5133
|
+
return methodOnAncestryOf(qualifiedName, spelling.method, ctx);
|
|
5134
|
+
}
|
|
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);
|
|
4738
5146
|
}
|
|
4739
5147
|
async function resolveMethodReference(name, site, ctx) {
|
|
4740
5148
|
const resolved = await resolveImplicitSelf(name, site, ctx);
|
|
@@ -4759,58 +5167,13 @@ async function resolveImplicitSelf(methodName, site, ctx) {
|
|
|
4759
5167
|
function resolveTopLevelName(methodName, ctx) {
|
|
4760
5168
|
const candidates = ctx.topLevelMethods.get(methodName);
|
|
4761
5169
|
if (candidates === void 0 || candidates.length === 0) {
|
|
4762
|
-
return
|
|
5170
|
+
return NO_DECLARATION;
|
|
4763
5171
|
}
|
|
4764
5172
|
if (candidates.length > 1) {
|
|
4765
5173
|
return stop("multipleSources");
|
|
4766
5174
|
}
|
|
4767
5175
|
return followed(candidates[0]);
|
|
4768
5176
|
}
|
|
4769
|
-
async function resolveOnReceiver(receiver, methodName, site, ctx) {
|
|
4770
|
-
if (receiver.type === "call") {
|
|
4771
|
-
const innerName = calleeMethodName(receiver);
|
|
4772
|
-
const innerReceiver = field(receiver, "receiver");
|
|
4773
|
-
if (innerName === "new" && innerReceiver !== null) {
|
|
4774
|
-
const classRef = classRefOf(innerReceiver, site, ctx);
|
|
4775
|
-
return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
|
|
4776
|
-
}
|
|
4777
|
-
const root = rootConstantOf(receiver);
|
|
4778
|
-
if (root === null) {
|
|
4779
|
-
return stop("unsettledValue");
|
|
4780
|
-
}
|
|
4781
|
-
return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
|
|
4782
|
-
}
|
|
4783
|
-
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
4784
|
-
const classRef = classRefOf(receiver, site, ctx);
|
|
4785
|
-
return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
|
|
4786
|
-
}
|
|
4787
|
-
return stop("unsettledValue");
|
|
4788
|
-
}
|
|
4789
|
-
function rootConstantOf(node) {
|
|
4790
|
-
if (node.type === "constant" || node.type === "scope_resolution") {
|
|
4791
|
-
return node;
|
|
4792
|
-
}
|
|
4793
|
-
if (node.type !== "call") {
|
|
4794
|
-
return null;
|
|
4795
|
-
}
|
|
4796
|
-
const receiver = field(node, "receiver");
|
|
4797
|
-
return receiver === null ? null : rootConstantOf(receiver);
|
|
4798
|
-
}
|
|
4799
|
-
function classRefOf(node, site, ctx) {
|
|
4800
|
-
if (node.type === "constant") {
|
|
4801
|
-
const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
|
|
4802
|
-
if (shadow !== null) {
|
|
4803
|
-
return shadow;
|
|
4804
|
-
}
|
|
4805
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4806
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4807
|
-
}
|
|
4808
|
-
if (node.type === "scope_resolution") {
|
|
4809
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4810
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4811
|
-
}
|
|
4812
|
-
return null;
|
|
4813
|
-
}
|
|
4814
5177
|
async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
4815
5178
|
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
4816
5179
|
if (ownBlocks === null) {
|
|
@@ -4826,7 +5189,7 @@ async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
|
4826
5189
|
found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
|
|
4827
5190
|
);
|
|
4828
5191
|
}
|
|
4829
|
-
return
|
|
5192
|
+
return NO_DECLARATION;
|
|
4830
5193
|
}
|
|
4831
5194
|
function reachedMethod(node, block, name) {
|
|
4832
5195
|
return {
|
|
@@ -4851,7 +5214,7 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
|
4851
5214
|
return followed(reachedMethod(found, block, methodName));
|
|
4852
5215
|
}
|
|
4853
5216
|
}
|
|
4854
|
-
return
|
|
5217
|
+
return NO_DECLARATION;
|
|
4855
5218
|
}
|
|
4856
5219
|
|
|
4857
5220
|
// src/reach/closure.ts
|
|
@@ -4864,7 +5227,7 @@ var REACHABLE_RULES = [
|
|
|
4864
5227
|
)
|
|
4865
5228
|
];
|
|
4866
5229
|
async function reachedFunctions(seeds, options) {
|
|
4867
|
-
const ctx = buildReachContext(options.files);
|
|
5230
|
+
const ctx = buildReachContext(options.files, options.facts);
|
|
4868
5231
|
const db = new Database();
|
|
4869
5232
|
const functionByKey = /* @__PURE__ */ new Map();
|
|
4870
5233
|
const seedKeys = /* @__PURE__ */ new Set();
|
|
@@ -4886,18 +5249,26 @@ async function reachedFunctions(seeds, options) {
|
|
|
4886
5249
|
db.add("entry", [seed.key]);
|
|
4887
5250
|
}
|
|
4888
5251
|
for (; ; ) {
|
|
4889
|
-
|
|
5252
|
+
evaluate(db, REACHABLE_RULES);
|
|
4890
5253
|
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
4891
5254
|
if (frontier.length === 0) {
|
|
4892
5255
|
break;
|
|
4893
5256
|
}
|
|
4894
|
-
|
|
5257
|
+
const bodies = frontier.flatMap((key) => {
|
|
4895
5258
|
scanned.add(key);
|
|
4896
5259
|
const source = functionByKey.get(key);
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
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
|
+
});
|
|
4901
5272
|
if (scan.stops.length > 0) {
|
|
4902
5273
|
stopsByKey.set(key, scan.stops);
|
|
4903
5274
|
}
|
|
@@ -4950,11 +5321,22 @@ async function reachedFunctions(seeds, options) {
|
|
|
4950
5321
|
function keyOf(target) {
|
|
4951
5322
|
return nodeId(target.file, target.node);
|
|
4952
5323
|
}
|
|
4953
|
-
|
|
5324
|
+
var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5325
|
+
function buildReachContext(files, facts) {
|
|
4954
5326
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
4955
5327
|
const classes = [];
|
|
5328
|
+
const classNames = /* @__PURE__ */ new Map();
|
|
5329
|
+
const classOfMethod = /* @__PURE__ */ new Map();
|
|
4956
5330
|
for (const { file, root } of files) {
|
|
4957
|
-
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
|
+
});
|
|
4958
5340
|
}
|
|
4959
5341
|
const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
|
|
4960
5342
|
for (const { file, info } of classes) {
|
|
@@ -4962,22 +5344,33 @@ function buildReachContext(files) {
|
|
|
4962
5344
|
list.push({ info, knownClasses, file });
|
|
4963
5345
|
blocksByQualifiedName.set(info.qualifiedName, list);
|
|
4964
5346
|
}
|
|
4965
|
-
const
|
|
5347
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4966
5348
|
for (const { file, root } of files) {
|
|
4967
|
-
for (const method of
|
|
5349
|
+
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
4968
5350
|
const name = field(method, "name")?.text;
|
|
4969
5351
|
if (name === void 0) {
|
|
4970
5352
|
continue;
|
|
4971
5353
|
}
|
|
4972
|
-
const
|
|
4973
|
-
|
|
5354
|
+
const owner = classOfMethod.get(key) ?? null;
|
|
5355
|
+
definitions.set(key, {
|
|
4974
5356
|
file,
|
|
4975
5357
|
node: method,
|
|
4976
5358
|
name,
|
|
4977
|
-
exportPath: [name],
|
|
4978
|
-
enclosingQualifiedName:
|
|
5359
|
+
exportPath: owner === null ? [name] : [owner, name],
|
|
5360
|
+
enclosingQualifiedName: owner
|
|
4979
5361
|
});
|
|
4980
|
-
|
|
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);
|
|
4981
5374
|
}
|
|
4982
5375
|
}
|
|
4983
5376
|
const lookup = {
|
|
@@ -4990,7 +5383,13 @@ function buildReachContext(files) {
|
|
|
4990
5383
|
parsedFile: async () => null,
|
|
4991
5384
|
localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
|
|
4992
5385
|
};
|
|
4993
|
-
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);
|
|
4994
5393
|
}
|
|
4995
5394
|
function topLevelMethodNodes(root, found = []) {
|
|
4996
5395
|
for (const child of bodyStatements(root)) {
|
|
@@ -5005,14 +5404,6 @@ function topLevelMethodNodes(root, found = []) {
|
|
|
5005
5404
|
}
|
|
5006
5405
|
return found;
|
|
5007
5406
|
}
|
|
5008
|
-
function nestingOf(qualifiedName) {
|
|
5009
|
-
const parts = qualifiedName.split("::");
|
|
5010
|
-
const out = [];
|
|
5011
|
-
for (let depth = parts.length; depth >= 1; depth--) {
|
|
5012
|
-
out.push(parts.slice(0, depth).join("::"));
|
|
5013
|
-
}
|
|
5014
|
-
return out;
|
|
5015
|
-
}
|
|
5016
5407
|
var EMPTY_SCAN = {
|
|
5017
5408
|
followed: [],
|
|
5018
5409
|
stops: [],
|
|
@@ -5030,21 +5421,23 @@ function methodReferenceSymbol(node) {
|
|
|
5030
5421
|
const argChildren = args === null ? [] : bodyStatements(args);
|
|
5031
5422
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
5032
5423
|
}
|
|
5033
|
-
|
|
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) {
|
|
5034
5435
|
const displayPathOf = options.displayPathOf;
|
|
5035
|
-
|
|
5036
|
-
if (body === null) {
|
|
5436
|
+
if (field(source.node, "body") === null) {
|
|
5037
5437
|
return EMPTY_SCAN;
|
|
5038
5438
|
}
|
|
5039
|
-
const calls =
|
|
5040
|
-
bodyCalls(source.node, options.inheritedMethods)
|
|
5041
|
-
);
|
|
5439
|
+
const { calls, site } = read;
|
|
5042
5440
|
const ownParameters = positionalParameters(source.node).map((p) => p.name);
|
|
5043
|
-
const site = {
|
|
5044
|
-
enclosingQualifiedName: source.enclosingQualifiedName,
|
|
5045
|
-
nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
|
|
5046
|
-
ownParameters
|
|
5047
|
-
};
|
|
5048
5441
|
const followed2 = [];
|
|
5049
5442
|
const stops = [];
|
|
5050
5443
|
const placements = new TargetPlacements();
|
|
@@ -5087,7 +5480,7 @@ async function scanBody(source, ctx, options) {
|
|
|
5087
5480
|
};
|
|
5088
5481
|
for (const call of calls) {
|
|
5089
5482
|
const callee = calleeText(call);
|
|
5090
|
-
const outcome = await resolveCallee(call, site, ctx);
|
|
5483
|
+
const outcome = await resolveCallee(call, site, ctx, read.spellings);
|
|
5091
5484
|
const placed = outcome.kind === "followed" ? {
|
|
5092
5485
|
file: displayPathOf(outcome.target.file),
|
|
5093
5486
|
span: spanOf(outcome.target.node)
|
|
@@ -5386,6 +5779,7 @@ async function extractRubyProject(options) {
|
|
|
5386
5779
|
() => reachedFunctions(seeds, {
|
|
5387
5780
|
files: parsed,
|
|
5388
5781
|
displayPathOf,
|
|
5782
|
+
facts: db,
|
|
5389
5783
|
...storage === void 0 ? {} : { storage },
|
|
5390
5784
|
inheritedMethods
|
|
5391
5785
|
})
|
|
@@ -5482,15 +5876,15 @@ function findRubyFiles(root) {
|
|
|
5482
5876
|
}
|
|
5483
5877
|
|
|
5484
5878
|
// src/facts/why.ts
|
|
5485
|
-
var WITNESS_RULES = [...
|
|
5486
|
-
var
|
|
5879
|
+
var WITNESS_RULES = [...RESOLUTION_RULES, ...RUBY_RULES];
|
|
5880
|
+
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5487
5881
|
function namedChildrenOf(node) {
|
|
5488
5882
|
return node.namedChildren.filter((child) => child !== null);
|
|
5489
5883
|
}
|
|
5490
5884
|
function enclosingMethod2(node) {
|
|
5491
5885
|
let current = node.parent;
|
|
5492
5886
|
while (current !== null) {
|
|
5493
|
-
if (
|
|
5887
|
+
if (METHOD_TYPES5.has(current.type)) {
|
|
5494
5888
|
return current;
|
|
5495
5889
|
}
|
|
5496
5890
|
current = current.parent;
|