@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.cjs
CHANGED
|
@@ -1346,7 +1346,7 @@ function calledMethod(node) {
|
|
|
1346
1346
|
}
|
|
1347
1347
|
|
|
1348
1348
|
// src/values/evaluator.ts
|
|
1349
|
-
var
|
|
1349
|
+
var import_resolution4 = require("@suss/resolution");
|
|
1350
1350
|
var import_values2 = require("@suss/values");
|
|
1351
1351
|
|
|
1352
1352
|
// src/facts/resolve.ts
|
|
@@ -1366,18 +1366,9 @@ var RUBY_RULES = [
|
|
|
1366
1366
|
]
|
|
1367
1367
|
)
|
|
1368
1368
|
];
|
|
1369
|
-
var
|
|
1370
|
-
[...import_resolution.RESOLUTION_RULES, ...RUBY_RULES, ...import_resolution.RESOLUTION_QUESTIONS],
|
|
1371
|
-
import_resolution.ANSWER_RELATIONS
|
|
1372
|
-
);
|
|
1369
|
+
var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
|
|
1373
1370
|
function resolveValues(db, keys) {
|
|
1374
|
-
|
|
1375
|
-
return;
|
|
1376
|
-
}
|
|
1377
|
-
for (const key of keys) {
|
|
1378
|
-
db.add("wanted", [key]);
|
|
1379
|
-
}
|
|
1380
|
-
(0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
|
|
1371
|
+
(0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
|
|
1381
1372
|
}
|
|
1382
1373
|
function resolvedFunctions(db, key) {
|
|
1383
1374
|
return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
|
|
@@ -1388,27 +1379,262 @@ function writtenValueOf(db, key) {
|
|
|
1388
1379
|
}
|
|
1389
1380
|
|
|
1390
1381
|
// src/facts/values.ts
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1382
|
+
var import_resolution3 = require("@suss/resolution");
|
|
1383
|
+
|
|
1384
|
+
// src/facts/locals.ts
|
|
1385
|
+
var import_resolution2 = require("@suss/resolution");
|
|
1386
|
+
var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1387
|
+
var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES2]);
|
|
1388
|
+
var RUBY_NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1389
|
+
"identifier",
|
|
1390
|
+
"instance_variable"
|
|
1391
|
+
]);
|
|
1392
|
+
var TARGET_LIST_TYPES = /* @__PURE__ */ new Set([
|
|
1393
|
+
"left_assignment_list",
|
|
1394
|
+
"destructured_left_assignment",
|
|
1395
|
+
"rest_assignment"
|
|
1396
|
+
]);
|
|
1397
|
+
var WHOLE_VALUE_OPERATORS = /* @__PURE__ */ new Set([
|
|
1398
|
+
"||=",
|
|
1399
|
+
"&&="
|
|
1400
|
+
]);
|
|
1397
1401
|
function paramNameOf(param) {
|
|
1398
1402
|
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
1399
1403
|
}
|
|
1400
|
-
function
|
|
1401
|
-
const params = field(
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
+
function parametersOf(owner) {
|
|
1405
|
+
const params = field(owner, "parameters");
|
|
1406
|
+
return params === null ? [] : bodyStatements(params);
|
|
1407
|
+
}
|
|
1408
|
+
function parameterNames2(owner) {
|
|
1409
|
+
const names = /* @__PURE__ */ new Set();
|
|
1410
|
+
for (const param of parametersOf(owner)) {
|
|
1404
1411
|
const name = paramNameOf(param);
|
|
1405
1412
|
if (name !== null) {
|
|
1406
|
-
|
|
1413
|
+
names.add(name.text);
|
|
1407
1414
|
}
|
|
1408
1415
|
}
|
|
1409
|
-
return
|
|
1416
|
+
return names;
|
|
1417
|
+
}
|
|
1418
|
+
function targetsOf(left) {
|
|
1419
|
+
if (left.type === "identifier" || left.type === "constant") {
|
|
1420
|
+
return [left];
|
|
1421
|
+
}
|
|
1422
|
+
if (TARGET_LIST_TYPES.has(left.type)) {
|
|
1423
|
+
return bodyStatements(left).flatMap(targetsOf);
|
|
1424
|
+
}
|
|
1425
|
+
return [];
|
|
1410
1426
|
}
|
|
1411
|
-
function
|
|
1427
|
+
function writeOf(target, value, at, fromParameter) {
|
|
1428
|
+
return { name: target.text, target, value, at, fromParameter };
|
|
1429
|
+
}
|
|
1430
|
+
function readAssignment(node) {
|
|
1431
|
+
const left = field(node, "left");
|
|
1432
|
+
const right = field(node, "right");
|
|
1433
|
+
if (left === null || right === null) {
|
|
1434
|
+
return [];
|
|
1435
|
+
}
|
|
1436
|
+
const value = left.type === "left_assignment_list" ? null : right;
|
|
1437
|
+
return targetsOf(left).map((target) => writeOf(target, value, node, false));
|
|
1438
|
+
}
|
|
1439
|
+
function readOperatorAssignment(node) {
|
|
1440
|
+
const left = field(node, "left");
|
|
1441
|
+
const right = field(node, "right");
|
|
1442
|
+
const operator = field(node, "operator");
|
|
1443
|
+
if (left === null || right === null || left.type !== "identifier") {
|
|
1444
|
+
return [];
|
|
1445
|
+
}
|
|
1446
|
+
const whole = operator !== null && WHOLE_VALUE_OPERATORS.has(operator.text);
|
|
1447
|
+
return [writeOf(left, whole ? right : null, node, false)];
|
|
1448
|
+
}
|
|
1449
|
+
function readForVariable(node) {
|
|
1450
|
+
const pattern = field(node, "pattern");
|
|
1451
|
+
if (pattern === null) {
|
|
1452
|
+
return [];
|
|
1453
|
+
}
|
|
1454
|
+
return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
|
|
1455
|
+
}
|
|
1456
|
+
function readBlockParameters(node) {
|
|
1457
|
+
const found = [];
|
|
1458
|
+
for (const param of parametersOf(node)) {
|
|
1459
|
+
const name = paramNameOf(param);
|
|
1460
|
+
if (name !== null) {
|
|
1461
|
+
found.push(writeOf(name, null, param, true));
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return found;
|
|
1465
|
+
}
|
|
1466
|
+
var WRITE_READERS = {
|
|
1467
|
+
assignment: readAssignment,
|
|
1468
|
+
operator_assignment: readOperatorAssignment,
|
|
1469
|
+
for: readForVariable,
|
|
1470
|
+
block: readBlockParameters,
|
|
1471
|
+
do_block: readBlockParameters
|
|
1472
|
+
};
|
|
1473
|
+
function writesUnder(body) {
|
|
1474
|
+
const found = [];
|
|
1475
|
+
const visit = (node) => {
|
|
1476
|
+
for (const child of bodyStatements(node)) {
|
|
1477
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
found.push(...WRITE_READERS[child.type]?.(child) ?? []);
|
|
1481
|
+
visit(child);
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
visit(body);
|
|
1485
|
+
return found;
|
|
1486
|
+
}
|
|
1487
|
+
var namesByTree = /* @__PURE__ */ new WeakMap();
|
|
1488
|
+
function cacheFor(tree) {
|
|
1489
|
+
const known = namesByTree.get(tree);
|
|
1490
|
+
if (known !== void 0) {
|
|
1491
|
+
return known;
|
|
1492
|
+
}
|
|
1493
|
+
const fresh = { file: null, byMethod: new NodeMap() };
|
|
1494
|
+
namesByTree.set(tree, fresh);
|
|
1495
|
+
return fresh;
|
|
1496
|
+
}
|
|
1497
|
+
function scopeNames(parameters, body) {
|
|
1498
|
+
const locals = new Set(parameters);
|
|
1499
|
+
const blockParams = /* @__PURE__ */ new Set();
|
|
1500
|
+
for (const write of body === null ? [] : writesUnder(body)) {
|
|
1501
|
+
if (write.fromParameter) {
|
|
1502
|
+
blockParams.add(write.name);
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
locals.add(write.name);
|
|
1506
|
+
}
|
|
1507
|
+
return { locals, blockParams };
|
|
1508
|
+
}
|
|
1509
|
+
function namesOfMethod(method) {
|
|
1510
|
+
const cache = cacheFor(method.tree);
|
|
1511
|
+
const remembered = cache.byMethod.get(method);
|
|
1512
|
+
if (remembered !== void 0) {
|
|
1513
|
+
return remembered;
|
|
1514
|
+
}
|
|
1515
|
+
const names = scopeNames(parameterNames2(method), field(method, "body"));
|
|
1516
|
+
cache.byMethod.set(method, names);
|
|
1517
|
+
return names;
|
|
1518
|
+
}
|
|
1519
|
+
function namesOfFile(tree) {
|
|
1520
|
+
const cache = cacheFor(tree);
|
|
1521
|
+
cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
|
|
1522
|
+
return cache.file;
|
|
1523
|
+
}
|
|
1524
|
+
function ownerOfName(node, name, enclosing) {
|
|
1525
|
+
if (node.type === "constant") {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
|
|
1529
|
+
if (names.blockParams.has(name)) {
|
|
1530
|
+
const block = blockDeclaring(node, name);
|
|
1531
|
+
if (block !== null) {
|
|
1532
|
+
return block;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return enclosing !== null && names.locals.has(name) ? enclosing : null;
|
|
1536
|
+
}
|
|
1537
|
+
function blockDeclaring(node, name) {
|
|
1538
|
+
let current = node.parent;
|
|
1539
|
+
while (current !== null) {
|
|
1540
|
+
if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
|
|
1541
|
+
return current;
|
|
1542
|
+
}
|
|
1543
|
+
if (OWN_BODY_TYPES.has(current.type)) {
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
current = current.parent;
|
|
1547
|
+
}
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
function parameterWrites(method) {
|
|
1551
|
+
const found = [];
|
|
1552
|
+
for (const param of method === null ? [] : parametersOf(method)) {
|
|
1553
|
+
const name = paramNameOf(param);
|
|
1554
|
+
if (name !== null) {
|
|
1555
|
+
found.push(writeOf(name, name, param, true));
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return found;
|
|
1559
|
+
}
|
|
1560
|
+
function collectWrites(method, body) {
|
|
1561
|
+
const writes = [...parameterWrites(method), ...writesUnder(body)].sort(
|
|
1562
|
+
(left, right) => left.target.startIndex - right.target.startIndex
|
|
1563
|
+
);
|
|
1564
|
+
const targetIds = new Set(writes.map((write) => write.target.id));
|
|
1565
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1566
|
+
for (const write of writes) {
|
|
1567
|
+
const owner = ownerOfName(write.target, write.name, method);
|
|
1568
|
+
const key = `${owner === null ? "" : owner.id} ${write.name}`;
|
|
1569
|
+
const group = groups.get(key);
|
|
1570
|
+
if (group === void 0) {
|
|
1571
|
+
groups.set(key, {
|
|
1572
|
+
owner,
|
|
1573
|
+
name: write.name,
|
|
1574
|
+
writes: [write],
|
|
1575
|
+
ordered: false
|
|
1576
|
+
});
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
group.writes.push(write);
|
|
1580
|
+
}
|
|
1581
|
+
const rules = nameReads(targetIds);
|
|
1582
|
+
for (const group of groups.values()) {
|
|
1583
|
+
const scope = group.owner === null ? body : field(group.owner, "body");
|
|
1584
|
+
if (scope === null) {
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
group.ordered = (0, import_resolution2.writesRunInOrder)(
|
|
1588
|
+
scope,
|
|
1589
|
+
group.name,
|
|
1590
|
+
group.writes.map((write) => orderedWrite(write, scope)),
|
|
1591
|
+
rules
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
return [...groups.values()];
|
|
1595
|
+
}
|
|
1596
|
+
function orderedWrite(write, scope) {
|
|
1597
|
+
return {
|
|
1598
|
+
at: write.at,
|
|
1599
|
+
direct: !write.fromParameter && write.at.parent?.id === scope.id
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function nameReads(targetIds) {
|
|
1603
|
+
return {
|
|
1604
|
+
nameTypes: RUBY_NAME_TYPES,
|
|
1605
|
+
laterBodies: LATER_BODY_TYPES,
|
|
1606
|
+
childrenOf: bodyStatements,
|
|
1607
|
+
isRead: (node) => isNameRead(node, targetIds)
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function isNameRead(node, targetIds) {
|
|
1611
|
+
if (targetIds.has(node.id)) {
|
|
1612
|
+
return false;
|
|
1613
|
+
}
|
|
1614
|
+
const parent = node.parent;
|
|
1615
|
+
return !(parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// src/facts/values.ts
|
|
1619
|
+
function nodeId(filePath, node) {
|
|
1620
|
+
return `${filePath}:${node.startIndex}-${node.endIndex}`;
|
|
1621
|
+
}
|
|
1622
|
+
function nameId(filePath, name) {
|
|
1623
|
+
return `${filePath}#${name}`;
|
|
1624
|
+
}
|
|
1625
|
+
function readThrough(node) {
|
|
1626
|
+
if (node.type !== "parenthesized_statements") {
|
|
1627
|
+
return node;
|
|
1628
|
+
}
|
|
1629
|
+
const inner = node.namedChildren.filter((child) => child !== null);
|
|
1630
|
+
return inner.length === 1 ? readThrough(inner[0]) : node;
|
|
1631
|
+
}
|
|
1632
|
+
function nameKey(filePath, node, enclosing) {
|
|
1633
|
+
const owner = ownerOfName(node, node.text, enclosing);
|
|
1634
|
+
return owner === null ? nameId(filePath, node.text) : `${nodeId(filePath, owner)}#${node.text}`;
|
|
1635
|
+
}
|
|
1636
|
+
function readKey(filePath, written, enclosing) {
|
|
1637
|
+
const node = readThrough(written);
|
|
1412
1638
|
if (node.type !== "identifier" && node.type !== "constant") {
|
|
1413
1639
|
return nodeId(filePath, node);
|
|
1414
1640
|
}
|
|
@@ -1416,10 +1642,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1416
1642
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1417
1643
|
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1418
1644
|
}
|
|
1419
|
-
|
|
1420
|
-
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
1421
|
-
}
|
|
1422
|
-
return nameId(filePath, node.text);
|
|
1645
|
+
return nameKey(filePath, node, enclosing);
|
|
1423
1646
|
}
|
|
1424
1647
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
1425
1648
|
"string",
|
|
@@ -1448,15 +1671,12 @@ function children2(node) {
|
|
|
1448
1671
|
function add(emitter, relation, ...tuple) {
|
|
1449
1672
|
emitter.db.add(relation, tuple);
|
|
1450
1673
|
}
|
|
1451
|
-
function valueKey(emitter,
|
|
1674
|
+
function valueKey(emitter, written) {
|
|
1675
|
+
const value = readThrough(written);
|
|
1452
1676
|
if (value.type !== "identifier" && value.type !== "constant") {
|
|
1453
1677
|
return nodeId(emitter.filePath, value);
|
|
1454
1678
|
}
|
|
1455
|
-
|
|
1456
|
-
if (enclosing !== null && enclosing.params.has(value.text)) {
|
|
1457
|
-
return `${enclosing.funcKey}#${value.text}`;
|
|
1458
|
-
}
|
|
1459
|
-
return nameId(emitter.filePath, value.text);
|
|
1679
|
+
return nameKey(emitter.filePath, value, emitter.enclosing);
|
|
1460
1680
|
}
|
|
1461
1681
|
function pairKeyText(key) {
|
|
1462
1682
|
if (key.type === "hash_key_symbol") {
|
|
@@ -1595,8 +1815,72 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1595
1815
|
if (child.type === "nil") {
|
|
1596
1816
|
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
1597
1817
|
}
|
|
1818
|
+
if (child.type === "self" && emitter.selfKey !== null) {
|
|
1819
|
+
add(emitter, "binds", nodeId(emitter.filePath, child), emitter.selfKey);
|
|
1820
|
+
}
|
|
1821
|
+
if (child.type === "instance_variable") {
|
|
1822
|
+
emitInstanceRead(emitter, child);
|
|
1823
|
+
}
|
|
1824
|
+
if (ASSIGNMENT_TYPES.has(child.type)) {
|
|
1825
|
+
collectInstanceWrite(emitter, child);
|
|
1826
|
+
}
|
|
1598
1827
|
});
|
|
1599
1828
|
}
|
|
1829
|
+
var ASSIGNMENT_TYPES = /* @__PURE__ */ new Set(["assignment", "operator_assignment"]);
|
|
1830
|
+
function isWriteTarget(node) {
|
|
1831
|
+
const parent = node.parent;
|
|
1832
|
+
return parent !== null && ASSIGNMENT_TYPES.has(parent.type) && field(parent, "left")?.id === node.id;
|
|
1833
|
+
}
|
|
1834
|
+
function emitInstanceRead(emitter, node) {
|
|
1835
|
+
if (emitter.selfKey === null || isWriteTarget(node)) {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
add(
|
|
1839
|
+
emitter,
|
|
1840
|
+
"readsProperty",
|
|
1841
|
+
nodeId(emitter.filePath, node),
|
|
1842
|
+
emitter.selfKey,
|
|
1843
|
+
node.text
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
function collectInstanceWrite(emitter, node) {
|
|
1847
|
+
const left = field(node, "left");
|
|
1848
|
+
const right = field(node, "right");
|
|
1849
|
+
const collector = emitter.instanceWrites;
|
|
1850
|
+
if (left === null || right === null || collector === null) {
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (left.type !== "instance_variable") {
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const operator = field(node, "operator")?.text;
|
|
1857
|
+
const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
|
|
1858
|
+
const written = collector.get(left.text) ?? [];
|
|
1859
|
+
written.push(
|
|
1860
|
+
describeWrite(emitter, {
|
|
1861
|
+
name: left.text,
|
|
1862
|
+
target: left,
|
|
1863
|
+
value,
|
|
1864
|
+
at: node,
|
|
1865
|
+
fromParameter: false
|
|
1866
|
+
})
|
|
1867
|
+
);
|
|
1868
|
+
collector.set(left.text, written);
|
|
1869
|
+
}
|
|
1870
|
+
function emitInstanceWrites(emitter, classKey, collected) {
|
|
1871
|
+
for (const [name, writes] of collected) {
|
|
1872
|
+
const settled = (0, import_resolution3.valueLeftByWrites)(writes, false);
|
|
1873
|
+
if (settled !== null) {
|
|
1874
|
+
add(emitter, "holdsProperty", classKey, name, settled);
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
for (const write of writes) {
|
|
1878
|
+
if (write.value !== null && !write.narrowsName) {
|
|
1879
|
+
add(emitter, "holdsProperty", classKey, name, write.value);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1600
1884
|
function implicitReturn(body) {
|
|
1601
1885
|
const statements = children2(body).filter(
|
|
1602
1886
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
@@ -1607,23 +1891,17 @@ function implicitReturn(body) {
|
|
|
1607
1891
|
function emitMethodFacts(emitter, method) {
|
|
1608
1892
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1609
1893
|
add(emitter, "func", funcKey);
|
|
1610
|
-
const params = field(method, "parameters");
|
|
1611
|
-
const declared = /* @__PURE__ */ new Set();
|
|
1612
1894
|
let position = 0;
|
|
1613
|
-
for (const param of
|
|
1895
|
+
for (const param of parametersOf(method)) {
|
|
1614
1896
|
const paramName = paramNameOf(param);
|
|
1615
1897
|
if (paramName !== null) {
|
|
1616
|
-
declared.add(paramName.text);
|
|
1617
1898
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
1618
1899
|
add(emitter, "paramOf", funcKey, String(position), paramKey);
|
|
1619
1900
|
add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
|
|
1620
1901
|
}
|
|
1621
1902
|
position += 1;
|
|
1622
1903
|
}
|
|
1623
|
-
const inside = {
|
|
1624
|
-
...emitter,
|
|
1625
|
-
enclosing: { funcKey, params: declared }
|
|
1626
|
-
};
|
|
1904
|
+
const inside = { ...emitter, enclosing: method };
|
|
1627
1905
|
const body = field(method, "body");
|
|
1628
1906
|
if (body === null) {
|
|
1629
1907
|
return funcKey;
|
|
@@ -1655,31 +1933,78 @@ function emitMethodFacts(emitter, method) {
|
|
|
1655
1933
|
add(inside, "returnsValue", funcKey, valueKey(inside, implicit));
|
|
1656
1934
|
}
|
|
1657
1935
|
emitExpressionFacts(inside, body);
|
|
1936
|
+
emitScopeWrites(inside, method, body);
|
|
1658
1937
|
return funcKey;
|
|
1659
1938
|
}
|
|
1660
|
-
function
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1939
|
+
function isConstruction(value) {
|
|
1940
|
+
return value.type === "call" || ARRAY_TYPES.has(value.type) || value.type === "hash";
|
|
1941
|
+
}
|
|
1942
|
+
function sourceOf(node) {
|
|
1943
|
+
return node.text.replace(/\s+/g, " ").trim();
|
|
1944
|
+
}
|
|
1945
|
+
function readFirst(node) {
|
|
1946
|
+
const inner = readThrough(node);
|
|
1947
|
+
if (inner !== node) {
|
|
1948
|
+
return inner;
|
|
1665
1949
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1950
|
+
return node.type === "call" ? field(node, "receiver") : null;
|
|
1951
|
+
}
|
|
1952
|
+
var CHAIN_READS = {
|
|
1953
|
+
nameTypes: RUBY_NAME_TYPES,
|
|
1954
|
+
readFirst
|
|
1955
|
+
};
|
|
1956
|
+
function describeWrite(emitter, write) {
|
|
1957
|
+
const value = write.value === null ? null : readThrough(write.value);
|
|
1958
|
+
return {
|
|
1959
|
+
value: value === null ? null : valueKey(emitter, value),
|
|
1960
|
+
placeholder: value?.type === "nil",
|
|
1961
|
+
construction: value !== null && isConstruction(value) ? sourceOf(value) : null,
|
|
1962
|
+
narrowsName: value !== null && value.type === "call" && (0, import_resolution3.startsAtName)(value, write.name, CHAIN_READS)
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
function settledValue(emitter, group) {
|
|
1966
|
+
const only = group.writes.length === 1 ? group.writes[0] : void 0;
|
|
1967
|
+
if (only !== void 0) {
|
|
1968
|
+
return only.fromParameter || only.value === null ? null : valueKey(emitter, only.value);
|
|
1668
1969
|
}
|
|
1669
|
-
|
|
1670
|
-
emitter,
|
|
1671
|
-
|
|
1672
|
-
nameId(emitter.filePath, left.text),
|
|
1673
|
-
valueKey(emitter, right)
|
|
1674
|
-
);
|
|
1675
|
-
add(
|
|
1676
|
-
emitter,
|
|
1677
|
-
"exportsAs",
|
|
1678
|
-
emitter.filePath,
|
|
1679
|
-
left.text,
|
|
1680
|
-
valueKey(emitter, right)
|
|
1970
|
+
return (0, import_resolution3.valueLeftByWrites)(
|
|
1971
|
+
group.writes.map((write) => describeWrite(emitter, write)),
|
|
1972
|
+
group.ordered
|
|
1681
1973
|
);
|
|
1682
1974
|
}
|
|
1975
|
+
function emitScopeWrites(emitter, method, body) {
|
|
1976
|
+
for (const group of collectWrites(method, body)) {
|
|
1977
|
+
const settled = settledValue(emitter, group);
|
|
1978
|
+
const key = group.owner === null ? nameId(emitter.filePath, group.name) : `${nodeId(emitter.filePath, group.owner)}#${group.name}`;
|
|
1979
|
+
if (settled === null) {
|
|
1980
|
+
emitCandidates(emitter, key, group);
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
add(
|
|
1984
|
+
emitter,
|
|
1985
|
+
group.writes.length === 1 ? "binds" : "endsHolding",
|
|
1986
|
+
key,
|
|
1987
|
+
settled
|
|
1988
|
+
);
|
|
1989
|
+
if (group.owner === null) {
|
|
1990
|
+
add(emitter, "exportsAs", emitter.filePath, group.name, settled);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
function emitCandidates(emitter, key, group) {
|
|
1995
|
+
for (const write of group.writes) {
|
|
1996
|
+
if (write.value === null) {
|
|
1997
|
+
add(emitter, "writesUnstated", key);
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
if (write.fromParameter) {
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
if (!describeWrite(emitter, write).narrowsName) {
|
|
2004
|
+
add(emitter, "mayHold", key, valueKey(emitter, write.value));
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1683
2008
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1684
2009
|
function emitClassFacts(emitter, cls) {
|
|
1685
2010
|
const classKey = nodeId(emitter.filePath, cls);
|
|
@@ -1691,6 +2016,12 @@ function emitClassFacts(emitter, cls) {
|
|
|
1691
2016
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1692
2017
|
}
|
|
1693
2018
|
const body = field(cls, "body");
|
|
2019
|
+
const collected = /* @__PURE__ */ new Map();
|
|
2020
|
+
const within = {
|
|
2021
|
+
...emitter,
|
|
2022
|
+
selfKey: classKey,
|
|
2023
|
+
instanceWrites: collected
|
|
2024
|
+
};
|
|
1694
2025
|
for (const statement of body === null ? [] : children2(body)) {
|
|
1695
2026
|
if (statement.type === "assignment") {
|
|
1696
2027
|
const left = field(statement, "left");
|
|
@@ -1714,16 +2045,23 @@ function emitClassFacts(emitter, cls) {
|
|
|
1714
2045
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
1715
2046
|
continue;
|
|
1716
2047
|
}
|
|
1717
|
-
const funcKey = emitMethodFacts(
|
|
2048
|
+
const funcKey = emitMethodFacts(within, statement);
|
|
1718
2049
|
const name = field(statement, "name");
|
|
1719
2050
|
if (name !== null) {
|
|
1720
2051
|
add(emitter, "holdsProperty", classKey, name.text, funcKey);
|
|
1721
2052
|
}
|
|
1722
2053
|
}
|
|
2054
|
+
emitInstanceWrites(within, classKey, collected);
|
|
1723
2055
|
return classKey;
|
|
1724
2056
|
}
|
|
1725
2057
|
function emitValueFacts(db, filePath, root) {
|
|
1726
|
-
const emitter = {
|
|
2058
|
+
const emitter = {
|
|
2059
|
+
db,
|
|
2060
|
+
filePath,
|
|
2061
|
+
enclosing: null,
|
|
2062
|
+
selfKey: null,
|
|
2063
|
+
instanceWrites: null
|
|
2064
|
+
};
|
|
1727
2065
|
const declaresName = (child, key) => {
|
|
1728
2066
|
const name = field(child, "name");
|
|
1729
2067
|
if (name === null) {
|
|
@@ -1741,14 +2079,12 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1741
2079
|
if (METHOD_TYPES.has(child.type)) {
|
|
1742
2080
|
declaresName(child, emitMethodFacts(emitter, child));
|
|
1743
2081
|
}
|
|
1744
|
-
if (child.type === "assignment") {
|
|
1745
|
-
emitAssignment(emitter, child);
|
|
1746
|
-
}
|
|
1747
2082
|
walk(child);
|
|
1748
2083
|
}
|
|
1749
2084
|
};
|
|
1750
2085
|
walk(root);
|
|
1751
2086
|
emitExpressionFacts(emitter, root);
|
|
2087
|
+
emitScopeWrites(emitter, null, root);
|
|
1752
2088
|
}
|
|
1753
2089
|
|
|
1754
2090
|
// src/values/lowering.ts
|
|
@@ -1764,7 +2100,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1764
2100
|
"module",
|
|
1765
2101
|
"singleton_class"
|
|
1766
2102
|
]);
|
|
1767
|
-
var
|
|
2103
|
+
var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1768
2104
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1769
2105
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1770
2106
|
"identifier",
|
|
@@ -2270,7 +2606,7 @@ function isTailExpression(node) {
|
|
|
2270
2606
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
2271
2607
|
return true;
|
|
2272
2608
|
}
|
|
2273
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
2609
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES3.has(owner.type)) {
|
|
2274
2610
|
return true;
|
|
2275
2611
|
}
|
|
2276
2612
|
if (owner.type === "when") {
|
|
@@ -2289,7 +2625,7 @@ function isRoot(node) {
|
|
|
2289
2625
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
2290
2626
|
return true;
|
|
2291
2627
|
}
|
|
2292
|
-
return
|
|
2628
|
+
return BLOCK_TYPES3.has(node.type) && node.parent?.type === "call";
|
|
2293
2629
|
}
|
|
2294
2630
|
function isPathStatement(node, parent) {
|
|
2295
2631
|
if (node.type === "elsif") {
|
|
@@ -2317,7 +2653,7 @@ function siteOf(node) {
|
|
|
2317
2653
|
current = parent;
|
|
2318
2654
|
}
|
|
2319
2655
|
}
|
|
2320
|
-
function
|
|
2656
|
+
function parametersOf2(parameters) {
|
|
2321
2657
|
if (parameters === null) {
|
|
2322
2658
|
return [];
|
|
2323
2659
|
}
|
|
@@ -2339,19 +2675,19 @@ function functionOf(node) {
|
|
|
2339
2675
|
return null;
|
|
2340
2676
|
}
|
|
2341
2677
|
return {
|
|
2342
|
-
parameters:
|
|
2678
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2343
2679
|
body: bodyOf(block)
|
|
2344
2680
|
};
|
|
2345
2681
|
}
|
|
2346
|
-
if (
|
|
2682
|
+
if (BLOCK_TYPES3.has(node.type)) {
|
|
2347
2683
|
return {
|
|
2348
|
-
parameters:
|
|
2684
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2349
2685
|
body: bodyOf(node)
|
|
2350
2686
|
};
|
|
2351
2687
|
}
|
|
2352
2688
|
if (METHOD_TYPES2.has(node.type)) {
|
|
2353
2689
|
const body = field(node, "body");
|
|
2354
|
-
const parameters =
|
|
2690
|
+
const parameters = parametersOf2(field(node, "parameters"));
|
|
2355
2691
|
if (body === null) {
|
|
2356
2692
|
return { parameters, body: [] };
|
|
2357
2693
|
}
|
|
@@ -2763,7 +3099,7 @@ function contextOver(db, nodes) {
|
|
|
2763
3099
|
db,
|
|
2764
3100
|
readKey(file, node, enclosingMethod(node))
|
|
2765
3101
|
);
|
|
2766
|
-
return answer === null ? null : (0,
|
|
3102
|
+
return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
|
|
2767
3103
|
},
|
|
2768
3104
|
callable: (call) => {
|
|
2769
3105
|
const file = fileOf(call);
|
|
@@ -3546,9 +3882,8 @@ function namesUnder(node) {
|
|
|
3546
3882
|
}
|
|
3547
3883
|
|
|
3548
3884
|
// src/storage.ts
|
|
3549
|
-
var import_datalog2 = require("@suss/datalog");
|
|
3550
3885
|
var import_ir_core = require("@suss/ir-core");
|
|
3551
|
-
var
|
|
3886
|
+
var import_resolution5 = require("@suss/resolution");
|
|
3552
3887
|
function children3(node) {
|
|
3553
3888
|
return node.namedChildren.filter((child) => child !== null);
|
|
3554
3889
|
}
|
|
@@ -3574,13 +3909,8 @@ function isConstant(node) {
|
|
|
3574
3909
|
function constantName(constant2) {
|
|
3575
3910
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3576
3911
|
}
|
|
3577
|
-
var ANCESTRY_PROGRAM = (0, import_datalog2.deriveOnDemand)(
|
|
3578
|
-
[...import_resolution3.RESOLUTION_RULES, ...import_resolution3.RESOLUTION_QUESTIONS],
|
|
3579
|
-
import_resolution3.ANSWER_RELATIONS
|
|
3580
|
-
);
|
|
3581
3912
|
function reachesBase(facts, classKey, bases) {
|
|
3582
|
-
|
|
3583
|
-
(0, import_datalog2.evaluate)(facts, ANCESTRY_PROGRAM.rules);
|
|
3913
|
+
(0, import_resolution5.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
3584
3914
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3585
3915
|
}
|
|
3586
3916
|
function classBehind(facts, file, constant2) {
|
|
@@ -4518,7 +4848,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4518
4848
|
if (written !== null) {
|
|
4519
4849
|
definitions.push({
|
|
4520
4850
|
qualifiedName: qualify(nesting, written),
|
|
4521
|
-
key: nodeId(filePath, node)
|
|
4851
|
+
key: nodeId(filePath, node),
|
|
4852
|
+
kind: "declaration"
|
|
4522
4853
|
});
|
|
4523
4854
|
const inside = [...nesting, ...written.split("::")];
|
|
4524
4855
|
for (const child of children4(node)) {
|
|
@@ -4533,7 +4864,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4533
4864
|
if (left !== null && left.type === "constant" && right !== null) {
|
|
4534
4865
|
definitions.push({
|
|
4535
4866
|
qualifiedName: qualify(nesting, left.text),
|
|
4536
|
-
key: nodeId(filePath, right)
|
|
4867
|
+
key: nodeId(filePath, right),
|
|
4868
|
+
kind: "assignment"
|
|
4537
4869
|
});
|
|
4538
4870
|
}
|
|
4539
4871
|
}
|
|
@@ -4560,14 +4892,18 @@ function emitConstantBindings(db, perFile) {
|
|
|
4560
4892
|
for (const file of files) {
|
|
4561
4893
|
for (const definition of file.definitions) {
|
|
4562
4894
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
4563
|
-
found.push(definition
|
|
4895
|
+
found.push(definition);
|
|
4564
4896
|
byName.set(definition.qualifiedName, found);
|
|
4565
4897
|
fileOfDefinition.set(definition.key, file.filePath);
|
|
4566
4898
|
}
|
|
4567
4899
|
}
|
|
4568
4900
|
const settles = (candidate) => {
|
|
4569
|
-
const found = byName.get(candidate);
|
|
4570
|
-
|
|
4901
|
+
const found = byName.get(candidate) ?? [];
|
|
4902
|
+
const declared = found.filter((one) => one.kind === "declaration");
|
|
4903
|
+
if (declared.length > 0) {
|
|
4904
|
+
return declared[0].key;
|
|
4905
|
+
}
|
|
4906
|
+
return found.length === 1 ? found[0].key : null;
|
|
4571
4907
|
};
|
|
4572
4908
|
for (const file of files) {
|
|
4573
4909
|
for (const reference of file.references) {
|
|
@@ -4593,8 +4929,8 @@ function emitConstantBindings(db, perFile) {
|
|
|
4593
4929
|
// src/facts/why.ts
|
|
4594
4930
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
4595
4931
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
4596
|
-
var
|
|
4597
|
-
var
|
|
4932
|
+
var import_datalog4 = require("@suss/datalog");
|
|
4933
|
+
var import_resolution7 = require("@suss/resolution");
|
|
4598
4934
|
|
|
4599
4935
|
// src/parser.ts
|
|
4600
4936
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -4656,7 +4992,7 @@ function parseWith(language, source) {
|
|
|
4656
4992
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
4657
4993
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
4658
4994
|
var import_behavioral_ir5 = require("@suss/behavioral-ir");
|
|
4659
|
-
var
|
|
4995
|
+
var import_datalog3 = require("@suss/datalog");
|
|
4660
4996
|
var import_extractor10 = require("@suss/extractor");
|
|
4661
4997
|
|
|
4662
4998
|
// src/diagnostics.ts
|
|
@@ -4711,34 +5047,118 @@ function requiredRelativePath(node) {
|
|
|
4711
5047
|
|
|
4712
5048
|
// src/reach/closure.ts
|
|
4713
5049
|
var import_behavioral_ir4 = require("@suss/behavioral-ir");
|
|
4714
|
-
var
|
|
5050
|
+
var import_datalog2 = require("@suss/datalog");
|
|
4715
5051
|
var import_extractor8 = require("@suss/extractor");
|
|
4716
5052
|
|
|
4717
5053
|
// src/reach/resolveCallee.ts
|
|
5054
|
+
var import_resolution6 = require("@suss/resolution");
|
|
4718
5055
|
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
5056
|
+
var INVOKES_RECEIVER = "call";
|
|
5057
|
+
var STOP_FOR = {
|
|
5058
|
+
severalSources: "multipleSources",
|
|
5059
|
+
outsideRun: "outsideRun",
|
|
5060
|
+
unsettled: "unsettledValue",
|
|
5061
|
+
undeclared: "noDeclaration"
|
|
5062
|
+
};
|
|
4719
5063
|
var stop = (reason) => ({
|
|
4720
5064
|
kind: "stopped",
|
|
4721
5065
|
reason
|
|
4722
5066
|
});
|
|
5067
|
+
var NO_DECLARATION = stop("noDeclaration");
|
|
4723
5068
|
function followed(target) {
|
|
4724
5069
|
return { kind: "followed", target };
|
|
4725
5070
|
}
|
|
4726
|
-
|
|
5071
|
+
function calleeSpellings(calls, ctx) {
|
|
5072
|
+
const spellingOf = /* @__PURE__ */ new Map();
|
|
5073
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5074
|
+
for (const { call, site } of calls) {
|
|
5075
|
+
const spelling = spellingFor(call, site);
|
|
5076
|
+
spellingOf.set(call.id, spelling);
|
|
5077
|
+
if (spelling.kind === "receiver") {
|
|
5078
|
+
keys.add(spelling.key);
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
return {
|
|
5082
|
+
spellingOf,
|
|
5083
|
+
outcomes: (0, import_resolution6.calleeOutcomes)(ctx.facts, [...keys], RUBY_PROGRAM)
|
|
5084
|
+
};
|
|
5085
|
+
}
|
|
5086
|
+
async function resolveCallee(call, site, ctx, read) {
|
|
5087
|
+
const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
|
|
5088
|
+
if (spelling.kind === "stopped") {
|
|
5089
|
+
return spelling;
|
|
5090
|
+
}
|
|
5091
|
+
if (spelling.kind === "implicitSelf") {
|
|
5092
|
+
return resolveImplicitSelf(spelling.name, site, ctx);
|
|
5093
|
+
}
|
|
5094
|
+
const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution6.calleeOutcomeOf)(ctx.facts, spelling.key, RUBY_PROGRAM);
|
|
5095
|
+
return asCallee(spelling, outcome, site, ctx);
|
|
5096
|
+
}
|
|
5097
|
+
function spellingFor(call, site) {
|
|
4727
5098
|
const methodName = calleeMethodName(call);
|
|
4728
5099
|
if (methodName === void 0) {
|
|
4729
|
-
return
|
|
5100
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
4730
5101
|
}
|
|
4731
5102
|
if (DYNAMIC_SEND_NAMES.has(methodName)) {
|
|
4732
|
-
return
|
|
5103
|
+
return { kind: "stopped", reason: "unsettledValue" };
|
|
4733
5104
|
}
|
|
4734
5105
|
const receiver = field(call, "receiver");
|
|
4735
5106
|
if (receiver === null || receiver.type === "self") {
|
|
4736
|
-
return
|
|
5107
|
+
return { kind: "implicitSelf", name: methodName };
|
|
5108
|
+
}
|
|
5109
|
+
return {
|
|
5110
|
+
kind: "receiver",
|
|
5111
|
+
key: readKey(site.file, receiver, site.method),
|
|
5112
|
+
method: methodName,
|
|
5113
|
+
onClassItself: receiver.type === "constant" || receiver.type === "scope_resolution"
|
|
5114
|
+
};
|
|
5115
|
+
}
|
|
5116
|
+
async function asCallee(spelling, outcome, site, ctx) {
|
|
5117
|
+
if (outcome.kind === "severalSources") {
|
|
5118
|
+
return stop("multipleSources");
|
|
5119
|
+
}
|
|
5120
|
+
if (outcome.kind === "callerSupplied") {
|
|
5121
|
+
return fromCaller(spelling, outcome.key, site);
|
|
5122
|
+
}
|
|
5123
|
+
const objects = objectsBehind(ctx.facts, spelling.key);
|
|
5124
|
+
if (objects.length > 1) {
|
|
5125
|
+
return stop("multipleSources");
|
|
4737
5126
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
5127
|
+
const objectKey = objects[0] ?? (outcome.kind === "object" ? outcome.key : void 0);
|
|
5128
|
+
if (objectKey !== void 0) {
|
|
5129
|
+
return methodOnObject(spelling, objectKey, ctx);
|
|
4740
5130
|
}
|
|
4741
|
-
|
|
5131
|
+
if (outcome.kind === "function") {
|
|
5132
|
+
return spelling.method === INVOKES_RECEIVER ? functionCallee(outcome.key, ctx) : NO_DECLARATION;
|
|
5133
|
+
}
|
|
5134
|
+
return stop(STOP_FOR[outcome.kind] ?? "noDeclaration");
|
|
5135
|
+
}
|
|
5136
|
+
function objectsBehind(facts, key) {
|
|
5137
|
+
return [
|
|
5138
|
+
...new Set(
|
|
5139
|
+
facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
|
|
5140
|
+
)
|
|
5141
|
+
];
|
|
5142
|
+
}
|
|
5143
|
+
async function methodOnObject(spelling, objectKey, ctx) {
|
|
5144
|
+
const qualifiedName = ctx.classNames.get(objectKey);
|
|
5145
|
+
if (qualifiedName === void 0) {
|
|
5146
|
+
return NO_DECLARATION;
|
|
5147
|
+
}
|
|
5148
|
+
if (!spelling.onClassItself) {
|
|
5149
|
+
return methodOnAncestryOf(qualifiedName, spelling.method, ctx);
|
|
5150
|
+
}
|
|
5151
|
+
return spelling.method === "new" ? methodOnAncestryOf(qualifiedName, "initialize", ctx) : singletonMethodOn(qualifiedName, spelling.method, ctx);
|
|
5152
|
+
}
|
|
5153
|
+
function fromCaller(spelling, parameterKey, site) {
|
|
5154
|
+
if (!parameterKey.startsWith(`${site.owner}#`) || spelling.method !== INVOKES_RECEIVER) {
|
|
5155
|
+
return stop("unsettledValue");
|
|
5156
|
+
}
|
|
5157
|
+
return stop("callerSupplied");
|
|
5158
|
+
}
|
|
5159
|
+
function functionCallee(key, ctx) {
|
|
5160
|
+
const target = ctx.definitions.get(key);
|
|
5161
|
+
return target === void 0 ? NO_DECLARATION : followed(target);
|
|
4742
5162
|
}
|
|
4743
5163
|
async function resolveMethodReference(name, site, ctx) {
|
|
4744
5164
|
const resolved = await resolveImplicitSelf(name, site, ctx);
|
|
@@ -4763,58 +5183,13 @@ async function resolveImplicitSelf(methodName, site, ctx) {
|
|
|
4763
5183
|
function resolveTopLevelName(methodName, ctx) {
|
|
4764
5184
|
const candidates = ctx.topLevelMethods.get(methodName);
|
|
4765
5185
|
if (candidates === void 0 || candidates.length === 0) {
|
|
4766
|
-
return
|
|
5186
|
+
return NO_DECLARATION;
|
|
4767
5187
|
}
|
|
4768
5188
|
if (candidates.length > 1) {
|
|
4769
5189
|
return stop("multipleSources");
|
|
4770
5190
|
}
|
|
4771
5191
|
return followed(candidates[0]);
|
|
4772
5192
|
}
|
|
4773
|
-
async function resolveOnReceiver(receiver, methodName, site, ctx) {
|
|
4774
|
-
if (receiver.type === "call") {
|
|
4775
|
-
const innerName = calleeMethodName(receiver);
|
|
4776
|
-
const innerReceiver = field(receiver, "receiver");
|
|
4777
|
-
if (innerName === "new" && innerReceiver !== null) {
|
|
4778
|
-
const classRef = classRefOf(innerReceiver, site, ctx);
|
|
4779
|
-
return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
|
|
4780
|
-
}
|
|
4781
|
-
const root = rootConstantOf(receiver);
|
|
4782
|
-
if (root === null) {
|
|
4783
|
-
return stop("unsettledValue");
|
|
4784
|
-
}
|
|
4785
|
-
return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
|
|
4786
|
-
}
|
|
4787
|
-
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
4788
|
-
const classRef = classRefOf(receiver, site, ctx);
|
|
4789
|
-
return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
|
|
4790
|
-
}
|
|
4791
|
-
return stop("unsettledValue");
|
|
4792
|
-
}
|
|
4793
|
-
function rootConstantOf(node) {
|
|
4794
|
-
if (node.type === "constant" || node.type === "scope_resolution") {
|
|
4795
|
-
return node;
|
|
4796
|
-
}
|
|
4797
|
-
if (node.type !== "call") {
|
|
4798
|
-
return null;
|
|
4799
|
-
}
|
|
4800
|
-
const receiver = field(node, "receiver");
|
|
4801
|
-
return receiver === null ? null : rootConstantOf(receiver);
|
|
4802
|
-
}
|
|
4803
|
-
function classRefOf(node, site, ctx) {
|
|
4804
|
-
if (node.type === "constant") {
|
|
4805
|
-
const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
|
|
4806
|
-
if (shadow !== null) {
|
|
4807
|
-
return shadow;
|
|
4808
|
-
}
|
|
4809
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4810
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4811
|
-
}
|
|
4812
|
-
if (node.type === "scope_resolution") {
|
|
4813
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4814
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4815
|
-
}
|
|
4816
|
-
return null;
|
|
4817
|
-
}
|
|
4818
5193
|
async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
4819
5194
|
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
4820
5195
|
if (ownBlocks === null) {
|
|
@@ -4830,7 +5205,7 @@ async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
|
4830
5205
|
found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
|
|
4831
5206
|
);
|
|
4832
5207
|
}
|
|
4833
|
-
return
|
|
5208
|
+
return NO_DECLARATION;
|
|
4834
5209
|
}
|
|
4835
5210
|
function reachedMethod(node, block, name) {
|
|
4836
5211
|
return {
|
|
@@ -4855,21 +5230,21 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
|
4855
5230
|
return followed(reachedMethod(found, block, methodName));
|
|
4856
5231
|
}
|
|
4857
5232
|
}
|
|
4858
|
-
return
|
|
5233
|
+
return NO_DECLARATION;
|
|
4859
5234
|
}
|
|
4860
5235
|
|
|
4861
5236
|
// src/reach/closure.ts
|
|
4862
5237
|
var REACHABLE_RULES = [
|
|
4863
|
-
(0,
|
|
4864
|
-
(0,
|
|
5238
|
+
(0, import_datalog2.rule)("reachable", [(0, import_datalog2.variable)("f")], [(0, import_datalog2.lit)("entry", (0, import_datalog2.variable)("f"))]),
|
|
5239
|
+
(0, import_datalog2.rule)(
|
|
4865
5240
|
"reachable",
|
|
4866
|
-
[(0,
|
|
4867
|
-
[(0,
|
|
5241
|
+
[(0, import_datalog2.variable)("g")],
|
|
5242
|
+
[(0, import_datalog2.lit)("reachable", (0, import_datalog2.variable)("f")), (0, import_datalog2.lit)("calls", (0, import_datalog2.variable)("f"), (0, import_datalog2.variable)("g"))]
|
|
4868
5243
|
)
|
|
4869
5244
|
];
|
|
4870
5245
|
async function reachedFunctions(seeds, options) {
|
|
4871
|
-
const ctx = buildReachContext(options.files);
|
|
4872
|
-
const db = new
|
|
5246
|
+
const ctx = buildReachContext(options.files, options.facts);
|
|
5247
|
+
const db = new import_datalog2.Database();
|
|
4873
5248
|
const functionByKey = /* @__PURE__ */ new Map();
|
|
4874
5249
|
const seedKeys = /* @__PURE__ */ new Set();
|
|
4875
5250
|
const scanned = /* @__PURE__ */ new Set();
|
|
@@ -4890,18 +5265,26 @@ async function reachedFunctions(seeds, options) {
|
|
|
4890
5265
|
db.add("entry", [seed.key]);
|
|
4891
5266
|
}
|
|
4892
5267
|
for (; ; ) {
|
|
4893
|
-
(0,
|
|
5268
|
+
(0, import_datalog2.evaluate)(db, REACHABLE_RULES);
|
|
4894
5269
|
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
4895
5270
|
if (frontier.length === 0) {
|
|
4896
5271
|
break;
|
|
4897
5272
|
}
|
|
4898
|
-
|
|
5273
|
+
const bodies = frontier.flatMap((key) => {
|
|
4899
5274
|
scanned.add(key);
|
|
4900
5275
|
const source = functionByKey.get(key);
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5276
|
+
return source === void 0 ? [] : [{ key, source, ...bodyOf2(source, options) }];
|
|
5277
|
+
});
|
|
5278
|
+
const spellings = calleeSpellings(
|
|
5279
|
+
bodies.flatMap((body) => body.written),
|
|
5280
|
+
ctx
|
|
5281
|
+
);
|
|
5282
|
+
for (const { key, source, calls, site } of bodies) {
|
|
5283
|
+
const scan = await scanBody(source, ctx, options, {
|
|
5284
|
+
calls,
|
|
5285
|
+
site,
|
|
5286
|
+
spellings
|
|
5287
|
+
});
|
|
4905
5288
|
if (scan.stops.length > 0) {
|
|
4906
5289
|
stopsByKey.set(key, scan.stops);
|
|
4907
5290
|
}
|
|
@@ -4954,11 +5337,22 @@ async function reachedFunctions(seeds, options) {
|
|
|
4954
5337
|
function keyOf(target) {
|
|
4955
5338
|
return nodeId(target.file, target.node);
|
|
4956
5339
|
}
|
|
4957
|
-
|
|
5340
|
+
var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5341
|
+
function buildReachContext(files, facts) {
|
|
4958
5342
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
4959
5343
|
const classes = [];
|
|
5344
|
+
const classNames = /* @__PURE__ */ new Map();
|
|
5345
|
+
const classOfMethod = /* @__PURE__ */ new Map();
|
|
4960
5346
|
for (const { file, root } of files) {
|
|
4961
|
-
walkDefinitions(root, (info) =>
|
|
5347
|
+
walkDefinitions(root, (info) => {
|
|
5348
|
+
classes.push({ file, info });
|
|
5349
|
+
classNames.set(nodeId(file, info.node), info.qualifiedName);
|
|
5350
|
+
for (const statement of namedChildren(info.bodyNode)) {
|
|
5351
|
+
if (METHOD_TYPES4.has(statement.type)) {
|
|
5352
|
+
classOfMethod.set(nodeId(file, statement), info.qualifiedName);
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
});
|
|
4962
5356
|
}
|
|
4963
5357
|
const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
|
|
4964
5358
|
for (const { file, info } of classes) {
|
|
@@ -4966,22 +5360,33 @@ function buildReachContext(files) {
|
|
|
4966
5360
|
list.push({ info, knownClasses, file });
|
|
4967
5361
|
blocksByQualifiedName.set(info.qualifiedName, list);
|
|
4968
5362
|
}
|
|
4969
|
-
const
|
|
5363
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4970
5364
|
for (const { file, root } of files) {
|
|
4971
|
-
for (const method of
|
|
5365
|
+
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
4972
5366
|
const name = field(method, "name")?.text;
|
|
4973
5367
|
if (name === void 0) {
|
|
4974
5368
|
continue;
|
|
4975
5369
|
}
|
|
4976
|
-
const
|
|
4977
|
-
|
|
5370
|
+
const owner = classOfMethod.get(key) ?? null;
|
|
5371
|
+
definitions.set(key, {
|
|
4978
5372
|
file,
|
|
4979
5373
|
node: method,
|
|
4980
5374
|
name,
|
|
4981
|
-
exportPath: [name],
|
|
4982
|
-
enclosingQualifiedName:
|
|
5375
|
+
exportPath: owner === null ? [name] : [owner, name],
|
|
5376
|
+
enclosingQualifiedName: owner
|
|
4983
5377
|
});
|
|
4984
|
-
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
const topLevelMethods = /* @__PURE__ */ new Map();
|
|
5381
|
+
for (const { file, root } of files) {
|
|
5382
|
+
for (const method of topLevelMethodNodes(root)) {
|
|
5383
|
+
const found = definitions.get(nodeId(file, method));
|
|
5384
|
+
if (found === void 0) {
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
const list = topLevelMethods.get(found.name) ?? [];
|
|
5388
|
+
list.push(found);
|
|
5389
|
+
topLevelMethods.set(found.name, list);
|
|
4985
5390
|
}
|
|
4986
5391
|
}
|
|
4987
5392
|
const lookup = {
|
|
@@ -4994,7 +5399,13 @@ function buildReachContext(files) {
|
|
|
4994
5399
|
parsedFile: async () => null,
|
|
4995
5400
|
localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
|
|
4996
5401
|
};
|
|
4997
|
-
return { lookup,
|
|
5402
|
+
return { lookup, topLevelMethods, facts, classNames, definitions };
|
|
5403
|
+
}
|
|
5404
|
+
function namedChildren(node) {
|
|
5405
|
+
if (node === null) {
|
|
5406
|
+
return [];
|
|
5407
|
+
}
|
|
5408
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
4998
5409
|
}
|
|
4999
5410
|
function topLevelMethodNodes(root, found = []) {
|
|
5000
5411
|
for (const child of bodyStatements(root)) {
|
|
@@ -5009,14 +5420,6 @@ function topLevelMethodNodes(root, found = []) {
|
|
|
5009
5420
|
}
|
|
5010
5421
|
return found;
|
|
5011
5422
|
}
|
|
5012
|
-
function nestingOf(qualifiedName) {
|
|
5013
|
-
const parts = qualifiedName.split("::");
|
|
5014
|
-
const out = [];
|
|
5015
|
-
for (let depth = parts.length; depth >= 1; depth--) {
|
|
5016
|
-
out.push(parts.slice(0, depth).join("::"));
|
|
5017
|
-
}
|
|
5018
|
-
return out;
|
|
5019
|
-
}
|
|
5020
5423
|
var EMPTY_SCAN = {
|
|
5021
5424
|
followed: [],
|
|
5022
5425
|
stops: [],
|
|
@@ -5034,21 +5437,23 @@ function methodReferenceSymbol(node) {
|
|
|
5034
5437
|
const argChildren = args === null ? [] : bodyStatements(args);
|
|
5035
5438
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
5036
5439
|
}
|
|
5037
|
-
|
|
5440
|
+
function bodyOf2(source, options) {
|
|
5441
|
+
const site = {
|
|
5442
|
+
file: source.file,
|
|
5443
|
+
method: source.node,
|
|
5444
|
+
owner: keyOf(source),
|
|
5445
|
+
enclosingQualifiedName: source.enclosingQualifiedName
|
|
5446
|
+
};
|
|
5447
|
+
const calls = field(source.node, "body") === null ? [] : withoutChainLinks(bodyCalls(source.node, options.inheritedMethods));
|
|
5448
|
+
return { calls, site, written: calls.map((call) => ({ call, site })) };
|
|
5449
|
+
}
|
|
5450
|
+
async function scanBody(source, ctx, options, read) {
|
|
5038
5451
|
const displayPathOf = options.displayPathOf;
|
|
5039
|
-
|
|
5040
|
-
if (body === null) {
|
|
5452
|
+
if (field(source.node, "body") === null) {
|
|
5041
5453
|
return EMPTY_SCAN;
|
|
5042
5454
|
}
|
|
5043
|
-
const calls =
|
|
5044
|
-
bodyCalls(source.node, options.inheritedMethods)
|
|
5045
|
-
);
|
|
5455
|
+
const { calls, site } = read;
|
|
5046
5456
|
const ownParameters = positionalParameters(source.node).map((p) => p.name);
|
|
5047
|
-
const site = {
|
|
5048
|
-
enclosingQualifiedName: source.enclosingQualifiedName,
|
|
5049
|
-
nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
|
|
5050
|
-
ownParameters
|
|
5051
|
-
};
|
|
5052
5457
|
const followed2 = [];
|
|
5053
5458
|
const stops = [];
|
|
5054
5459
|
const placements = new import_behavioral_ir4.TargetPlacements();
|
|
@@ -5091,7 +5496,7 @@ async function scanBody(source, ctx, options) {
|
|
|
5091
5496
|
};
|
|
5092
5497
|
for (const call of calls) {
|
|
5093
5498
|
const callee = calleeText(call);
|
|
5094
|
-
const outcome = await resolveCallee(call, site, ctx);
|
|
5499
|
+
const outcome = await resolveCallee(call, site, ctx, read.spellings);
|
|
5095
5500
|
const placed = outcome.kind === "followed" ? {
|
|
5096
5501
|
file: displayPathOf(outcome.target.file),
|
|
5097
5502
|
span: spanOf(outcome.target.node)
|
|
@@ -5259,9 +5664,9 @@ async function extractRubyProject(options) {
|
|
|
5259
5664
|
options.onCacheDiagnostic?.(lookup.diagnostic);
|
|
5260
5665
|
if (lookup.kind === "hit") {
|
|
5261
5666
|
options.onTiming?.(timer.report());
|
|
5262
|
-
return { summaries: lookup.summaries, facts: new
|
|
5667
|
+
return { summaries: lookup.summaries, facts: new import_datalog3.Database() };
|
|
5263
5668
|
}
|
|
5264
|
-
const db = new
|
|
5669
|
+
const db = new import_datalog3.Database();
|
|
5265
5670
|
const summaries = [];
|
|
5266
5671
|
const tallies = (0, import_extractor6.createPackTallies)(options.packs);
|
|
5267
5672
|
const constants = [];
|
|
@@ -5390,6 +5795,7 @@ async function extractRubyProject(options) {
|
|
|
5390
5795
|
() => reachedFunctions(seeds, {
|
|
5391
5796
|
files: parsed,
|
|
5392
5797
|
displayPathOf,
|
|
5798
|
+
facts: db,
|
|
5393
5799
|
...storage === void 0 ? {} : { storage },
|
|
5394
5800
|
inheritedMethods
|
|
5395
5801
|
})
|
|
@@ -5486,15 +5892,15 @@ function findRubyFiles(root) {
|
|
|
5486
5892
|
}
|
|
5487
5893
|
|
|
5488
5894
|
// src/facts/why.ts
|
|
5489
|
-
var WITNESS_RULES = [...
|
|
5490
|
-
var
|
|
5895
|
+
var WITNESS_RULES = [...import_resolution7.RESOLUTION_RULES, ...RUBY_RULES];
|
|
5896
|
+
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5491
5897
|
function namedChildrenOf(node) {
|
|
5492
5898
|
return node.namedChildren.filter((child) => child !== null);
|
|
5493
5899
|
}
|
|
5494
5900
|
function enclosingMethod2(node) {
|
|
5495
5901
|
let current = node.parent;
|
|
5496
5902
|
while (current !== null) {
|
|
5497
|
-
if (
|
|
5903
|
+
if (METHOD_TYPES5.has(current.type)) {
|
|
5498
5904
|
return current;
|
|
5499
5905
|
}
|
|
5500
5906
|
current = current.parent;
|
|
@@ -5537,7 +5943,7 @@ function width(node) {
|
|
|
5537
5943
|
}
|
|
5538
5944
|
var RubyWhySession = class {
|
|
5539
5945
|
root;
|
|
5540
|
-
db = new
|
|
5946
|
+
db = new import_datalog4.Database();
|
|
5541
5947
|
locations = /* @__PURE__ */ new Map();
|
|
5542
5948
|
trees = /* @__PURE__ */ new Map();
|
|
5543
5949
|
constructor(options) {
|
|
@@ -5607,7 +6013,7 @@ var RubyWhySession = class {
|
|
|
5607
6013
|
* which the caller says in its own words.
|
|
5608
6014
|
*/
|
|
5609
6015
|
explain(value, options = {}) {
|
|
5610
|
-
return (0,
|
|
6016
|
+
return (0, import_resolution7.explainResolvedKey)({
|
|
5611
6017
|
db: this.db,
|
|
5612
6018
|
rules: WITNESS_RULES,
|
|
5613
6019
|
key: readKey(value.file, value.node, enclosingMethod2(value.node)),
|