@suss/adapter-ruby 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +778 -250
- package/dist/index.d.cts +46 -14
- package/dist/index.d.ts +46 -14
- package/dist/index.js +768 -253
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
inheritedStatements: () => inheritedStatements,
|
|
52
52
|
methodDefinitionsIn: () => methodDefinitionsIn,
|
|
53
53
|
methodInAncestry: () => methodInAncestry,
|
|
54
|
+
nestedStatements: () => nestedStatements,
|
|
54
55
|
nodeId: () => nodeId,
|
|
55
56
|
parseRuby: () => parseRuby,
|
|
56
57
|
parseRubySync: () => parseRubySync,
|
|
@@ -94,6 +95,45 @@ function field(node, name) {
|
|
|
94
95
|
function bodyStatements(body) {
|
|
95
96
|
return body.namedChildren.filter((child) => child !== null);
|
|
96
97
|
}
|
|
98
|
+
var BRANCH_FIELDS = {
|
|
99
|
+
if: ["consequence", "alternative"],
|
|
100
|
+
unless: ["consequence", "alternative"],
|
|
101
|
+
elsif: ["consequence", "alternative"],
|
|
102
|
+
if_modifier: ["body"],
|
|
103
|
+
unless_modifier: ["body"],
|
|
104
|
+
while: ["body"],
|
|
105
|
+
until: ["body"],
|
|
106
|
+
while_modifier: ["body"],
|
|
107
|
+
until_modifier: ["body"],
|
|
108
|
+
when: ["body"],
|
|
109
|
+
rescue: ["body"]
|
|
110
|
+
};
|
|
111
|
+
var STATEMENT_LISTS = /* @__PURE__ */ new Set(["then", "else", "do", "ensure", "begin"]);
|
|
112
|
+
var CLAUSE_TYPES = /* @__PURE__ */ new Set(["elsif", "else", "when", "rescue", "ensure"]);
|
|
113
|
+
function nestedStatements(stmt) {
|
|
114
|
+
const branchFields = BRANCH_FIELDS[stmt.type];
|
|
115
|
+
if (branchFields !== void 0) {
|
|
116
|
+
return branchFields.flatMap((name) => {
|
|
117
|
+
const branch = field(stmt, name);
|
|
118
|
+
return branch === null ? [] : statementsRunBy(branch);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (STATEMENT_LISTS.has(stmt.type)) {
|
|
122
|
+
return bodyStatements(stmt).flatMap(
|
|
123
|
+
(child) => CLAUSE_TYPES.has(child.type) ? nestedStatements(child) : [child]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (stmt.type === "case") {
|
|
127
|
+
return bodyStatements(stmt).filter((child) => CLAUSE_TYPES.has(child.type)).flatMap(nestedStatements);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
function statementsRunBy(branch) {
|
|
132
|
+
if (STATEMENT_LISTS.has(branch.type) || CLAUSE_TYPES.has(branch.type)) {
|
|
133
|
+
return nestedStatements(branch);
|
|
134
|
+
}
|
|
135
|
+
return [branch];
|
|
136
|
+
}
|
|
97
137
|
var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
98
138
|
"method",
|
|
99
139
|
"singleton_method",
|
|
@@ -157,8 +197,8 @@ var VISIBILITY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
157
197
|
"protected",
|
|
158
198
|
"public"
|
|
159
199
|
]);
|
|
160
|
-
function visibilityKeyword(
|
|
161
|
-
return VISIBILITY_KEYWORDS.has(
|
|
200
|
+
function visibilityKeyword(text2) {
|
|
201
|
+
return VISIBILITY_KEYWORDS.has(text2) ? text2 : null;
|
|
162
202
|
}
|
|
163
203
|
function setVisibility(visibility, name, keyword) {
|
|
164
204
|
if (keyword === "public") {
|
|
@@ -281,14 +321,31 @@ var NodeMap = class extends import_extractor.IdMap {
|
|
|
281
321
|
// src/constantPath.ts
|
|
282
322
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
283
323
|
var import_node_path = __toESM(require("path"), 1);
|
|
284
|
-
function underscoreConstantPath(qualifiedName) {
|
|
285
|
-
return qualifiedName.replaceAll("::", "/").replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
324
|
+
function underscoreConstantPath(qualifiedName, acronyms = []) {
|
|
325
|
+
return lowerAcronyms(qualifiedName.replaceAll("::", "/"), acronyms).replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
326
|
+
}
|
|
327
|
+
function lowerAcronyms(word, acronyms) {
|
|
328
|
+
if (acronyms.length === 0) {
|
|
329
|
+
return word;
|
|
330
|
+
}
|
|
331
|
+
const alternatives = acronyms.map(escapeRegExp).join("|");
|
|
332
|
+
const acronymRegex = new RegExp(
|
|
333
|
+
`(?:(?<=([A-Za-z\\d]))|\\b)(${alternatives})(?=\\b|[^a-z])`,
|
|
334
|
+
"g"
|
|
335
|
+
);
|
|
336
|
+
return word.replace(
|
|
337
|
+
acronymRegex,
|
|
338
|
+
(_match, before, acronym) => `${before === void 0 ? "" : "_"}${acronym.toLowerCase()}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
function escapeRegExp(text2) {
|
|
342
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
286
343
|
}
|
|
287
344
|
var PATH_CONVENTIONS = {
|
|
288
345
|
railsUnderscore: underscoreConstantPath
|
|
289
346
|
};
|
|
290
|
-
function resolveConstantFile(root, qualifiedName, convention) {
|
|
291
|
-
const relative = `${PATH_CONVENTIONS[convention](qualifiedName)}.rb`;
|
|
347
|
+
function resolveConstantFile(root, qualifiedName, convention, acronyms = []) {
|
|
348
|
+
const relative = `${PATH_CONVENTIONS[convention](qualifiedName, acronyms)}.rb`;
|
|
292
349
|
for (const candidate of [root, ...autoloadDirectories(root)]) {
|
|
293
350
|
const file = import_node_path.default.join(candidate, relative);
|
|
294
351
|
if (import_node_fs.default.existsSync(file)) {
|
|
@@ -362,12 +419,19 @@ function walkBody(statements, nesting, visit) {
|
|
|
362
419
|
}
|
|
363
420
|
}
|
|
364
421
|
function ownIdentity(nameNode, outerNesting) {
|
|
365
|
-
const qualifiedName =
|
|
422
|
+
const qualifiedName = definedName(nameNode, outerNesting);
|
|
366
423
|
if (qualifiedName === null) {
|
|
367
424
|
return null;
|
|
368
425
|
}
|
|
369
426
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
370
427
|
}
|
|
428
|
+
function definedName(nameNode, outerNesting) {
|
|
429
|
+
const innermost = outerNesting[0] ?? null;
|
|
430
|
+
if (nameNode.type === "scope_resolution" && innermost !== null && !nameNode.text.startsWith("::")) {
|
|
431
|
+
return `${innermost}::${nameNode.text}`;
|
|
432
|
+
}
|
|
433
|
+
return qualifyConstantRef(nameNode, outerNesting);
|
|
434
|
+
}
|
|
371
435
|
function visitClass(node, nesting, visit) {
|
|
372
436
|
const nameNode = field(node, "name");
|
|
373
437
|
const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
|
|
@@ -431,7 +495,8 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
431
495
|
const filePath = resolveConstantFile(
|
|
432
496
|
lookup.root,
|
|
433
497
|
qualifiedName,
|
|
434
|
-
lookup.pathConvention
|
|
498
|
+
lookup.pathConvention,
|
|
499
|
+
lookup.acronyms
|
|
435
500
|
);
|
|
436
501
|
if (filePath === null) {
|
|
437
502
|
return null;
|
|
@@ -473,7 +538,10 @@ async function chainOf(self, lookup, active) {
|
|
|
473
538
|
async function superclassChain(self, lookup, active) {
|
|
474
539
|
const candidates = superclassCandidatesOf(self.blocks);
|
|
475
540
|
for (const candidate of candidates) {
|
|
476
|
-
if (lookup.ancestryRootClassNames.includes(candidate)
|
|
541
|
+
if (lookup.ancestryRootClassNames.includes(candidate)) {
|
|
542
|
+
return [{ type: "root", name: candidate }];
|
|
543
|
+
}
|
|
544
|
+
if (active.has(candidate)) {
|
|
477
545
|
return [];
|
|
478
546
|
}
|
|
479
547
|
const blocks = await definitionOf(candidate, lookup);
|
|
@@ -551,6 +619,9 @@ function superclassCandidatesOf(blocks) {
|
|
|
551
619
|
}
|
|
552
620
|
function methodInAncestry(ancestry, name) {
|
|
553
621
|
for (const entry of ancestry) {
|
|
622
|
+
if (entry.type === "root") {
|
|
623
|
+
return { type: "none" };
|
|
624
|
+
}
|
|
554
625
|
if (entry.type === "unfollowed") {
|
|
555
626
|
return {
|
|
556
627
|
type: "unsettled",
|
|
@@ -1275,7 +1346,7 @@ function calledMethod(node) {
|
|
|
1275
1346
|
}
|
|
1276
1347
|
|
|
1277
1348
|
// src/values/evaluator.ts
|
|
1278
|
-
var
|
|
1349
|
+
var import_resolution4 = require("@suss/resolution");
|
|
1279
1350
|
var import_values2 = require("@suss/values");
|
|
1280
1351
|
|
|
1281
1352
|
// src/facts/resolve.ts
|
|
@@ -1295,18 +1366,9 @@ var RUBY_RULES = [
|
|
|
1295
1366
|
]
|
|
1296
1367
|
)
|
|
1297
1368
|
];
|
|
1298
|
-
var
|
|
1299
|
-
[...import_resolution.RESOLUTION_RULES, ...RUBY_RULES, ...import_resolution.RESOLUTION_QUESTIONS],
|
|
1300
|
-
import_resolution.ANSWER_RELATIONS
|
|
1301
|
-
);
|
|
1369
|
+
var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
|
|
1302
1370
|
function resolveValues(db, keys) {
|
|
1303
|
-
|
|
1304
|
-
return;
|
|
1305
|
-
}
|
|
1306
|
-
for (const key of keys) {
|
|
1307
|
-
db.add("wanted", [key]);
|
|
1308
|
-
}
|
|
1309
|
-
(0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
|
|
1371
|
+
(0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
|
|
1310
1372
|
}
|
|
1311
1373
|
function resolvedFunctions(db, key) {
|
|
1312
1374
|
return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
|
|
@@ -1317,27 +1379,262 @@ function writtenValueOf(db, key) {
|
|
|
1317
1379
|
}
|
|
1318
1380
|
|
|
1319
1381
|
// src/facts/values.ts
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
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
|
+
]);
|
|
1326
1401
|
function paramNameOf(param) {
|
|
1327
1402
|
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
1328
1403
|
}
|
|
1329
|
-
function
|
|
1330
|
-
const params = field(
|
|
1331
|
-
|
|
1332
|
-
|
|
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)) {
|
|
1411
|
+
const name = paramNameOf(param);
|
|
1412
|
+
if (name !== null) {
|
|
1413
|
+
names.add(name.text);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
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 [];
|
|
1426
|
+
}
|
|
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)) {
|
|
1333
1459
|
const name = paramNameOf(param);
|
|
1334
1460
|
if (name !== null) {
|
|
1335
|
-
|
|
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);
|
|
1336
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;
|
|
1337
1514
|
}
|
|
1338
|
-
|
|
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;
|
|
1339
1523
|
}
|
|
1340
|
-
function
|
|
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);
|
|
1341
1638
|
if (node.type !== "identifier" && node.type !== "constant") {
|
|
1342
1639
|
return nodeId(filePath, node);
|
|
1343
1640
|
}
|
|
@@ -1345,10 +1642,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1345
1642
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1346
1643
|
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1347
1644
|
}
|
|
1348
|
-
|
|
1349
|
-
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
1350
|
-
}
|
|
1351
|
-
return nameId(filePath, node.text);
|
|
1645
|
+
return nameKey(filePath, node, enclosing);
|
|
1352
1646
|
}
|
|
1353
1647
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
1354
1648
|
"string",
|
|
@@ -1377,15 +1671,12 @@ function children2(node) {
|
|
|
1377
1671
|
function add(emitter, relation, ...tuple) {
|
|
1378
1672
|
emitter.db.add(relation, tuple);
|
|
1379
1673
|
}
|
|
1380
|
-
function valueKey(emitter,
|
|
1674
|
+
function valueKey(emitter, written) {
|
|
1675
|
+
const value = readThrough(written);
|
|
1381
1676
|
if (value.type !== "identifier" && value.type !== "constant") {
|
|
1382
1677
|
return nodeId(emitter.filePath, value);
|
|
1383
1678
|
}
|
|
1384
|
-
|
|
1385
|
-
if (enclosing !== null && enclosing.params.has(value.text)) {
|
|
1386
|
-
return `${enclosing.funcKey}#${value.text}`;
|
|
1387
|
-
}
|
|
1388
|
-
return nameId(emitter.filePath, value.text);
|
|
1679
|
+
return nameKey(emitter.filePath, value, emitter.enclosing);
|
|
1389
1680
|
}
|
|
1390
1681
|
function pairKeyText(key) {
|
|
1391
1682
|
if (key.type === "hash_key_symbol") {
|
|
@@ -1524,8 +1815,72 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1524
1815
|
if (child.type === "nil") {
|
|
1525
1816
|
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
1526
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
|
+
}
|
|
1527
1827
|
});
|
|
1528
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
|
+
}
|
|
1529
1884
|
function implicitReturn(body) {
|
|
1530
1885
|
const statements = children2(body).filter(
|
|
1531
1886
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
@@ -1536,23 +1891,17 @@ function implicitReturn(body) {
|
|
|
1536
1891
|
function emitMethodFacts(emitter, method) {
|
|
1537
1892
|
const funcKey = nodeId(emitter.filePath, method);
|
|
1538
1893
|
add(emitter, "func", funcKey);
|
|
1539
|
-
const params = field(method, "parameters");
|
|
1540
|
-
const declared = /* @__PURE__ */ new Set();
|
|
1541
1894
|
let position = 0;
|
|
1542
|
-
for (const param of
|
|
1895
|
+
for (const param of parametersOf(method)) {
|
|
1543
1896
|
const paramName = paramNameOf(param);
|
|
1544
1897
|
if (paramName !== null) {
|
|
1545
|
-
declared.add(paramName.text);
|
|
1546
1898
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
1547
1899
|
add(emitter, "paramOf", funcKey, String(position), paramKey);
|
|
1548
1900
|
add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
|
|
1549
1901
|
}
|
|
1550
1902
|
position += 1;
|
|
1551
1903
|
}
|
|
1552
|
-
const inside = {
|
|
1553
|
-
...emitter,
|
|
1554
|
-
enclosing: { funcKey, params: declared }
|
|
1555
|
-
};
|
|
1904
|
+
const inside = { ...emitter, enclosing: method };
|
|
1556
1905
|
const body = field(method, "body");
|
|
1557
1906
|
if (body === null) {
|
|
1558
1907
|
return funcKey;
|
|
@@ -1584,31 +1933,78 @@ function emitMethodFacts(emitter, method) {
|
|
|
1584
1933
|
add(inside, "returnsValue", funcKey, valueKey(inside, implicit));
|
|
1585
1934
|
}
|
|
1586
1935
|
emitExpressionFacts(inside, body);
|
|
1936
|
+
emitScopeWrites(inside, method, body);
|
|
1587
1937
|
return funcKey;
|
|
1588
1938
|
}
|
|
1589
|
-
function
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
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;
|
|
1594
1949
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
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);
|
|
1597
1969
|
}
|
|
1598
|
-
|
|
1599
|
-
emitter,
|
|
1600
|
-
|
|
1601
|
-
nameId(emitter.filePath, left.text),
|
|
1602
|
-
valueKey(emitter, right)
|
|
1603
|
-
);
|
|
1604
|
-
add(
|
|
1605
|
-
emitter,
|
|
1606
|
-
"exportsAs",
|
|
1607
|
-
emitter.filePath,
|
|
1608
|
-
left.text,
|
|
1609
|
-
valueKey(emitter, right)
|
|
1970
|
+
return (0, import_resolution3.valueLeftByWrites)(
|
|
1971
|
+
group.writes.map((write) => describeWrite(emitter, write)),
|
|
1972
|
+
group.ordered
|
|
1610
1973
|
);
|
|
1611
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
|
+
}
|
|
1612
2008
|
var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1613
2009
|
function emitClassFacts(emitter, cls) {
|
|
1614
2010
|
const classKey = nodeId(emitter.filePath, cls);
|
|
@@ -1620,6 +2016,12 @@ function emitClassFacts(emitter, cls) {
|
|
|
1620
2016
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1621
2017
|
}
|
|
1622
2018
|
const body = field(cls, "body");
|
|
2019
|
+
const collected = /* @__PURE__ */ new Map();
|
|
2020
|
+
const within = {
|
|
2021
|
+
...emitter,
|
|
2022
|
+
selfKey: classKey,
|
|
2023
|
+
instanceWrites: collected
|
|
2024
|
+
};
|
|
1623
2025
|
for (const statement of body === null ? [] : children2(body)) {
|
|
1624
2026
|
if (statement.type === "assignment") {
|
|
1625
2027
|
const left = field(statement, "left");
|
|
@@ -1643,16 +2045,23 @@ function emitClassFacts(emitter, cls) {
|
|
|
1643
2045
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
1644
2046
|
continue;
|
|
1645
2047
|
}
|
|
1646
|
-
const funcKey = emitMethodFacts(
|
|
2048
|
+
const funcKey = emitMethodFacts(within, statement);
|
|
1647
2049
|
const name = field(statement, "name");
|
|
1648
2050
|
if (name !== null) {
|
|
1649
2051
|
add(emitter, "holdsProperty", classKey, name.text, funcKey);
|
|
1650
2052
|
}
|
|
1651
2053
|
}
|
|
2054
|
+
emitInstanceWrites(within, classKey, collected);
|
|
1652
2055
|
return classKey;
|
|
1653
2056
|
}
|
|
1654
2057
|
function emitValueFacts(db, filePath, root) {
|
|
1655
|
-
const emitter = {
|
|
2058
|
+
const emitter = {
|
|
2059
|
+
db,
|
|
2060
|
+
filePath,
|
|
2061
|
+
enclosing: null,
|
|
2062
|
+
selfKey: null,
|
|
2063
|
+
instanceWrites: null
|
|
2064
|
+
};
|
|
1656
2065
|
const declaresName = (child, key) => {
|
|
1657
2066
|
const name = field(child, "name");
|
|
1658
2067
|
if (name === null) {
|
|
@@ -1670,14 +2079,12 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1670
2079
|
if (METHOD_TYPES.has(child.type)) {
|
|
1671
2080
|
declaresName(child, emitMethodFacts(emitter, child));
|
|
1672
2081
|
}
|
|
1673
|
-
if (child.type === "assignment") {
|
|
1674
|
-
emitAssignment(emitter, child);
|
|
1675
|
-
}
|
|
1676
2082
|
walk(child);
|
|
1677
2083
|
}
|
|
1678
2084
|
};
|
|
1679
2085
|
walk(root);
|
|
1680
2086
|
emitExpressionFacts(emitter, root);
|
|
2087
|
+
emitScopeWrites(emitter, null, root);
|
|
1681
2088
|
}
|
|
1682
2089
|
|
|
1683
2090
|
// src/values/lowering.ts
|
|
@@ -1693,7 +2100,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1693
2100
|
"module",
|
|
1694
2101
|
"singleton_class"
|
|
1695
2102
|
]);
|
|
1696
|
-
var
|
|
2103
|
+
var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1697
2104
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1698
2105
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1699
2106
|
"identifier",
|
|
@@ -2199,7 +2606,7 @@ function isTailExpression(node) {
|
|
|
2199
2606
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
2200
2607
|
return true;
|
|
2201
2608
|
}
|
|
2202
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
2609
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES3.has(owner.type)) {
|
|
2203
2610
|
return true;
|
|
2204
2611
|
}
|
|
2205
2612
|
if (owner.type === "when") {
|
|
@@ -2218,7 +2625,7 @@ function isRoot(node) {
|
|
|
2218
2625
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
2219
2626
|
return true;
|
|
2220
2627
|
}
|
|
2221
|
-
return
|
|
2628
|
+
return BLOCK_TYPES3.has(node.type) && node.parent?.type === "call";
|
|
2222
2629
|
}
|
|
2223
2630
|
function isPathStatement(node, parent) {
|
|
2224
2631
|
if (node.type === "elsif") {
|
|
@@ -2246,7 +2653,7 @@ function siteOf(node) {
|
|
|
2246
2653
|
current = parent;
|
|
2247
2654
|
}
|
|
2248
2655
|
}
|
|
2249
|
-
function
|
|
2656
|
+
function parametersOf2(parameters) {
|
|
2250
2657
|
if (parameters === null) {
|
|
2251
2658
|
return [];
|
|
2252
2659
|
}
|
|
@@ -2268,19 +2675,19 @@ function functionOf(node) {
|
|
|
2268
2675
|
return null;
|
|
2269
2676
|
}
|
|
2270
2677
|
return {
|
|
2271
|
-
parameters:
|
|
2678
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2272
2679
|
body: bodyOf(block)
|
|
2273
2680
|
};
|
|
2274
2681
|
}
|
|
2275
|
-
if (
|
|
2682
|
+
if (BLOCK_TYPES3.has(node.type)) {
|
|
2276
2683
|
return {
|
|
2277
|
-
parameters:
|
|
2684
|
+
parameters: parametersOf2(field(node, "parameters")),
|
|
2278
2685
|
body: bodyOf(node)
|
|
2279
2686
|
};
|
|
2280
2687
|
}
|
|
2281
2688
|
if (METHOD_TYPES2.has(node.type)) {
|
|
2282
2689
|
const body = field(node, "body");
|
|
2283
|
-
const parameters =
|
|
2690
|
+
const parameters = parametersOf2(field(node, "parameters"));
|
|
2284
2691
|
if (body === null) {
|
|
2285
2692
|
return { parameters, body: [] };
|
|
2286
2693
|
}
|
|
@@ -2623,11 +3030,18 @@ function bindEvaluator(db, nodes) {
|
|
|
2623
3030
|
)
|
|
2624
3031
|
);
|
|
2625
3032
|
}
|
|
2626
|
-
function evaluatedValue(node, db) {
|
|
2627
|
-
|
|
3033
|
+
function evaluatedValue(node, db, bindings) {
|
|
3034
|
+
const evaluator = evaluatorFor(node, db);
|
|
3035
|
+
if (bindings === void 0) {
|
|
3036
|
+
return (0, import_values2.force)(evaluator.evaluate(node));
|
|
3037
|
+
}
|
|
3038
|
+
const supplied = new Map(
|
|
3039
|
+
[...bindings].map(([name, value]) => [name, (0, import_values2.text)(value)])
|
|
3040
|
+
);
|
|
3041
|
+
return (0, import_values2.force)(evaluator.evaluate(node, { bindings: supplied }));
|
|
2628
3042
|
}
|
|
2629
|
-
function stringValueOf(node, db) {
|
|
2630
|
-
return (0, import_values2.literalOf)(evaluatedValue(node, db));
|
|
3043
|
+
function stringValueOf(node, db, bindings) {
|
|
3044
|
+
return (0, import_values2.literalOf)(evaluatedValue(node, db, bindings));
|
|
2631
3045
|
}
|
|
2632
3046
|
function methodDefinitionsIn(file, root) {
|
|
2633
3047
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -2685,7 +3099,7 @@ function contextOver(db, nodes) {
|
|
|
2685
3099
|
db,
|
|
2686
3100
|
readKey(file, node, enclosingMethod(node))
|
|
2687
3101
|
);
|
|
2688
|
-
return answer === null ? null : (0,
|
|
3102
|
+
return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
|
|
2689
3103
|
},
|
|
2690
3104
|
callable: (call) => {
|
|
2691
3105
|
const file = fileOf(call);
|
|
@@ -3288,8 +3702,7 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3288
3702
|
skipNames.set(form.skippedBy, form.name);
|
|
3289
3703
|
}
|
|
3290
3704
|
}
|
|
3291
|
-
|
|
3292
|
-
const skips = [];
|
|
3705
|
+
let declared = [];
|
|
3293
3706
|
for (const { statement } of inheritedStatements(ancestry)) {
|
|
3294
3707
|
const called = calledName(statement);
|
|
3295
3708
|
if (called === null) {
|
|
@@ -3297,16 +3710,21 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3297
3710
|
}
|
|
3298
3711
|
const form = byName.get(called);
|
|
3299
3712
|
if (form !== void 0) {
|
|
3300
|
-
|
|
3713
|
+
for (const declaration of declarationsOf(statement, form)) {
|
|
3714
|
+
declared = [
|
|
3715
|
+
...declared.filter((earlier) => !sameFilter(earlier, declaration)),
|
|
3716
|
+
declaration
|
|
3717
|
+
];
|
|
3718
|
+
}
|
|
3301
3719
|
continue;
|
|
3302
3720
|
}
|
|
3303
3721
|
const skipped = skipNames.get(called);
|
|
3304
3722
|
if (skipped !== void 0) {
|
|
3305
|
-
|
|
3723
|
+
declared = applySkips(declared, skipsOf(statement, skipped));
|
|
3306
3724
|
}
|
|
3307
3725
|
}
|
|
3308
3726
|
const resolved = [];
|
|
3309
|
-
for (const declaration of
|
|
3727
|
+
for (const declaration of declared) {
|
|
3310
3728
|
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
3311
3729
|
if (found.type !== "found") {
|
|
3312
3730
|
continue;
|
|
@@ -3419,6 +3837,9 @@ function skipsOf(statement, filterName) {
|
|
|
3419
3837
|
const actions = namesUnder(args.keyword.only);
|
|
3420
3838
|
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
3421
3839
|
}
|
|
3840
|
+
function sameFilter(one, other) {
|
|
3841
|
+
return one.filter.name === other.filter.name && one.methodName === other.methodName;
|
|
3842
|
+
}
|
|
3422
3843
|
function applySkips(declared, skips) {
|
|
3423
3844
|
const left = [];
|
|
3424
3845
|
for (const declaration of declared) {
|
|
@@ -3461,9 +3882,8 @@ function namesUnder(node) {
|
|
|
3461
3882
|
}
|
|
3462
3883
|
|
|
3463
3884
|
// src/storage.ts
|
|
3464
|
-
var import_datalog2 = require("@suss/datalog");
|
|
3465
3885
|
var import_ir_core = require("@suss/ir-core");
|
|
3466
|
-
var
|
|
3886
|
+
var import_resolution5 = require("@suss/resolution");
|
|
3467
3887
|
function children3(node) {
|
|
3468
3888
|
return node.namedChildren.filter((child) => child !== null);
|
|
3469
3889
|
}
|
|
@@ -3489,13 +3909,8 @@ function isConstant(node) {
|
|
|
3489
3909
|
function constantName(constant2) {
|
|
3490
3910
|
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3491
3911
|
}
|
|
3492
|
-
var ANCESTRY_PROGRAM = (0, import_datalog2.deriveOnDemand)(
|
|
3493
|
-
[...import_resolution3.RESOLUTION_RULES, ...import_resolution3.RESOLUTION_QUESTIONS],
|
|
3494
|
-
import_resolution3.ANSWER_RELATIONS
|
|
3495
|
-
);
|
|
3496
3912
|
function reachesBase(facts, classKey, bases) {
|
|
3497
|
-
|
|
3498
|
-
(0, import_datalog2.evaluate)(facts, ANCESTRY_PROGRAM.rules);
|
|
3913
|
+
(0, import_resolution5.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
|
|
3499
3914
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3500
3915
|
}
|
|
3501
3916
|
function classBehind(facts, file, constant2) {
|
|
@@ -3542,7 +3957,7 @@ function storageEffect(call, model, pattern, kind, selector) {
|
|
|
3542
3957
|
binding: (0, import_ir_core.storageBinding)({
|
|
3543
3958
|
recognition: "ruby-storage",
|
|
3544
3959
|
storageSystem: pattern.storageSystem,
|
|
3545
|
-
scope:
|
|
3960
|
+
scope: "default",
|
|
3546
3961
|
container: constantName(model)
|
|
3547
3962
|
}),
|
|
3548
3963
|
callee: call.text,
|
|
@@ -3710,6 +4125,7 @@ function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
|
|
|
3710
4125
|
lookup: {
|
|
3711
4126
|
root: pattern.root,
|
|
3712
4127
|
pathConvention: pattern.pathConvention,
|
|
4128
|
+
acronyms: pattern.acronyms ?? [],
|
|
3713
4129
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3714
4130
|
parsedFile: (absPath) => cache.get(absPath),
|
|
3715
4131
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
@@ -3824,12 +4240,16 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3824
4240
|
const lookup = {
|
|
3825
4241
|
root: pattern.root,
|
|
3826
4242
|
pathConvention: pattern.pathConvention,
|
|
4243
|
+
acronyms: pattern.acronyms ?? [],
|
|
3827
4244
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3828
4245
|
parsedFile: (absPath) => options.cache.get(absPath),
|
|
3829
4246
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
3830
4247
|
};
|
|
3831
4248
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, lookup);
|
|
3832
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName,
|
|
4249
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, [
|
|
4250
|
+
...pattern.baseClassNames,
|
|
4251
|
+
...pattern.ancestryRootClassNames
|
|
4252
|
+
])) {
|
|
3833
4253
|
return [];
|
|
3834
4254
|
}
|
|
3835
4255
|
const filters = controllerFilters(pattern, ancestry);
|
|
@@ -3849,43 +4269,73 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3849
4269
|
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3850
4270
|
});
|
|
3851
4271
|
}
|
|
3852
|
-
|
|
3853
|
-
|
|
4272
|
+
const emitAction = (actionName, method, block) => {
|
|
4273
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
4274
|
+
(filter) => filterReference(
|
|
4275
|
+
filter,
|
|
4276
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
4277
|
+
)
|
|
4278
|
+
);
|
|
4279
|
+
const raw = buildControllerActionUnit(
|
|
4280
|
+
pack,
|
|
4281
|
+
pattern,
|
|
4282
|
+
info.qualifiedName,
|
|
4283
|
+
actionName,
|
|
4284
|
+
method,
|
|
4285
|
+
{
|
|
4286
|
+
display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
|
|
4287
|
+
absolute: block.file
|
|
4288
|
+
},
|
|
4289
|
+
options,
|
|
4290
|
+
around
|
|
4291
|
+
);
|
|
4292
|
+
units.push(raw);
|
|
4293
|
+
options.onReachSeed?.(raw, {
|
|
4294
|
+
file: block.file,
|
|
4295
|
+
node: method,
|
|
4296
|
+
enclosingQualifiedName: block.info.qualifiedName
|
|
4297
|
+
});
|
|
4298
|
+
};
|
|
4299
|
+
const own = /* @__PURE__ */ new Set();
|
|
4300
|
+
for (const [actionName, method, block] of publicInstanceMethods(ownBlocks)) {
|
|
4301
|
+
own.add(actionName);
|
|
4302
|
+
emitAction(actionName, method, block);
|
|
4303
|
+
}
|
|
4304
|
+
const seen = new Set(own);
|
|
4305
|
+
for (const entry of ancestry) {
|
|
4306
|
+
if (entry.type !== "bodies" || entry.name === info.qualifiedName) {
|
|
3854
4307
|
continue;
|
|
3855
4308
|
}
|
|
3856
|
-
const
|
|
3857
|
-
|
|
3858
|
-
block.info.bodyNode
|
|
4309
|
+
for (const [actionName, method, block] of publicInstanceMethods(
|
|
4310
|
+
entry.blocks
|
|
3859
4311
|
)) {
|
|
3860
|
-
if (
|
|
4312
|
+
if (seen.has(actionName)) {
|
|
3861
4313
|
continue;
|
|
3862
4314
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
);
|
|
3869
|
-
const raw = buildControllerActionUnit(
|
|
3870
|
-
pack,
|
|
3871
|
-
pattern,
|
|
3872
|
-
info.qualifiedName,
|
|
3873
|
-
actionName,
|
|
3874
|
-
method,
|
|
3875
|
-
{ display: options.filePath, absolute: block.file },
|
|
3876
|
-
options,
|
|
3877
|
-
around
|
|
3878
|
-
);
|
|
3879
|
-
units.push(raw);
|
|
3880
|
-
options.onReachSeed?.(raw, {
|
|
3881
|
-
file: block.file,
|
|
3882
|
-
node: method,
|
|
3883
|
-
enclosingQualifiedName: info.qualifiedName
|
|
3884
|
-
});
|
|
4315
|
+
seen.add(actionName);
|
|
4316
|
+
if (pattern.routeFor(info.qualifiedName, actionName) === null) {
|
|
4317
|
+
continue;
|
|
4318
|
+
}
|
|
4319
|
+
emitAction(actionName, method, block);
|
|
3885
4320
|
}
|
|
3886
4321
|
}
|
|
3887
4322
|
return units;
|
|
3888
4323
|
}
|
|
4324
|
+
function publicInstanceMethods(blocks) {
|
|
4325
|
+
const found = [];
|
|
4326
|
+
for (const block of blocks) {
|
|
4327
|
+
if (block.info.bodyNode === null) {
|
|
4328
|
+
continue;
|
|
4329
|
+
}
|
|
4330
|
+
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
4331
|
+
for (const [name, method] of instanceMethodsByName(block.info.bodyNode)) {
|
|
4332
|
+
if ((visibility.get(name) ?? "public") === "public") {
|
|
4333
|
+
found.push([name, method, block]);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
return found;
|
|
4338
|
+
}
|
|
3889
4339
|
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, file, bodyRead, wrappers = []) {
|
|
3890
4340
|
const range = rangeOf(method);
|
|
3891
4341
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
@@ -4398,7 +4848,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4398
4848
|
if (written !== null) {
|
|
4399
4849
|
definitions.push({
|
|
4400
4850
|
qualifiedName: qualify(nesting, written),
|
|
4401
|
-
key: nodeId(filePath, node)
|
|
4851
|
+
key: nodeId(filePath, node),
|
|
4852
|
+
kind: "declaration"
|
|
4402
4853
|
});
|
|
4403
4854
|
const inside = [...nesting, ...written.split("::")];
|
|
4404
4855
|
for (const child of children4(node)) {
|
|
@@ -4413,7 +4864,8 @@ function collectFileConstants(filePath, root) {
|
|
|
4413
4864
|
if (left !== null && left.type === "constant" && right !== null) {
|
|
4414
4865
|
definitions.push({
|
|
4415
4866
|
qualifiedName: qualify(nesting, left.text),
|
|
4416
|
-
key: nodeId(filePath, right)
|
|
4867
|
+
key: nodeId(filePath, right),
|
|
4868
|
+
kind: "assignment"
|
|
4417
4869
|
});
|
|
4418
4870
|
}
|
|
4419
4871
|
}
|
|
@@ -4440,14 +4892,18 @@ function emitConstantBindings(db, perFile) {
|
|
|
4440
4892
|
for (const file of files) {
|
|
4441
4893
|
for (const definition of file.definitions) {
|
|
4442
4894
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
4443
|
-
found.push(definition
|
|
4895
|
+
found.push(definition);
|
|
4444
4896
|
byName.set(definition.qualifiedName, found);
|
|
4445
4897
|
fileOfDefinition.set(definition.key, file.filePath);
|
|
4446
4898
|
}
|
|
4447
4899
|
}
|
|
4448
4900
|
const settles = (candidate) => {
|
|
4449
|
-
const found = byName.get(candidate);
|
|
4450
|
-
|
|
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;
|
|
4451
4907
|
};
|
|
4452
4908
|
for (const file of files) {
|
|
4453
4909
|
for (const reference of file.references) {
|
|
@@ -4473,8 +4929,8 @@ function emitConstantBindings(db, perFile) {
|
|
|
4473
4929
|
// src/facts/why.ts
|
|
4474
4930
|
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
4475
4931
|
var import_node_path5 = __toESM(require("path"), 1);
|
|
4476
|
-
var
|
|
4477
|
-
var
|
|
4932
|
+
var import_datalog4 = require("@suss/datalog");
|
|
4933
|
+
var import_resolution7 = require("@suss/resolution");
|
|
4478
4934
|
|
|
4479
4935
|
// src/parser.ts
|
|
4480
4936
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -4536,7 +4992,7 @@ function parseWith(language, source) {
|
|
|
4536
4992
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
4537
4993
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
4538
4994
|
var import_behavioral_ir5 = require("@suss/behavioral-ir");
|
|
4539
|
-
var
|
|
4995
|
+
var import_datalog3 = require("@suss/datalog");
|
|
4540
4996
|
var import_extractor10 = require("@suss/extractor");
|
|
4541
4997
|
|
|
4542
4998
|
// src/diagnostics.ts
|
|
@@ -4591,34 +5047,118 @@ function requiredRelativePath(node) {
|
|
|
4591
5047
|
|
|
4592
5048
|
// src/reach/closure.ts
|
|
4593
5049
|
var import_behavioral_ir4 = require("@suss/behavioral-ir");
|
|
4594
|
-
var
|
|
5050
|
+
var import_datalog2 = require("@suss/datalog");
|
|
4595
5051
|
var import_extractor8 = require("@suss/extractor");
|
|
4596
5052
|
|
|
4597
5053
|
// src/reach/resolveCallee.ts
|
|
5054
|
+
var import_resolution6 = require("@suss/resolution");
|
|
4598
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
|
+
};
|
|
4599
5063
|
var stop = (reason) => ({
|
|
4600
5064
|
kind: "stopped",
|
|
4601
5065
|
reason
|
|
4602
5066
|
});
|
|
5067
|
+
var NO_DECLARATION = stop("noDeclaration");
|
|
4603
5068
|
function followed(target) {
|
|
4604
5069
|
return { kind: "followed", target };
|
|
4605
5070
|
}
|
|
4606
|
-
|
|
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) {
|
|
4607
5098
|
const methodName = calleeMethodName(call);
|
|
4608
5099
|
if (methodName === void 0) {
|
|
4609
|
-
return
|
|
5100
|
+
return { kind: "stopped", reason: "noDeclaration" };
|
|
4610
5101
|
}
|
|
4611
5102
|
if (DYNAMIC_SEND_NAMES.has(methodName)) {
|
|
4612
|
-
return
|
|
5103
|
+
return { kind: "stopped", reason: "unsettledValue" };
|
|
4613
5104
|
}
|
|
4614
5105
|
const receiver = field(call, "receiver");
|
|
4615
5106
|
if (receiver === null || receiver.type === "self") {
|
|
4616
|
-
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");
|
|
5126
|
+
}
|
|
5127
|
+
const objectKey = objects[0] ?? (outcome.kind === "object" ? outcome.key : void 0);
|
|
5128
|
+
if (objectKey !== void 0) {
|
|
5129
|
+
return methodOnObject(spelling, objectKey, ctx);
|
|
5130
|
+
}
|
|
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;
|
|
4617
5147
|
}
|
|
4618
|
-
if (
|
|
4619
|
-
return
|
|
5148
|
+
if (!spelling.onClassItself) {
|
|
5149
|
+
return methodOnAncestryOf(qualifiedName, spelling.method, ctx);
|
|
4620
5150
|
}
|
|
4621
|
-
return
|
|
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);
|
|
4622
5162
|
}
|
|
4623
5163
|
async function resolveMethodReference(name, site, ctx) {
|
|
4624
5164
|
const resolved = await resolveImplicitSelf(name, site, ctx);
|
|
@@ -4643,58 +5183,13 @@ async function resolveImplicitSelf(methodName, site, ctx) {
|
|
|
4643
5183
|
function resolveTopLevelName(methodName, ctx) {
|
|
4644
5184
|
const candidates = ctx.topLevelMethods.get(methodName);
|
|
4645
5185
|
if (candidates === void 0 || candidates.length === 0) {
|
|
4646
|
-
return
|
|
5186
|
+
return NO_DECLARATION;
|
|
4647
5187
|
}
|
|
4648
5188
|
if (candidates.length > 1) {
|
|
4649
5189
|
return stop("multipleSources");
|
|
4650
5190
|
}
|
|
4651
5191
|
return followed(candidates[0]);
|
|
4652
5192
|
}
|
|
4653
|
-
async function resolveOnReceiver(receiver, methodName, site, ctx) {
|
|
4654
|
-
if (receiver.type === "call") {
|
|
4655
|
-
const innerName = calleeMethodName(receiver);
|
|
4656
|
-
const innerReceiver = field(receiver, "receiver");
|
|
4657
|
-
if (innerName === "new" && innerReceiver !== null) {
|
|
4658
|
-
const classRef = classRefOf(innerReceiver, site, ctx);
|
|
4659
|
-
return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
|
|
4660
|
-
}
|
|
4661
|
-
const root = rootConstantOf(receiver);
|
|
4662
|
-
if (root === null) {
|
|
4663
|
-
return stop("unsettledValue");
|
|
4664
|
-
}
|
|
4665
|
-
return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
|
|
4666
|
-
}
|
|
4667
|
-
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
4668
|
-
const classRef = classRefOf(receiver, site, ctx);
|
|
4669
|
-
return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
|
|
4670
|
-
}
|
|
4671
|
-
return stop("unsettledValue");
|
|
4672
|
-
}
|
|
4673
|
-
function rootConstantOf(node) {
|
|
4674
|
-
if (node.type === "constant" || node.type === "scope_resolution") {
|
|
4675
|
-
return node;
|
|
4676
|
-
}
|
|
4677
|
-
if (node.type !== "call") {
|
|
4678
|
-
return null;
|
|
4679
|
-
}
|
|
4680
|
-
const receiver = field(node, "receiver");
|
|
4681
|
-
return receiver === null ? null : rootConstantOf(receiver);
|
|
4682
|
-
}
|
|
4683
|
-
function classRefOf(node, site, ctx) {
|
|
4684
|
-
if (node.type === "constant") {
|
|
4685
|
-
const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
|
|
4686
|
-
if (shadow !== null) {
|
|
4687
|
-
return shadow;
|
|
4688
|
-
}
|
|
4689
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4690
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4691
|
-
}
|
|
4692
|
-
if (node.type === "scope_resolution") {
|
|
4693
|
-
const qualified = qualifyConstantRef(node, site.nesting);
|
|
4694
|
-
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
4695
|
-
}
|
|
4696
|
-
return null;
|
|
4697
|
-
}
|
|
4698
5193
|
async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
4699
5194
|
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
4700
5195
|
if (ownBlocks === null) {
|
|
@@ -4710,7 +5205,7 @@ async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
|
4710
5205
|
found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
|
|
4711
5206
|
);
|
|
4712
5207
|
}
|
|
4713
|
-
return
|
|
5208
|
+
return NO_DECLARATION;
|
|
4714
5209
|
}
|
|
4715
5210
|
function reachedMethod(node, block, name) {
|
|
4716
5211
|
return {
|
|
@@ -4735,21 +5230,21 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
|
4735
5230
|
return followed(reachedMethod(found, block, methodName));
|
|
4736
5231
|
}
|
|
4737
5232
|
}
|
|
4738
|
-
return
|
|
5233
|
+
return NO_DECLARATION;
|
|
4739
5234
|
}
|
|
4740
5235
|
|
|
4741
5236
|
// src/reach/closure.ts
|
|
4742
5237
|
var REACHABLE_RULES = [
|
|
4743
|
-
(0,
|
|
4744
|
-
(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)(
|
|
4745
5240
|
"reachable",
|
|
4746
|
-
[(0,
|
|
4747
|
-
[(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"))]
|
|
4748
5243
|
)
|
|
4749
5244
|
];
|
|
4750
5245
|
async function reachedFunctions(seeds, options) {
|
|
4751
|
-
const ctx = buildReachContext(options.files);
|
|
4752
|
-
const db = new
|
|
5246
|
+
const ctx = buildReachContext(options.files, options.facts);
|
|
5247
|
+
const db = new import_datalog2.Database();
|
|
4753
5248
|
const functionByKey = /* @__PURE__ */ new Map();
|
|
4754
5249
|
const seedKeys = /* @__PURE__ */ new Set();
|
|
4755
5250
|
const scanned = /* @__PURE__ */ new Set();
|
|
@@ -4770,18 +5265,26 @@ async function reachedFunctions(seeds, options) {
|
|
|
4770
5265
|
db.add("entry", [seed.key]);
|
|
4771
5266
|
}
|
|
4772
5267
|
for (; ; ) {
|
|
4773
|
-
(0,
|
|
5268
|
+
(0, import_datalog2.evaluate)(db, REACHABLE_RULES);
|
|
4774
5269
|
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
4775
5270
|
if (frontier.length === 0) {
|
|
4776
5271
|
break;
|
|
4777
5272
|
}
|
|
4778
|
-
|
|
5273
|
+
const bodies = frontier.flatMap((key) => {
|
|
4779
5274
|
scanned.add(key);
|
|
4780
5275
|
const source = functionByKey.get(key);
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
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
|
+
});
|
|
4785
5288
|
if (scan.stops.length > 0) {
|
|
4786
5289
|
stopsByKey.set(key, scan.stops);
|
|
4787
5290
|
}
|
|
@@ -4834,11 +5337,22 @@ async function reachedFunctions(seeds, options) {
|
|
|
4834
5337
|
function keyOf(target) {
|
|
4835
5338
|
return nodeId(target.file, target.node);
|
|
4836
5339
|
}
|
|
4837
|
-
|
|
5340
|
+
var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5341
|
+
function buildReachContext(files, facts) {
|
|
4838
5342
|
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
4839
5343
|
const classes = [];
|
|
5344
|
+
const classNames = /* @__PURE__ */ new Map();
|
|
5345
|
+
const classOfMethod = /* @__PURE__ */ new Map();
|
|
4840
5346
|
for (const { file, root } of files) {
|
|
4841
|
-
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
|
+
});
|
|
4842
5356
|
}
|
|
4843
5357
|
const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
|
|
4844
5358
|
for (const { file, info } of classes) {
|
|
@@ -4846,22 +5360,33 @@ function buildReachContext(files) {
|
|
|
4846
5360
|
list.push({ info, knownClasses, file });
|
|
4847
5361
|
blocksByQualifiedName.set(info.qualifiedName, list);
|
|
4848
5362
|
}
|
|
4849
|
-
const
|
|
5363
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
4850
5364
|
for (const { file, root } of files) {
|
|
4851
|
-
for (const method of
|
|
5365
|
+
for (const [key, method] of methodDefinitionsIn(file, root)) {
|
|
4852
5366
|
const name = field(method, "name")?.text;
|
|
4853
5367
|
if (name === void 0) {
|
|
4854
5368
|
continue;
|
|
4855
5369
|
}
|
|
4856
|
-
const
|
|
4857
|
-
|
|
5370
|
+
const owner = classOfMethod.get(key) ?? null;
|
|
5371
|
+
definitions.set(key, {
|
|
4858
5372
|
file,
|
|
4859
5373
|
node: method,
|
|
4860
5374
|
name,
|
|
4861
|
-
exportPath: [name],
|
|
4862
|
-
enclosingQualifiedName:
|
|
5375
|
+
exportPath: owner === null ? [name] : [owner, name],
|
|
5376
|
+
enclosingQualifiedName: owner
|
|
4863
5377
|
});
|
|
4864
|
-
|
|
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);
|
|
4865
5390
|
}
|
|
4866
5391
|
}
|
|
4867
5392
|
const lookup = {
|
|
@@ -4874,7 +5399,13 @@ function buildReachContext(files) {
|
|
|
4874
5399
|
parsedFile: async () => null,
|
|
4875
5400
|
localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
|
|
4876
5401
|
};
|
|
4877
|
-
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);
|
|
4878
5409
|
}
|
|
4879
5410
|
function topLevelMethodNodes(root, found = []) {
|
|
4880
5411
|
for (const child of bodyStatements(root)) {
|
|
@@ -4889,14 +5420,6 @@ function topLevelMethodNodes(root, found = []) {
|
|
|
4889
5420
|
}
|
|
4890
5421
|
return found;
|
|
4891
5422
|
}
|
|
4892
|
-
function nestingOf(qualifiedName) {
|
|
4893
|
-
const parts = qualifiedName.split("::");
|
|
4894
|
-
const out = [];
|
|
4895
|
-
for (let depth = parts.length; depth >= 1; depth--) {
|
|
4896
|
-
out.push(parts.slice(0, depth).join("::"));
|
|
4897
|
-
}
|
|
4898
|
-
return out;
|
|
4899
|
-
}
|
|
4900
5423
|
var EMPTY_SCAN = {
|
|
4901
5424
|
followed: [],
|
|
4902
5425
|
stops: [],
|
|
@@ -4914,21 +5437,23 @@ function methodReferenceSymbol(node) {
|
|
|
4914
5437
|
const argChildren = args === null ? [] : bodyStatements(args);
|
|
4915
5438
|
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
4916
5439
|
}
|
|
4917
|
-
|
|
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) {
|
|
4918
5451
|
const displayPathOf = options.displayPathOf;
|
|
4919
|
-
|
|
4920
|
-
if (body === null) {
|
|
5452
|
+
if (field(source.node, "body") === null) {
|
|
4921
5453
|
return EMPTY_SCAN;
|
|
4922
5454
|
}
|
|
4923
|
-
const calls =
|
|
4924
|
-
bodyCalls(source.node, options.inheritedMethods)
|
|
4925
|
-
);
|
|
5455
|
+
const { calls, site } = read;
|
|
4926
5456
|
const ownParameters = positionalParameters(source.node).map((p) => p.name);
|
|
4927
|
-
const site = {
|
|
4928
|
-
enclosingQualifiedName: source.enclosingQualifiedName,
|
|
4929
|
-
nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
|
|
4930
|
-
ownParameters
|
|
4931
|
-
};
|
|
4932
5457
|
const followed2 = [];
|
|
4933
5458
|
const stops = [];
|
|
4934
5459
|
const placements = new import_behavioral_ir4.TargetPlacements();
|
|
@@ -4971,7 +5496,7 @@ async function scanBody(source, ctx, options) {
|
|
|
4971
5496
|
};
|
|
4972
5497
|
for (const call of calls) {
|
|
4973
5498
|
const callee = calleeText(call);
|
|
4974
|
-
const outcome = await resolveCallee(call, site, ctx);
|
|
5499
|
+
const outcome = await resolveCallee(call, site, ctx, read.spellings);
|
|
4975
5500
|
const placed = outcome.kind === "followed" ? {
|
|
4976
5501
|
file: displayPathOf(outcome.target.file),
|
|
4977
5502
|
span: spanOf(outcome.target.node)
|
|
@@ -5109,7 +5634,8 @@ function inheritedMethodsIn(packs) {
|
|
|
5109
5634
|
return found;
|
|
5110
5635
|
}
|
|
5111
5636
|
function alreadyDiscovered(seen, raw) {
|
|
5112
|
-
const
|
|
5637
|
+
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
5638
|
+
const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
|
|
5113
5639
|
if (seen.has(key)) {
|
|
5114
5640
|
return true;
|
|
5115
5641
|
}
|
|
@@ -5138,9 +5664,9 @@ async function extractRubyProject(options) {
|
|
|
5138
5664
|
options.onCacheDiagnostic?.(lookup.diagnostic);
|
|
5139
5665
|
if (lookup.kind === "hit") {
|
|
5140
5666
|
options.onTiming?.(timer.report());
|
|
5141
|
-
return { summaries: lookup.summaries, facts: new
|
|
5667
|
+
return { summaries: lookup.summaries, facts: new import_datalog3.Database() };
|
|
5142
5668
|
}
|
|
5143
|
-
const db = new
|
|
5669
|
+
const db = new import_datalog3.Database();
|
|
5144
5670
|
const summaries = [];
|
|
5145
5671
|
const tallies = (0, import_extractor6.createPackTallies)(options.packs);
|
|
5146
5672
|
const constants = [];
|
|
@@ -5269,6 +5795,7 @@ async function extractRubyProject(options) {
|
|
|
5269
5795
|
() => reachedFunctions(seeds, {
|
|
5270
5796
|
files: parsed,
|
|
5271
5797
|
displayPathOf,
|
|
5798
|
+
facts: db,
|
|
5272
5799
|
...storage === void 0 ? {} : { storage },
|
|
5273
5800
|
inheritedMethods
|
|
5274
5801
|
})
|
|
@@ -5365,15 +5892,15 @@ function findRubyFiles(root) {
|
|
|
5365
5892
|
}
|
|
5366
5893
|
|
|
5367
5894
|
// src/facts/why.ts
|
|
5368
|
-
var WITNESS_RULES = [...
|
|
5369
|
-
var
|
|
5895
|
+
var WITNESS_RULES = [...import_resolution7.RESOLUTION_RULES, ...RUBY_RULES];
|
|
5896
|
+
var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
5370
5897
|
function namedChildrenOf(node) {
|
|
5371
5898
|
return node.namedChildren.filter((child) => child !== null);
|
|
5372
5899
|
}
|
|
5373
5900
|
function enclosingMethod2(node) {
|
|
5374
5901
|
let current = node.parent;
|
|
5375
5902
|
while (current !== null) {
|
|
5376
|
-
if (
|
|
5903
|
+
if (METHOD_TYPES5.has(current.type)) {
|
|
5377
5904
|
return current;
|
|
5378
5905
|
}
|
|
5379
5906
|
current = current.parent;
|
|
@@ -5408,15 +5935,15 @@ function displayNameOf(node) {
|
|
|
5408
5935
|
if (node.type === "class" || node.type === "module") {
|
|
5409
5936
|
return field(node, "name")?.text ?? node.text;
|
|
5410
5937
|
}
|
|
5411
|
-
const
|
|
5412
|
-
return
|
|
5938
|
+
const text2 = node.text.split("\n")[0].trim();
|
|
5939
|
+
return text2.length > 40 ? `${text2.slice(0, 40)}...` : text2;
|
|
5413
5940
|
}
|
|
5414
5941
|
function width(node) {
|
|
5415
5942
|
return node.endIndex - node.startIndex;
|
|
5416
5943
|
}
|
|
5417
5944
|
var RubyWhySession = class {
|
|
5418
5945
|
root;
|
|
5419
|
-
db = new
|
|
5946
|
+
db = new import_datalog4.Database();
|
|
5420
5947
|
locations = /* @__PURE__ */ new Map();
|
|
5421
5948
|
trees = /* @__PURE__ */ new Map();
|
|
5422
5949
|
constructor(options) {
|
|
@@ -5436,14 +5963,14 @@ var RubyWhySession = class {
|
|
|
5436
5963
|
* The smallest expression on `line` of `file` whose text is exactly
|
|
5437
5964
|
* `text`, or null.
|
|
5438
5965
|
*/
|
|
5439
|
-
findExpression(file, line,
|
|
5966
|
+
findExpression(file, line, text2) {
|
|
5440
5967
|
const root = this.rootOf(file);
|
|
5441
5968
|
if (root === null) {
|
|
5442
5969
|
return null;
|
|
5443
5970
|
}
|
|
5444
5971
|
let found = null;
|
|
5445
5972
|
const visit = (node) => {
|
|
5446
|
-
if (node.startPosition.row + 1 === line && node.text ===
|
|
5973
|
+
if (node.startPosition.row + 1 === line && node.text === text2) {
|
|
5447
5974
|
if (found === null || width(node) <= width(found)) {
|
|
5448
5975
|
found = node;
|
|
5449
5976
|
}
|
|
@@ -5486,7 +6013,7 @@ var RubyWhySession = class {
|
|
|
5486
6013
|
* which the caller says in its own words.
|
|
5487
6014
|
*/
|
|
5488
6015
|
explain(value, options = {}) {
|
|
5489
|
-
return (0,
|
|
6016
|
+
return (0, import_resolution7.explainResolvedKey)({
|
|
5490
6017
|
db: this.db,
|
|
5491
6018
|
rules: WITNESS_RULES,
|
|
5492
6019
|
key: readKey(value.file, value.node, enclosingMethod2(value.node)),
|
|
@@ -5614,6 +6141,7 @@ async function rubyStubEvidence(options) {
|
|
|
5614
6141
|
inheritedStatements,
|
|
5615
6142
|
methodDefinitionsIn,
|
|
5616
6143
|
methodInAncestry,
|
|
6144
|
+
nestedStatements,
|
|
5617
6145
|
nodeId,
|
|
5618
6146
|
parseRuby,
|
|
5619
6147
|
parseRubySync,
|