@suss/adapter-ruby 0.21.1 → 0.22.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/README.md +78 -2
- package/dist/index.cjs +2224 -519
- package/dist/index.d.cts +280 -8
- package/dist/index.d.ts +280 -8
- package/dist/index.js +1902 -174
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
|
29
29
|
"module",
|
|
30
30
|
"singleton_class"
|
|
31
31
|
]);
|
|
32
|
+
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
32
33
|
var ARGUMENT_LIST_TYPE = "argument_list";
|
|
33
34
|
function runStatements(body, blockConfigures = () => false) {
|
|
34
35
|
const found = [];
|
|
@@ -46,6 +47,22 @@ function runStatements(body, blockConfigures = () => false) {
|
|
|
46
47
|
});
|
|
47
48
|
return found;
|
|
48
49
|
}
|
|
50
|
+
function stringLiteralValue(node) {
|
|
51
|
+
if (node.type !== "string") {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
let content = "";
|
|
55
|
+
for (const child of bodyStatements(node)) {
|
|
56
|
+
if (child.type === "string_content") {
|
|
57
|
+
content += child.text;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (child.type === "interpolation") {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return content;
|
|
65
|
+
}
|
|
49
66
|
function symbolValue(node) {
|
|
50
67
|
return node.type === "simple_symbol" ? node.text.slice(1) : null;
|
|
51
68
|
}
|
|
@@ -62,6 +79,71 @@ function instanceMethodsByName(body) {
|
|
|
62
79
|
}
|
|
63
80
|
return methods;
|
|
64
81
|
}
|
|
82
|
+
var VISIBILITY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
83
|
+
"private",
|
|
84
|
+
"protected",
|
|
85
|
+
"public"
|
|
86
|
+
]);
|
|
87
|
+
function visibilityKeyword(text) {
|
|
88
|
+
return VISIBILITY_KEYWORDS.has(text) ? text : null;
|
|
89
|
+
}
|
|
90
|
+
function setVisibility(visibility, name, keyword) {
|
|
91
|
+
if (keyword === "public") {
|
|
92
|
+
visibility.delete(name);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
visibility.set(name, keyword);
|
|
96
|
+
}
|
|
97
|
+
function markCalledOutMethods(call, keyword, visibility) {
|
|
98
|
+
const argumentList = field(call, "arguments");
|
|
99
|
+
if (argumentList === null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
for (const arg of bodyStatements(argumentList)) {
|
|
103
|
+
const name = arg.type === "method" ? field(arg, "name")?.text : symbolValue(arg);
|
|
104
|
+
if (name !== void 0 && name !== null) {
|
|
105
|
+
setVisibility(visibility, name, keyword);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function instanceMethodVisibility(body) {
|
|
110
|
+
const visibility = /* @__PURE__ */ new Map();
|
|
111
|
+
let mode = "public";
|
|
112
|
+
for (const stmt of bodyStatements(body)) {
|
|
113
|
+
if (stmt.type === "method") {
|
|
114
|
+
const name = field(stmt, "name")?.text;
|
|
115
|
+
if (name !== void 0) {
|
|
116
|
+
setVisibility(visibility, name, mode);
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (stmt.type === "identifier") {
|
|
121
|
+
mode = visibilityKeyword(stmt.text) ?? mode;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const keyword = visibilityKeyword(field(stmt, "method")?.text ?? "");
|
|
128
|
+
if (keyword !== null) {
|
|
129
|
+
markCalledOutMethods(stmt, keyword, visibility);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return visibility;
|
|
133
|
+
}
|
|
134
|
+
function singletonMethodsByName(body) {
|
|
135
|
+
const methods = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const stmt of runStatements(body)) {
|
|
137
|
+
if (stmt.type !== "singleton_method") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const name = field(stmt, "name")?.text;
|
|
141
|
+
if (name !== void 0) {
|
|
142
|
+
methods.set(name, stmt);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return methods;
|
|
146
|
+
}
|
|
65
147
|
function bareCallArgumentGroups(body, name) {
|
|
66
148
|
const groups = [];
|
|
67
149
|
for (const stmt of runStatements(body)) {
|
|
@@ -249,7 +331,10 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
249
331
|
qualifiedName,
|
|
250
332
|
lookup.pathConvention
|
|
251
333
|
);
|
|
252
|
-
|
|
334
|
+
if (filePath === null) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const fileRoot = await lookup.parsedFile(filePath);
|
|
253
338
|
if (fileRoot === null) {
|
|
254
339
|
return null;
|
|
255
340
|
}
|
|
@@ -260,7 +345,7 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
260
345
|
return null;
|
|
261
346
|
}
|
|
262
347
|
const knownClasses = new Set(all.map((info) => info.qualifiedName));
|
|
263
|
-
return matches.map((info) => ({ info, knownClasses }));
|
|
348
|
+
return matches.map((info) => ({ info, knownClasses, file: filePath }));
|
|
264
349
|
}
|
|
265
350
|
function ancestryOf(name, blocks, lookup) {
|
|
266
351
|
return chainOf(
|
|
@@ -352,17 +437,19 @@ function methodInAncestry(ancestry, name) {
|
|
|
352
437
|
if (entry.type === "unfollowed") {
|
|
353
438
|
return {
|
|
354
439
|
type: "unsettled",
|
|
355
|
-
reason: `inherited from ${entry.name}, which this run did not read
|
|
440
|
+
reason: `inherited from ${entry.name}, which this run did not read`,
|
|
441
|
+
cause: "unreadAncestor"
|
|
356
442
|
};
|
|
357
443
|
}
|
|
358
444
|
const found = definitionIn(entry.blocks, name);
|
|
359
|
-
if (found.method !== null) {
|
|
360
|
-
return { type: "found", method: found.method };
|
|
445
|
+
if (found.method !== null && found.block !== null) {
|
|
446
|
+
return { type: "found", method: found.method, block: found.block };
|
|
361
447
|
}
|
|
362
448
|
if (found.dynamic) {
|
|
363
449
|
return {
|
|
364
450
|
type: "unsettled",
|
|
365
|
-
reason: "defined with define_method, which this reader does not follow"
|
|
451
|
+
reason: "defined with define_method, which this reader does not follow",
|
|
452
|
+
cause: "dynamicDefine"
|
|
366
453
|
};
|
|
367
454
|
}
|
|
368
455
|
}
|
|
@@ -370,16 +457,21 @@ function methodInAncestry(ancestry, name) {
|
|
|
370
457
|
}
|
|
371
458
|
function definitionIn(blocks, name) {
|
|
372
459
|
let method = null;
|
|
460
|
+
let block = null;
|
|
373
461
|
let dynamic = false;
|
|
374
|
-
for (const
|
|
375
|
-
const body =
|
|
462
|
+
for (const candidate of blocks) {
|
|
463
|
+
const body = candidate.info.bodyNode;
|
|
376
464
|
if (body === null) {
|
|
377
465
|
continue;
|
|
378
466
|
}
|
|
379
|
-
|
|
467
|
+
const found = instanceMethodsByName(body).get(name);
|
|
468
|
+
if (found !== void 0) {
|
|
469
|
+
method = found;
|
|
470
|
+
block = candidate;
|
|
471
|
+
}
|
|
380
472
|
dynamic ||= bareCallArgumentGroups(body, DEFINE_METHOD_CALL).length > 0;
|
|
381
473
|
}
|
|
382
|
-
return { method, dynamic };
|
|
474
|
+
return { method, block, dynamic };
|
|
383
475
|
}
|
|
384
476
|
function inheritedStatements(ancestry, blockConfigures) {
|
|
385
477
|
const statements = [];
|
|
@@ -401,9 +493,114 @@ function inheritedStatements(ancestry, blockConfigures) {
|
|
|
401
493
|
}
|
|
402
494
|
|
|
403
495
|
// src/discovery.ts
|
|
404
|
-
import {
|
|
496
|
+
import {
|
|
497
|
+
dispatchByType as dispatchByType2,
|
|
498
|
+
graphqlResolverBinding,
|
|
499
|
+
restBinding
|
|
500
|
+
} from "@suss/behavioral-ir";
|
|
405
501
|
import { unreadableReading } from "@suss/extractor";
|
|
406
502
|
|
|
503
|
+
// src/envReads.ts
|
|
504
|
+
import { runtimeConfigBinding } from "@suss/behavioral-ir";
|
|
505
|
+
import { SKIP_CHILDREN as SKIP_CHILDREN2, walkDescendants as walkDescendants2 } from "@suss/extractor";
|
|
506
|
+
var RUBY_ENV_RECOGNITION = "ruby-env";
|
|
507
|
+
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
|
|
508
|
+
function envReadEffects(root) {
|
|
509
|
+
const effects = [];
|
|
510
|
+
walkDescendants2(root, null, {
|
|
511
|
+
at: (node) => {
|
|
512
|
+
const read = envReadAt(node);
|
|
513
|
+
if (read !== null) {
|
|
514
|
+
effects.push(configReadEffect(read));
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? SKIP_CHILDREN2 : null
|
|
518
|
+
});
|
|
519
|
+
return effects;
|
|
520
|
+
}
|
|
521
|
+
function configReadEffect(read) {
|
|
522
|
+
return {
|
|
523
|
+
type: "interaction",
|
|
524
|
+
binding: runtimeConfigBinding({
|
|
525
|
+
recognition: RUBY_ENV_RECOGNITION,
|
|
526
|
+
deploymentTarget: "lambda",
|
|
527
|
+
instanceName: "<unknown>"
|
|
528
|
+
}),
|
|
529
|
+
callee: `ENV["${read.name}"]`,
|
|
530
|
+
interaction: {
|
|
531
|
+
class: "config-read",
|
|
532
|
+
name: read.name,
|
|
533
|
+
defaulted: read.defaulted
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function envReadAt(node) {
|
|
538
|
+
if (node.type === "element_reference") {
|
|
539
|
+
return elementRead(node);
|
|
540
|
+
}
|
|
541
|
+
if (node.type === "call") {
|
|
542
|
+
return fetchRead(node);
|
|
543
|
+
}
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
function elementRead(node) {
|
|
547
|
+
const object2 = field(node, "object");
|
|
548
|
+
if (object2 === null || !isEnv(object2) || isAssignedTo(node)) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const index = node.namedChildren.find(
|
|
552
|
+
(child) => child !== null && child.id !== object2.id
|
|
553
|
+
);
|
|
554
|
+
const name = index === void 0 ? null : stringLiteralValue(index);
|
|
555
|
+
if (name === null) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
return { name, defaulted: isDefaultedAt(node) };
|
|
559
|
+
}
|
|
560
|
+
function fetchRead(node) {
|
|
561
|
+
const receiver = field(node, "receiver");
|
|
562
|
+
if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
const { positional } = readCallArgs(field(node, "arguments"));
|
|
566
|
+
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
567
|
+
if (name === null) {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
const hasDefault = positional.length > 1 || field(node, "block") !== null;
|
|
571
|
+
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
572
|
+
}
|
|
573
|
+
function isEnv(node) {
|
|
574
|
+
if (node.type === "constant") {
|
|
575
|
+
return node.text === "ENV";
|
|
576
|
+
}
|
|
577
|
+
return node.type === "scope_resolution" && field(node, "scope") === null && field(node, "name")?.text === "ENV";
|
|
578
|
+
}
|
|
579
|
+
function isAssignedTo(node) {
|
|
580
|
+
const parent = node.parent;
|
|
581
|
+
return parent !== null && (parent.type === "assignment" || parent.type === "operator_assignment") && field(parent, "left")?.id === node.id;
|
|
582
|
+
}
|
|
583
|
+
function isDefaultedAt(node) {
|
|
584
|
+
let child = node;
|
|
585
|
+
let parent = node.parent;
|
|
586
|
+
while (parent !== null) {
|
|
587
|
+
if (parent.type === "parenthesized_statements") {
|
|
588
|
+
child = parent;
|
|
589
|
+
parent = parent.parent;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (parent.type !== "binary" || field(parent, "operator")?.text !== "||") {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
if (field(parent, "left")?.id === child.id) {
|
|
596
|
+
return true;
|
|
597
|
+
}
|
|
598
|
+
child = parent;
|
|
599
|
+
parent = parent.parent;
|
|
600
|
+
}
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
|
|
407
604
|
// src/paths/effects.ts
|
|
408
605
|
import { enumerateOrDegrade, sharedGatingConditions } from "@suss/extractor";
|
|
409
606
|
|
|
@@ -656,8 +853,18 @@ function withoutChainLinks(calls) {
|
|
|
656
853
|
}
|
|
657
854
|
return calls.filter((call) => !isLink.has(call.id));
|
|
658
855
|
}
|
|
856
|
+
function calleeMethodName(call) {
|
|
857
|
+
const method = field(call, "method");
|
|
858
|
+
if (method !== null) {
|
|
859
|
+
return method.text;
|
|
860
|
+
}
|
|
861
|
+
return field(call, "receiver") !== null ? "call" : children(call)[0]?.text;
|
|
862
|
+
}
|
|
659
863
|
function isPropertyRead(node) {
|
|
660
|
-
|
|
864
|
+
if (field(node, "receiver") === null || field(node, "arguments") !== null) {
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
return calleeMethodName(node) !== "call";
|
|
661
868
|
}
|
|
662
869
|
function isRaise2(node) {
|
|
663
870
|
const method = field(node, "method") ?? children(node)[0] ?? null;
|
|
@@ -677,11 +884,11 @@ function bodyCalls(node, found = []) {
|
|
|
677
884
|
}
|
|
678
885
|
function calleeText(call) {
|
|
679
886
|
const receiver = field(call, "receiver");
|
|
680
|
-
const method =
|
|
887
|
+
const method = calleeMethodName(call);
|
|
681
888
|
if (method === void 0) {
|
|
682
889
|
return call.text;
|
|
683
890
|
}
|
|
684
|
-
return receiver === null ? method
|
|
891
|
+
return receiver === null ? method : `${receiver.text}.${method}`;
|
|
685
892
|
}
|
|
686
893
|
function argOf(node) {
|
|
687
894
|
const literal2 = LITERAL_ARGS[node.type]?.(node);
|
|
@@ -752,7 +959,7 @@ function invocationEffects(definitionNode) {
|
|
|
752
959
|
}
|
|
753
960
|
|
|
754
961
|
// src/storage.ts
|
|
755
|
-
import { deriveOnDemand, evaluate } from "@suss/datalog";
|
|
962
|
+
import { deriveOnDemand, evaluate as evaluate2 } from "@suss/datalog";
|
|
756
963
|
|
|
757
964
|
// ../../../node_modules/zod/v4/classic/external.js
|
|
758
965
|
var external_exports = {};
|
|
@@ -1434,8 +1641,8 @@ function assertNever(_x) {
|
|
|
1434
1641
|
function assert(_) {
|
|
1435
1642
|
}
|
|
1436
1643
|
function getEnumValues(entries) {
|
|
1437
|
-
const numericValues = Object.values(entries).filter((
|
|
1438
|
-
const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_,
|
|
1644
|
+
const numericValues = Object.values(entries).filter((v4) => typeof v4 === "number");
|
|
1645
|
+
const values = Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v4]) => v4);
|
|
1439
1646
|
return values;
|
|
1440
1647
|
}
|
|
1441
1648
|
function joinValues(array2, separator = "|") {
|
|
@@ -1489,9 +1696,9 @@ function defineLazy(object2, key, getter) {
|
|
|
1489
1696
|
}
|
|
1490
1697
|
return value;
|
|
1491
1698
|
},
|
|
1492
|
-
set(
|
|
1699
|
+
set(v4) {
|
|
1493
1700
|
Object.defineProperty(object2, key, {
|
|
1494
|
-
value:
|
|
1701
|
+
value: v4
|
|
1495
1702
|
// configurable: true,
|
|
1496
1703
|
});
|
|
1497
1704
|
},
|
|
@@ -1520,10 +1727,10 @@ function mergeDefs(...defs) {
|
|
|
1520
1727
|
function cloneDef(schema) {
|
|
1521
1728
|
return mergeDefs(schema._zod.def);
|
|
1522
1729
|
}
|
|
1523
|
-
function getElementAtPath(obj,
|
|
1524
|
-
if (!
|
|
1730
|
+
function getElementAtPath(obj, path7) {
|
|
1731
|
+
if (!path7)
|
|
1525
1732
|
return obj;
|
|
1526
|
-
return
|
|
1733
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
1527
1734
|
}
|
|
1528
1735
|
function promiseAllObject(promisesObj) {
|
|
1529
1736
|
const keys = Object.keys(promisesObj);
|
|
@@ -1932,11 +2139,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1932
2139
|
}
|
|
1933
2140
|
return false;
|
|
1934
2141
|
}
|
|
1935
|
-
function prefixIssues(
|
|
2142
|
+
function prefixIssues(path7, issues) {
|
|
1936
2143
|
return issues.map((iss) => {
|
|
1937
2144
|
var _a3;
|
|
1938
2145
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1939
|
-
iss.path.unshift(
|
|
2146
|
+
iss.path.unshift(path7);
|
|
1940
2147
|
return iss;
|
|
1941
2148
|
});
|
|
1942
2149
|
}
|
|
@@ -2083,16 +2290,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2083
2290
|
}
|
|
2084
2291
|
function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
2085
2292
|
const fieldErrors = { _errors: [] };
|
|
2086
|
-
const processError = (error52,
|
|
2293
|
+
const processError = (error52, path7 = []) => {
|
|
2087
2294
|
for (const issue2 of error52.issues) {
|
|
2088
2295
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2089
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2296
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
2090
2297
|
} else if (issue2.code === "invalid_key") {
|
|
2091
|
-
processError({ issues: issue2.issues }, [...
|
|
2298
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
2092
2299
|
} else if (issue2.code === "invalid_element") {
|
|
2093
|
-
processError({ issues: issue2.issues }, [...
|
|
2300
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
2094
2301
|
} else {
|
|
2095
|
-
const fullpath = [...
|
|
2302
|
+
const fullpath = [...path7, ...issue2.path];
|
|
2096
2303
|
if (fullpath.length === 0) {
|
|
2097
2304
|
fieldErrors._errors.push(mapper(issue2));
|
|
2098
2305
|
} else {
|
|
@@ -2119,17 +2326,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2119
2326
|
}
|
|
2120
2327
|
function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
2121
2328
|
const result = { errors: [] };
|
|
2122
|
-
const processError = (error52,
|
|
2329
|
+
const processError = (error52, path7 = []) => {
|
|
2123
2330
|
var _a3, _b;
|
|
2124
2331
|
for (const issue2 of error52.issues) {
|
|
2125
2332
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
2126
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
2333
|
+
issue2.errors.map((issues) => processError({ issues }, [...path7, ...issue2.path]));
|
|
2127
2334
|
} else if (issue2.code === "invalid_key") {
|
|
2128
|
-
processError({ issues: issue2.issues }, [...
|
|
2335
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
2129
2336
|
} else if (issue2.code === "invalid_element") {
|
|
2130
|
-
processError({ issues: issue2.issues }, [...
|
|
2337
|
+
processError({ issues: issue2.issues }, [...path7, ...issue2.path]);
|
|
2131
2338
|
} else {
|
|
2132
|
-
const fullpath = [...
|
|
2339
|
+
const fullpath = [...path7, ...issue2.path];
|
|
2133
2340
|
if (fullpath.length === 0) {
|
|
2134
2341
|
result.errors.push(mapper(issue2));
|
|
2135
2342
|
continue;
|
|
@@ -2161,8 +2368,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
2161
2368
|
}
|
|
2162
2369
|
function toDotPath(_path) {
|
|
2163
2370
|
const segs = [];
|
|
2164
|
-
const
|
|
2165
|
-
for (const seg of
|
|
2371
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
2372
|
+
for (const seg of path7) {
|
|
2166
2373
|
if (typeof seg === "number")
|
|
2167
2374
|
segs.push(`[${seg}]`);
|
|
2168
2375
|
else if (typeof seg === "symbol")
|
|
@@ -3178,10 +3385,10 @@ var $ZodUUID = /* @__PURE__ */ $constructor("$ZodUUID", (inst, def) => {
|
|
|
3178
3385
|
v7: 7,
|
|
3179
3386
|
v8: 8
|
|
3180
3387
|
};
|
|
3181
|
-
const
|
|
3182
|
-
if (
|
|
3388
|
+
const v4 = versionMap[def.version];
|
|
3389
|
+
if (v4 === void 0)
|
|
3183
3390
|
throw new Error(`Invalid UUID version: "${def.version}"`);
|
|
3184
|
-
def.pattern ?? (def.pattern = uuid(
|
|
3391
|
+
def.pattern ?? (def.pattern = uuid(v4));
|
|
3185
3392
|
} else
|
|
3186
3393
|
def.pattern ?? (def.pattern = uuid());
|
|
3187
3394
|
$ZodStringFormat.init(inst, def);
|
|
@@ -3786,8 +3993,8 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
3786
3993
|
const field2 = shape[key]._zod;
|
|
3787
3994
|
if (field2.values) {
|
|
3788
3995
|
propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());
|
|
3789
|
-
for (const
|
|
3790
|
-
propValues[key].add(
|
|
3996
|
+
for (const v4 of field2.values)
|
|
3997
|
+
propValues[key].add(v4);
|
|
3791
3998
|
}
|
|
3792
3999
|
}
|
|
3793
4000
|
return propValues;
|
|
@@ -4082,10 +4289,10 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
|
|
|
4082
4289
|
const pv = option._zod.propValues;
|
|
4083
4290
|
if (!pv || Object.keys(pv).length === 0)
|
|
4084
4291
|
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
|
|
4085
|
-
for (const [k,
|
|
4292
|
+
for (const [k, v4] of Object.entries(pv)) {
|
|
4086
4293
|
if (!propValues[k])
|
|
4087
4294
|
propValues[k] = /* @__PURE__ */ new Set();
|
|
4088
|
-
for (const val of
|
|
4295
|
+
for (const val of v4) {
|
|
4089
4296
|
propValues[k].add(val);
|
|
4090
4297
|
}
|
|
4091
4298
|
}
|
|
@@ -4099,11 +4306,11 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
|
|
|
4099
4306
|
const values = o._zod.propValues?.[def.discriminator];
|
|
4100
4307
|
if (!values || values.size === 0)
|
|
4101
4308
|
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o)}"`);
|
|
4102
|
-
for (const
|
|
4103
|
-
if (map2.has(
|
|
4104
|
-
throw new Error(`Duplicate discriminator value "${String(
|
|
4309
|
+
for (const v4 of values) {
|
|
4310
|
+
if (map2.has(v4)) {
|
|
4311
|
+
throw new Error(`Duplicate discriminator value "${String(v4)}"`);
|
|
4105
4312
|
}
|
|
4106
|
-
map2.set(
|
|
4313
|
+
map2.set(v4, o);
|
|
4107
4314
|
}
|
|
4108
4315
|
}
|
|
4109
4316
|
return map2;
|
|
@@ -4742,8 +4949,8 @@ var $ZodPrefault = /* @__PURE__ */ $constructor("$ZodPrefault", (inst, def) => {
|
|
|
4742
4949
|
var $ZodNonOptional = /* @__PURE__ */ $constructor("$ZodNonOptional", (inst, def) => {
|
|
4743
4950
|
$ZodType.init(inst, def);
|
|
4744
4951
|
defineLazy(inst._zod, "values", () => {
|
|
4745
|
-
const
|
|
4746
|
-
return
|
|
4952
|
+
const v4 = def.innerType._zod.values;
|
|
4953
|
+
return v4 ? new Set([...v4].filter((x) => x !== void 0)) : void 0;
|
|
4747
4954
|
});
|
|
4748
4955
|
inst._zod.parse = (payload, ctx) => {
|
|
4749
4956
|
const result = def.innerType._zod.run(payload, ctx);
|
|
@@ -7140,7 +7347,7 @@ var error17 = () => {
|
|
|
7140
7347
|
if (issue2.values.length === 1) {
|
|
7141
7348
|
return `\u05E2\u05E8\u05DA \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF: \u05D4\u05E2\u05E8\u05DA \u05D7\u05D9\u05D9\u05D1 \u05DC\u05D4\u05D9\u05D5\u05EA ${stringifyPrimitive(issue2.values[0])}`;
|
|
7142
7349
|
}
|
|
7143
|
-
const stringified = issue2.values.map((
|
|
7350
|
+
const stringified = issue2.values.map((v4) => stringifyPrimitive(v4));
|
|
7144
7351
|
if (issue2.values.length === 2) {
|
|
7145
7352
|
return `\u05E2\u05E8\u05DA \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF: \u05D4\u05D0\u05E4\u05E9\u05E8\u05D5\u05D9\u05D5\u05EA \u05D4\u05DE\u05EA\u05D0\u05D9\u05DE\u05D5\u05EA \u05D4\u05DF ${stringified[0]} \u05D0\u05D5 ${stringified[1]}`;
|
|
7146
7353
|
}
|
|
@@ -11895,7 +12102,7 @@ function _set(Class2, valueType, params) {
|
|
|
11895
12102
|
}
|
|
11896
12103
|
// @__NO_SIDE_EFFECTS__
|
|
11897
12104
|
function _enum(Class2, values, params) {
|
|
11898
|
-
const entries = Array.isArray(values) ? Object.fromEntries(values.map((
|
|
12105
|
+
const entries = Array.isArray(values) ? Object.fromEntries(values.map((v4) => [v4, v4])) : values;
|
|
11899
12106
|
return new Class2({
|
|
11900
12107
|
type: "enum",
|
|
11901
12108
|
entries,
|
|
@@ -12100,8 +12307,8 @@ function _stringbool(Classes, _params) {
|
|
|
12100
12307
|
let truthyArray = params.truthy ?? ["true", "1", "yes", "on", "y", "enabled"];
|
|
12101
12308
|
let falsyArray = params.falsy ?? ["false", "0", "no", "off", "n", "disabled"];
|
|
12102
12309
|
if (params.case !== "sensitive") {
|
|
12103
|
-
truthyArray = truthyArray.map((
|
|
12104
|
-
falsyArray = falsyArray.map((
|
|
12310
|
+
truthyArray = truthyArray.map((v4) => typeof v4 === "string" ? v4.toLowerCase() : v4);
|
|
12311
|
+
falsyArray = falsyArray.map((v4) => typeof v4 === "string" ? v4.toLowerCase() : v4);
|
|
12105
12312
|
}
|
|
12106
12313
|
const truthySet = new Set(truthyArray);
|
|
12107
12314
|
const falsySet = new Set(falsyArray);
|
|
@@ -12643,9 +12850,9 @@ var dateProcessor = (_schema, ctx, _json, _params) => {
|
|
|
12643
12850
|
var enumProcessor = (schema, _ctx, json2, _params) => {
|
|
12644
12851
|
const def = schema._zod.def;
|
|
12645
12852
|
const values = getEnumValues(def.entries);
|
|
12646
|
-
if (values.every((
|
|
12853
|
+
if (values.every((v4) => typeof v4 === "number"))
|
|
12647
12854
|
json2.type = "number";
|
|
12648
|
-
if (values.every((
|
|
12855
|
+
if (values.every((v4) => typeof v4 === "string"))
|
|
12649
12856
|
json2.type = "string";
|
|
12650
12857
|
json2.enum = values;
|
|
12651
12858
|
};
|
|
@@ -12678,13 +12885,13 @@ var literalProcessor = (schema, ctx, json2, _params) => {
|
|
|
12678
12885
|
json2.const = val;
|
|
12679
12886
|
}
|
|
12680
12887
|
} else {
|
|
12681
|
-
if (vals.every((
|
|
12888
|
+
if (vals.every((v4) => typeof v4 === "number"))
|
|
12682
12889
|
json2.type = "number";
|
|
12683
|
-
if (vals.every((
|
|
12890
|
+
if (vals.every((v4) => typeof v4 === "string"))
|
|
12684
12891
|
json2.type = "string";
|
|
12685
|
-
if (vals.every((
|
|
12892
|
+
if (vals.every((v4) => typeof v4 === "boolean"))
|
|
12686
12893
|
json2.type = "boolean";
|
|
12687
|
-
if (vals.every((
|
|
12894
|
+
if (vals.every((v4) => v4 === null))
|
|
12688
12895
|
json2.type = "null";
|
|
12689
12896
|
json2.enum = vals;
|
|
12690
12897
|
}
|
|
@@ -12782,11 +12989,11 @@ var objectProcessor = (schema, ctx, _json, params) => {
|
|
|
12782
12989
|
}
|
|
12783
12990
|
const allKeys = new Set(Object.keys(shape));
|
|
12784
12991
|
const requiredKeys = new Set([...allKeys].filter((key) => {
|
|
12785
|
-
const
|
|
12992
|
+
const v4 = def.shape[key]._zod;
|
|
12786
12993
|
if (ctx.io === "input") {
|
|
12787
|
-
return
|
|
12994
|
+
return v4.optin === void 0;
|
|
12788
12995
|
} else {
|
|
12789
|
-
return
|
|
12996
|
+
return v4.optout === void 0;
|
|
12790
12997
|
}
|
|
12791
12998
|
}));
|
|
12792
12999
|
if (requiredKeys.size > 0) {
|
|
@@ -12906,7 +13113,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
|
|
|
12906
13113
|
}
|
|
12907
13114
|
const keyValues = keyType._zod.values;
|
|
12908
13115
|
if (keyValues) {
|
|
12909
|
-
const validKeyValues = [...keyValues].filter((
|
|
13116
|
+
const validKeyValues = [...keyValues].filter((v4) => typeof v4 === "string" || typeof v4 === "number");
|
|
12910
13117
|
if (validKeyValues.length > 0) {
|
|
12911
13118
|
json2.required = validKeyValues;
|
|
12912
13119
|
}
|
|
@@ -13471,12 +13678,12 @@ function _installLazyMethods(inst, group, methods) {
|
|
|
13471
13678
|
});
|
|
13472
13679
|
return bound;
|
|
13473
13680
|
},
|
|
13474
|
-
set(
|
|
13681
|
+
set(v4) {
|
|
13475
13682
|
Object.defineProperty(this, key, {
|
|
13476
13683
|
configurable: true,
|
|
13477
13684
|
writable: true,
|
|
13478
13685
|
enumerable: true,
|
|
13479
|
-
value:
|
|
13686
|
+
value: v4
|
|
13480
13687
|
});
|
|
13481
13688
|
}
|
|
13482
13689
|
});
|
|
@@ -14364,7 +14571,7 @@ var ZodEnum = /* @__PURE__ */ $constructor("ZodEnum", (inst, def) => {
|
|
|
14364
14571
|
};
|
|
14365
14572
|
});
|
|
14366
14573
|
function _enum2(values, params) {
|
|
14367
|
-
const entries = Array.isArray(values) ? Object.fromEntries(values.map((
|
|
14574
|
+
const entries = Array.isArray(values) ? Object.fromEntries(values.map((v4) => [v4, v4])) : values;
|
|
14368
14575
|
return new ZodEnum({
|
|
14369
14576
|
type: "enum",
|
|
14370
14577
|
entries,
|
|
@@ -14854,13 +15061,13 @@ function resolveRef(ref, ctx) {
|
|
|
14854
15061
|
if (!ref.startsWith("#")) {
|
|
14855
15062
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
14856
15063
|
}
|
|
14857
|
-
const
|
|
14858
|
-
if (
|
|
15064
|
+
const path7 = ref.slice(1).split("/").filter(Boolean);
|
|
15065
|
+
if (path7.length === 0) {
|
|
14859
15066
|
return ctx.rootSchema;
|
|
14860
15067
|
}
|
|
14861
15068
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
14862
|
-
if (
|
|
14863
|
-
const key =
|
|
15069
|
+
if (path7[0] === defsKey) {
|
|
15070
|
+
const key = path7[1];
|
|
14864
15071
|
if (!key || !ctx.defs[key]) {
|
|
14865
15072
|
throw new Error(`Reference not found: ${ref}`);
|
|
14866
15073
|
}
|
|
@@ -14918,10 +15125,10 @@ function convertBaseSchema(schema, ctx) {
|
|
|
14918
15125
|
if (enumValues.length === 1) {
|
|
14919
15126
|
return z.literal(enumValues[0]);
|
|
14920
15127
|
}
|
|
14921
|
-
if (enumValues.every((
|
|
15128
|
+
if (enumValues.every((v4) => typeof v4 === "string")) {
|
|
14922
15129
|
return z.enum(enumValues);
|
|
14923
15130
|
}
|
|
14924
|
-
const literalSchemas = enumValues.map((
|
|
15131
|
+
const literalSchemas = enumValues.map((v4) => z.literal(v4));
|
|
14925
15132
|
if (literalSchemas.length < 2) {
|
|
14926
15133
|
return literalSchemas[0];
|
|
14927
15134
|
}
|
|
@@ -15620,8 +15827,8 @@ function baseUrlReference(semantics) {
|
|
|
15620
15827
|
const label = OPENING_HOLE.exec(semantics.path ?? "")?.[1];
|
|
15621
15828
|
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
15622
15829
|
}
|
|
15623
|
-
function normalizePath(
|
|
15624
|
-
let normalized =
|
|
15830
|
+
function normalizePath(path7) {
|
|
15831
|
+
let normalized = path7.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
15625
15832
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
15626
15833
|
normalized = normalized.slice(0, -1);
|
|
15627
15834
|
}
|
|
@@ -15631,8 +15838,8 @@ function normalizePath(path4) {
|
|
|
15631
15838
|
);
|
|
15632
15839
|
return normalized;
|
|
15633
15840
|
}
|
|
15634
|
-
function pathShape(
|
|
15635
|
-
return normalizePath(
|
|
15841
|
+
function pathShape(path7) {
|
|
15842
|
+
return normalizePath(path7).replace(/\{[^}]*\}/g, "{}");
|
|
15636
15843
|
}
|
|
15637
15844
|
function methodsAgree(a, b) {
|
|
15638
15845
|
if (a === null || b === null) {
|
|
@@ -15710,8 +15917,8 @@ var restSemantics = defineBoundarySemantics({
|
|
|
15710
15917
|
return null;
|
|
15711
15918
|
}
|
|
15712
15919
|
const method = semantics.method === null ? "ANY" : semantics.method.toUpperCase();
|
|
15713
|
-
const
|
|
15714
|
-
return `${method} ${
|
|
15920
|
+
const path7 = semantics.path === null ? "?" : normalizePath(semantics.path);
|
|
15921
|
+
return `${method} ${path7}`;
|
|
15715
15922
|
},
|
|
15716
15923
|
/**
|
|
15717
15924
|
* A route with both halves stated decides for itself: the method
|
|
@@ -15720,14 +15927,14 @@ var restSemantics = defineBoundarySemantics({
|
|
|
15720
15927
|
* half might still be the one that handles the request, and nothing
|
|
15721
15928
|
* here can settle that, so it abstains instead of refusing.
|
|
15722
15929
|
*/
|
|
15723
|
-
servesRequest(semantics, method,
|
|
15930
|
+
servesRequest(semantics, method, path7) {
|
|
15724
15931
|
if (semantics.method === null || semantics.path === null) {
|
|
15725
15932
|
return "unknown";
|
|
15726
15933
|
}
|
|
15727
15934
|
if (!methodsAgree(semantics.method, method.toUpperCase())) {
|
|
15728
15935
|
return "nomatch";
|
|
15729
15936
|
}
|
|
15730
|
-
return routePathAdmits(semantics.path,
|
|
15937
|
+
return routePathAdmits(semantics.path, path7) ? "match" : "nomatch";
|
|
15731
15938
|
},
|
|
15732
15939
|
/**
|
|
15733
15940
|
* A route the registration pattern admits is inside it. A route
|
|
@@ -16146,7 +16353,291 @@ function storageBinding(opts) {
|
|
|
16146
16353
|
|
|
16147
16354
|
// ../../resolution/dist/index.js
|
|
16148
16355
|
import { ruleLabel } from "@suss/datalog";
|
|
16356
|
+
import { Database, evaluate, proofOf, witnesses } from "@suss/datalog";
|
|
16149
16357
|
import { constant, lit, rule, variable as v } from "@suss/datalog";
|
|
16358
|
+
function exportTrail(proof, describe3) {
|
|
16359
|
+
if (proof.kind !== "derived") {
|
|
16360
|
+
return [];
|
|
16361
|
+
}
|
|
16362
|
+
const name = ruleLabel(proof.rule);
|
|
16363
|
+
if (name === "re-export") {
|
|
16364
|
+
const [m, n, m2, n2] = proof.premises[0].tuple;
|
|
16365
|
+
return [
|
|
16366
|
+
`${describe3(m)} takes ${String(n)} from ${describe3(m2)}, where it is called ${String(n2)}`,
|
|
16367
|
+
...exportTrail(proof.premises[1], describe3)
|
|
16368
|
+
];
|
|
16369
|
+
}
|
|
16370
|
+
if (name === "re-export all") {
|
|
16371
|
+
const [m, m2] = proof.premises[0].tuple;
|
|
16372
|
+
return [
|
|
16373
|
+
`${describe3(m)} forwards everything ${describe3(m2)} exports`,
|
|
16374
|
+
...exportTrail(proof.premises[1], describe3)
|
|
16375
|
+
];
|
|
16376
|
+
}
|
|
16377
|
+
return [];
|
|
16378
|
+
}
|
|
16379
|
+
function argumentReason(proof, describe3) {
|
|
16380
|
+
const [, p, a] = proof.tuple;
|
|
16381
|
+
if (proof.kind !== "derived") {
|
|
16382
|
+
return `${describe3(p)} is a parameter, and a call passes it ${describe3(a)}`;
|
|
16383
|
+
}
|
|
16384
|
+
const f = proof.premises[0].tuple[0];
|
|
16385
|
+
return `${describe3(p)} is a parameter of ${describe3(f)}, and a call passes it ${describe3(a)}`;
|
|
16386
|
+
}
|
|
16387
|
+
var DEFAULT_PHRASES = {
|
|
16388
|
+
alias: ({ tuple: tuple2, describe: describe3 }) => ({
|
|
16389
|
+
reason: `${describe3(tuple2[0])} is declared as ${describe3(tuple2[1])}`
|
|
16390
|
+
}),
|
|
16391
|
+
"last write": ({ tuple: tuple2, describe: describe3 }) => ({
|
|
16392
|
+
reason: `${describe3(tuple2[0])} is written more than once, and the last write leaves it as ${describe3(tuple2[1])}`
|
|
16393
|
+
}),
|
|
16394
|
+
fallback: ({ tuple: tuple2, describe: describe3 }) => ({
|
|
16395
|
+
reason: `${describe3(tuple2[0])} is a fallback expression, and ${describe3(tuple2[1])} is the branch that resolves`
|
|
16396
|
+
}),
|
|
16397
|
+
import: ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
16398
|
+
const [, module, name] = premises[0].tuple;
|
|
16399
|
+
return {
|
|
16400
|
+
reason: `${describe3(tuple2[0])} is imported from ${describe3(module)} under the name ${String(name)}`,
|
|
16401
|
+
notes: exportTrail(premises[1], describe3)
|
|
16402
|
+
};
|
|
16403
|
+
},
|
|
16404
|
+
argument: ({ premises, describe: describe3 }) => ({
|
|
16405
|
+
reason: argumentReason(premises[0], describe3)
|
|
16406
|
+
}),
|
|
16407
|
+
"property read": ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
16408
|
+
const [, object2, name] = premises[0].tuple;
|
|
16409
|
+
const contains = premises[2];
|
|
16410
|
+
const inherited = contains.kind === "derived" && contains.premises.length > 1;
|
|
16411
|
+
return {
|
|
16412
|
+
reason: `${describe3(tuple2[0])} reads ${String(name)} off ${describe3(object2)}, which contains ${describe3(tuple2[1])}`,
|
|
16413
|
+
notes: inherited ? [`${String(name)} comes from a base class`] : []
|
|
16414
|
+
};
|
|
16415
|
+
},
|
|
16416
|
+
"class instance": ({ tuple: tuple2, premises, describe: describe3, inline }) => ({
|
|
16417
|
+
reason: `${describe3(tuple2[0])} makes an instance of ${describe3(tuple2[1])}`,
|
|
16418
|
+
notes: inline(premises[1])
|
|
16419
|
+
}),
|
|
16420
|
+
"factory unwrap": ({ tuple: tuple2, premises, describe: describe3, inline }) => {
|
|
16421
|
+
const factory = premises[1].tuple[1];
|
|
16422
|
+
return {
|
|
16423
|
+
reason: `${describe3(tuple2[0])} calls ${describe3(factory)}, a factory that passes its argument through, so it comes down to ${describe3(tuple2[1])}`,
|
|
16424
|
+
notes: inline(premises[1])
|
|
16425
|
+
};
|
|
16426
|
+
},
|
|
16427
|
+
"declared wrapper": ({ tuple: tuple2, premises, describe: describe3 }) => {
|
|
16428
|
+
const callee = String(premises[0].tuple[1]);
|
|
16429
|
+
const module = String(premises[2].tuple[1]);
|
|
16430
|
+
const argument = String(premises[1].tuple[1]);
|
|
16431
|
+
return {
|
|
16432
|
+
reason: `${describe3(tuple2[0])} calls ${callee} from ${module}, which passes argument ${argument} through, so it comes down to ${describe3(tuple2[1])}`,
|
|
16433
|
+
assumptions: [
|
|
16434
|
+
`a pack declares that ${callee} from ${module} passes argument ${argument} through to its result`
|
|
16435
|
+
]
|
|
16436
|
+
};
|
|
16437
|
+
},
|
|
16438
|
+
"call result": ({ tuple: tuple2, premises, describe: describe3, inline }) => {
|
|
16439
|
+
const invokes = premises[0];
|
|
16440
|
+
const invoked = invokes.tuple[1];
|
|
16441
|
+
const calleeWalks = invokes.kind === "derived" ? invokes.premises.filter(
|
|
16442
|
+
(premise) => premise.kind === "derived" && (premise.relation === "comesTo" || premise.relation === "givesBack")
|
|
16443
|
+
) : [];
|
|
16444
|
+
return {
|
|
16445
|
+
reason: `${describe3(tuple2[0])} runs ${describe3(invoked)}, which returns ${describe3(tuple2[1])}`,
|
|
16446
|
+
notes: calleeWalks.flatMap((premise) => inline(premise))
|
|
16447
|
+
};
|
|
16448
|
+
}
|
|
16449
|
+
};
|
|
16450
|
+
var CHAIN_RELATIONS = /* @__PURE__ */ new Set([
|
|
16451
|
+
"resolves",
|
|
16452
|
+
"comesTo",
|
|
16453
|
+
"givesBack",
|
|
16454
|
+
"isWrittenAs",
|
|
16455
|
+
"comesFrom",
|
|
16456
|
+
"reaches",
|
|
16457
|
+
"stepsTo"
|
|
16458
|
+
]);
|
|
16459
|
+
function inlineChain(proof, state) {
|
|
16460
|
+
const explained = explainResolutionProof(proof, {
|
|
16461
|
+
describe: state.describe,
|
|
16462
|
+
phrases: state.phrases
|
|
16463
|
+
});
|
|
16464
|
+
if (explained === null || explained.steps.length === 0) {
|
|
16465
|
+
return [];
|
|
16466
|
+
}
|
|
16467
|
+
if (explained.truncated) {
|
|
16468
|
+
state.truncated = true;
|
|
16469
|
+
}
|
|
16470
|
+
return explained.steps.map((step) => step.reason);
|
|
16471
|
+
}
|
|
16472
|
+
function stepFrom(proof, state) {
|
|
16473
|
+
const name = ruleLabel(proof.rule);
|
|
16474
|
+
const phrase = state.phrases[name];
|
|
16475
|
+
const from = proof.tuple[0];
|
|
16476
|
+
const to = proof.tuple[1];
|
|
16477
|
+
if (phrase === void 0) {
|
|
16478
|
+
return {
|
|
16479
|
+
from,
|
|
16480
|
+
to,
|
|
16481
|
+
rule: name,
|
|
16482
|
+
reason: `${state.describe(from)} steps to ${state.describe(to)} (${name})`,
|
|
16483
|
+
notes: [],
|
|
16484
|
+
assumptions: []
|
|
16485
|
+
};
|
|
16486
|
+
}
|
|
16487
|
+
const said = phrase({
|
|
16488
|
+
tuple: proof.tuple,
|
|
16489
|
+
premises: proof.premises,
|
|
16490
|
+
describe: state.describe,
|
|
16491
|
+
inline: (nested) => inlineChain(nested, state)
|
|
16492
|
+
});
|
|
16493
|
+
return {
|
|
16494
|
+
from,
|
|
16495
|
+
to,
|
|
16496
|
+
rule: name,
|
|
16497
|
+
reason: said.reason,
|
|
16498
|
+
notes: said.notes ?? [],
|
|
16499
|
+
assumptions: said.assumptions ?? []
|
|
16500
|
+
};
|
|
16501
|
+
}
|
|
16502
|
+
function importStep(proof, state) {
|
|
16503
|
+
const [x, module, name] = proof.tuple;
|
|
16504
|
+
return {
|
|
16505
|
+
from: x,
|
|
16506
|
+
to: module,
|
|
16507
|
+
rule: "import",
|
|
16508
|
+
reason: `${state.describe(x)} is imported from ${state.describe(module)} under the name ${String(name)}`,
|
|
16509
|
+
notes: [],
|
|
16510
|
+
assumptions: []
|
|
16511
|
+
};
|
|
16512
|
+
}
|
|
16513
|
+
var flattenInto = (proof, state) => {
|
|
16514
|
+
if (proof.kind === "truncated") {
|
|
16515
|
+
state.truncated = true;
|
|
16516
|
+
return;
|
|
16517
|
+
}
|
|
16518
|
+
if (proof.kind !== "derived") {
|
|
16519
|
+
return;
|
|
16520
|
+
}
|
|
16521
|
+
const relation = proof.relation;
|
|
16522
|
+
if (relation === "stepsTo") {
|
|
16523
|
+
state.steps.push(stepFrom(proof, state));
|
|
16524
|
+
return;
|
|
16525
|
+
}
|
|
16526
|
+
if (relation === "reaches") {
|
|
16527
|
+
for (const p of proof.premises) {
|
|
16528
|
+
flattenInto(p, state);
|
|
16529
|
+
}
|
|
16530
|
+
return;
|
|
16531
|
+
}
|
|
16532
|
+
if (relation === "comesFrom") {
|
|
16533
|
+
if (proof.premises.length === 2) {
|
|
16534
|
+
flattenInto(proof.premises[0], state);
|
|
16535
|
+
state.steps.push(importStep(proof.premises[1], state));
|
|
16536
|
+
} else {
|
|
16537
|
+
state.steps.push(importStep(proof.premises[0], state));
|
|
16538
|
+
}
|
|
16539
|
+
return;
|
|
16540
|
+
}
|
|
16541
|
+
for (const p of proof.premises) {
|
|
16542
|
+
if (p.kind === "derived" && (p.relation === "reaches" || p.relation === "comesTo")) {
|
|
16543
|
+
flattenInto(p, state);
|
|
16544
|
+
}
|
|
16545
|
+
if (p.kind === "truncated") {
|
|
16546
|
+
state.truncated = true;
|
|
16547
|
+
}
|
|
16548
|
+
}
|
|
16549
|
+
};
|
|
16550
|
+
function explainResolutionProof(proof, options) {
|
|
16551
|
+
if (proof.kind === "absence") {
|
|
16552
|
+
return null;
|
|
16553
|
+
}
|
|
16554
|
+
if (proof.kind === "derived" && !CHAIN_RELATIONS.has(proof.relation)) {
|
|
16555
|
+
return null;
|
|
16556
|
+
}
|
|
16557
|
+
const state = {
|
|
16558
|
+
steps: [],
|
|
16559
|
+
truncated: proof.kind === "truncated",
|
|
16560
|
+
describe: options.describe,
|
|
16561
|
+
phrases: { ...DEFAULT_PHRASES, ...options.phrases }
|
|
16562
|
+
};
|
|
16563
|
+
flattenInto(proof, state);
|
|
16564
|
+
const atoms = proof.kind === "fact" || proof.kind === "truncated" ? [proof.tuple[0]] : [proof.tuple[0], ...state.steps.map((step) => step.to)];
|
|
16565
|
+
return {
|
|
16566
|
+
atoms,
|
|
16567
|
+
steps: state.steps,
|
|
16568
|
+
assumptions: state.steps.flatMap((step) => step.assumptions),
|
|
16569
|
+
truncated: state.truncated
|
|
16570
|
+
};
|
|
16571
|
+
}
|
|
16572
|
+
function renderExplanation(explanation, describe3) {
|
|
16573
|
+
const lines = [];
|
|
16574
|
+
if (explanation.steps.length > 0) {
|
|
16575
|
+
lines.push(explanation.atoms.map((atom) => describe3(atom)).join(" -> "));
|
|
16576
|
+
}
|
|
16577
|
+
for (const step of explanation.steps) {
|
|
16578
|
+
lines.push(` ${step.reason}`);
|
|
16579
|
+
for (const note of step.notes) {
|
|
16580
|
+
lines.push(` ${note}`);
|
|
16581
|
+
}
|
|
16582
|
+
}
|
|
16583
|
+
for (const assumption of explanation.assumptions) {
|
|
16584
|
+
lines.push(` assuming ${assumption}`);
|
|
16585
|
+
}
|
|
16586
|
+
if (explanation.truncated) {
|
|
16587
|
+
lines.push(" the chain goes on past the proof depth cap");
|
|
16588
|
+
}
|
|
16589
|
+
return lines;
|
|
16590
|
+
}
|
|
16591
|
+
function explainResolvedKey(options) {
|
|
16592
|
+
const { db, rules, key, locate, displayPath } = options;
|
|
16593
|
+
const proofDb = new Database();
|
|
16594
|
+
let baseFacts = 0;
|
|
16595
|
+
for (const relation of db.relationNames()) {
|
|
16596
|
+
for (const tuple2 of db.facts(relation)) {
|
|
16597
|
+
proofDb.add(relation, tuple2);
|
|
16598
|
+
baseFacts++;
|
|
16599
|
+
}
|
|
16600
|
+
}
|
|
16601
|
+
const started = performance.now();
|
|
16602
|
+
evaluate(proofDb, rules, witnesses);
|
|
16603
|
+
const evaluateMs = performance.now() - started;
|
|
16604
|
+
const derivedFacts = proofDb.relationNames().reduce((count, relation) => count + proofDb.size(relation), 0) - baseFacts;
|
|
16605
|
+
const targets = new Set(
|
|
16606
|
+
proofDb.lookup("resolves", 0, key).map((tuple2) => String(tuple2[1]))
|
|
16607
|
+
);
|
|
16608
|
+
if (targets.size !== 1) {
|
|
16609
|
+
return null;
|
|
16610
|
+
}
|
|
16611
|
+
const target = [...targets][0];
|
|
16612
|
+
const proof = proofOf(
|
|
16613
|
+
proofDb,
|
|
16614
|
+
"resolves",
|
|
16615
|
+
[key, target],
|
|
16616
|
+
options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth }
|
|
16617
|
+
);
|
|
16618
|
+
const describe3 = (atom) => {
|
|
16619
|
+
const location = locate(String(atom));
|
|
16620
|
+
return location === null ? displayPath(String(atom)) : `${location.name} (${location.file}:${location.line})`;
|
|
16621
|
+
};
|
|
16622
|
+
const explanation = explainResolutionProof(proof, {
|
|
16623
|
+
describe: describe3,
|
|
16624
|
+
...options.phrases === void 0 ? {} : { phrases: options.phrases }
|
|
16625
|
+
});
|
|
16626
|
+
if (explanation === null) {
|
|
16627
|
+
return null;
|
|
16628
|
+
}
|
|
16629
|
+
const targetLocation = locate(target);
|
|
16630
|
+
if (targetLocation === null) {
|
|
16631
|
+
return null;
|
|
16632
|
+
}
|
|
16633
|
+
return {
|
|
16634
|
+
explanation,
|
|
16635
|
+
chain: explanation.atoms.map(describe3),
|
|
16636
|
+
lines: renderExplanation(explanation, describe3),
|
|
16637
|
+
target: targetLocation,
|
|
16638
|
+
stats: { baseFacts, derivedFacts, evaluateMs }
|
|
16639
|
+
};
|
|
16640
|
+
}
|
|
16150
16641
|
var VALUE_STEP = constant("value");
|
|
16151
16642
|
var NAMESPACE_IMPORT_NAME = "*";
|
|
16152
16643
|
var NAMESPACE_IMPORT = constant(NAMESPACE_IMPORT_NAME);
|
|
@@ -16338,6 +16829,18 @@ var RESOLUTION_RULES = [
|
|
|
16338
16829
|
[v("x"), v("z")],
|
|
16339
16830
|
[lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
|
|
16340
16831
|
),
|
|
16832
|
+
// A call is written as whatever its callee's return value is written
|
|
16833
|
+
// as. Unlike `comesTo`, this has nothing to protect: which expression
|
|
16834
|
+
// a value was written as does not compete with unwrapping it.
|
|
16835
|
+
rule(
|
|
16836
|
+
"isWrittenAs",
|
|
16837
|
+
[v("x"), v("z")],
|
|
16838
|
+
[
|
|
16839
|
+
lit("invokes", v("x"), v("f")),
|
|
16840
|
+
lit("returnsValue", v("f"), v("ret")),
|
|
16841
|
+
lit("isWrittenAs", v("ret"), v("z"))
|
|
16842
|
+
]
|
|
16843
|
+
),
|
|
16341
16844
|
// What one call site put in a parameter, told apart from what the
|
|
16342
16845
|
// other callers passed.
|
|
16343
16846
|
rule(
|
|
@@ -16899,11 +17402,11 @@ var ANCESTRY_PROGRAM = deriveOnDemand(
|
|
|
16899
17402
|
);
|
|
16900
17403
|
function reachesBase(facts, classKey, bases) {
|
|
16901
17404
|
facts.add("wantedAncestry", [classKey]);
|
|
16902
|
-
|
|
17405
|
+
evaluate2(facts, ANCESTRY_PROGRAM.rules);
|
|
16903
17406
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
16904
17407
|
}
|
|
16905
|
-
function classBehind(facts,
|
|
16906
|
-
const wanted = `#${
|
|
17408
|
+
function classBehind(facts, constant3) {
|
|
17409
|
+
const wanted = `#${constant3.text}`;
|
|
16907
17410
|
const bound = new Set(
|
|
16908
17411
|
facts.facts("binds").filter((row) => String(row[0]).endsWith(wanted)).map((row) => String(row[1]))
|
|
16909
17412
|
);
|
|
@@ -16946,11 +17449,11 @@ function storageEffects(calls, options) {
|
|
|
16946
17449
|
if (partOfOne.has(call.id)) {
|
|
16947
17450
|
continue;
|
|
16948
17451
|
}
|
|
16949
|
-
const
|
|
16950
|
-
if (
|
|
17452
|
+
const constant3 = rootConstant(call);
|
|
17453
|
+
if (constant3 === null) {
|
|
16951
17454
|
continue;
|
|
16952
17455
|
}
|
|
16953
|
-
const classKey = classBehind(options.facts,
|
|
17456
|
+
const classKey = classBehind(options.facts, constant3);
|
|
16954
17457
|
if (classKey === void 0) {
|
|
16955
17458
|
continue;
|
|
16956
17459
|
}
|
|
@@ -16968,7 +17471,7 @@ function storageEffects(calls, options) {
|
|
|
16968
17471
|
recognition: "ruby-storage",
|
|
16969
17472
|
storageSystem: pattern.storageSystem,
|
|
16970
17473
|
scope: pattern.baseClasses[0] ?? "",
|
|
16971
|
-
container:
|
|
17474
|
+
container: constant3.text
|
|
16972
17475
|
}),
|
|
16973
17476
|
callee: call.text,
|
|
16974
17477
|
interaction: {
|
|
@@ -17084,7 +17587,8 @@ async function discoverUnits(root, options) {
|
|
|
17084
17587
|
const knownClasses = new Set(classes.map((info) => info.qualifiedName));
|
|
17085
17588
|
const fileBlocks = classes.map((info) => ({
|
|
17086
17589
|
info,
|
|
17087
|
-
knownClasses
|
|
17590
|
+
knownClasses,
|
|
17591
|
+
file: options.absoluteFile ?? options.filePath
|
|
17088
17592
|
}));
|
|
17089
17593
|
const units = [];
|
|
17090
17594
|
for (const info of classes) {
|
|
@@ -17108,14 +17612,15 @@ async function discoverUnits(root, options) {
|
|
|
17108
17612
|
}
|
|
17109
17613
|
return units;
|
|
17110
17614
|
}
|
|
17111
|
-
function reachesConfiguredBase(ancestry, self,
|
|
17615
|
+
function reachesConfiguredBase(ancestry, self, baseClassNames) {
|
|
17112
17616
|
return ancestry.some(
|
|
17113
|
-
(entry) => entry.name !== self &&
|
|
17617
|
+
(entry) => entry.name !== self && baseClassNames.includes(entry.name)
|
|
17114
17618
|
);
|
|
17115
17619
|
}
|
|
17116
17620
|
function unitsFor(pattern, pack, info, ownBlocks, fileBlocks, options) {
|
|
17117
17621
|
const table = {
|
|
17118
|
-
graphqlObjectFields: (p) => graphqlObjectFieldUnits(p, pack, info, ownBlocks, fileBlocks, options)
|
|
17622
|
+
graphqlObjectFields: (p) => graphqlObjectFieldUnits(p, pack, info, ownBlocks, fileBlocks, options),
|
|
17623
|
+
controllerActions: (p) => controllerActionUnits(p, pack, info, ownBlocks, fileBlocks, options)
|
|
17119
17624
|
};
|
|
17120
17625
|
return dispatchByType2(table, pattern);
|
|
17121
17626
|
}
|
|
@@ -17130,7 +17635,7 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
|
|
|
17130
17635
|
options.storage
|
|
17131
17636
|
);
|
|
17132
17637
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, ctx.lookup);
|
|
17133
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern)) {
|
|
17638
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
17134
17639
|
return [];
|
|
17135
17640
|
}
|
|
17136
17641
|
const typeName = graphqlTypeNameFromQualified(
|
|
@@ -17149,9 +17654,145 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
|
|
|
17149
17654
|
declsByName.set(decl.fieldName, decl);
|
|
17150
17655
|
}
|
|
17151
17656
|
}
|
|
17152
|
-
return [...declsByName.values()].map(
|
|
17153
|
-
|
|
17154
|
-
|
|
17657
|
+
return [...declsByName.values()].map((decl) => {
|
|
17658
|
+
const raw = buildFieldUnit(pack, typeName, decl, options.filePath);
|
|
17659
|
+
if (decl.body.reachSeed !== void 0) {
|
|
17660
|
+
options.onReachSeed?.(raw, decl.body.reachSeed);
|
|
17661
|
+
}
|
|
17662
|
+
return raw;
|
|
17663
|
+
});
|
|
17664
|
+
}
|
|
17665
|
+
async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks, options) {
|
|
17666
|
+
if (info.bodyNode === null || pattern.baseClassNames.includes(info.qualifiedName)) {
|
|
17667
|
+
return [];
|
|
17668
|
+
}
|
|
17669
|
+
const lookup = {
|
|
17670
|
+
root: pattern.root,
|
|
17671
|
+
pathConvention: pattern.pathConvention,
|
|
17672
|
+
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
17673
|
+
parsedFile: (absPath) => options.cache.get(absPath),
|
|
17674
|
+
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
17675
|
+
};
|
|
17676
|
+
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, lookup);
|
|
17677
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
17678
|
+
return [];
|
|
17679
|
+
}
|
|
17680
|
+
const units = [];
|
|
17681
|
+
for (const block of ownBlocks) {
|
|
17682
|
+
if (block.info.bodyNode === null) {
|
|
17683
|
+
continue;
|
|
17684
|
+
}
|
|
17685
|
+
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
17686
|
+
for (const [actionName, method] of instanceMethodsByName(
|
|
17687
|
+
block.info.bodyNode
|
|
17688
|
+
)) {
|
|
17689
|
+
if ((visibility.get(actionName) ?? "public") !== "public") {
|
|
17690
|
+
continue;
|
|
17691
|
+
}
|
|
17692
|
+
const raw = buildControllerActionUnit(
|
|
17693
|
+
pack,
|
|
17694
|
+
pattern,
|
|
17695
|
+
info.qualifiedName,
|
|
17696
|
+
actionName,
|
|
17697
|
+
method,
|
|
17698
|
+
options.filePath,
|
|
17699
|
+
options.storage
|
|
17700
|
+
);
|
|
17701
|
+
units.push(raw);
|
|
17702
|
+
options.onReachSeed?.(raw, {
|
|
17703
|
+
file: block.file,
|
|
17704
|
+
node: method,
|
|
17705
|
+
enclosingQualifiedName: info.qualifiedName
|
|
17706
|
+
});
|
|
17707
|
+
}
|
|
17708
|
+
}
|
|
17709
|
+
return units;
|
|
17710
|
+
}
|
|
17711
|
+
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, filePath, storage) {
|
|
17712
|
+
const range = rangeOf(method);
|
|
17713
|
+
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
17714
|
+
const body = bodyOfMethod(method, storage);
|
|
17715
|
+
return {
|
|
17716
|
+
identity: {
|
|
17717
|
+
name: actionName,
|
|
17718
|
+
nameKind: "binding",
|
|
17719
|
+
kind: "handler",
|
|
17720
|
+
file: filePath,
|
|
17721
|
+
range,
|
|
17722
|
+
span: spanOf(method),
|
|
17723
|
+
exportName: actionName,
|
|
17724
|
+
exportPath: [controllerQualifiedName, actionName]
|
|
17725
|
+
},
|
|
17726
|
+
boundaryBinding: route === null ? null : restBinding({
|
|
17727
|
+
transport: pack.protocol,
|
|
17728
|
+
method: route.method,
|
|
17729
|
+
path: route.path,
|
|
17730
|
+
recognition: pack.name
|
|
17731
|
+
}),
|
|
17732
|
+
parameters: [],
|
|
17733
|
+
branches: [
|
|
17734
|
+
{
|
|
17735
|
+
conditions: [],
|
|
17736
|
+
terminal: {
|
|
17737
|
+
kind: "response",
|
|
17738
|
+
statusCode: { type: "literal", value: pattern.defaultStatusCode },
|
|
17739
|
+
body: null,
|
|
17740
|
+
exceptionType: null,
|
|
17741
|
+
message: null,
|
|
17742
|
+
component: null,
|
|
17743
|
+
renderTree: null,
|
|
17744
|
+
delegateTarget: null,
|
|
17745
|
+
emitEvent: null,
|
|
17746
|
+
location: range
|
|
17747
|
+
},
|
|
17748
|
+
effects: body.effects ?? [],
|
|
17749
|
+
...body.extraEffects === void 0 ? {} : { extraEffects: body.extraEffects },
|
|
17750
|
+
location: range,
|
|
17751
|
+
isDefault: true
|
|
17752
|
+
}
|
|
17753
|
+
],
|
|
17754
|
+
bodyContent: body.bodyContent ?? "absent",
|
|
17755
|
+
dependencyCalls: [],
|
|
17756
|
+
declaredContract: null
|
|
17757
|
+
};
|
|
17758
|
+
}
|
|
17759
|
+
function routingGapUnit(pattern, gaps) {
|
|
17760
|
+
const range = { start: 1, end: 1 };
|
|
17761
|
+
return {
|
|
17762
|
+
identity: {
|
|
17763
|
+
name: "routes",
|
|
17764
|
+
kind: "module-init",
|
|
17765
|
+
file: pattern.routesFile,
|
|
17766
|
+
range,
|
|
17767
|
+
exportName: null,
|
|
17768
|
+
exportPath: null
|
|
17769
|
+
},
|
|
17770
|
+
boundaryBinding: null,
|
|
17771
|
+
parameters: [],
|
|
17772
|
+
branches: [
|
|
17773
|
+
{
|
|
17774
|
+
conditions: [],
|
|
17775
|
+
terminal: {
|
|
17776
|
+
kind: "void",
|
|
17777
|
+
statusCode: null,
|
|
17778
|
+
body: null,
|
|
17779
|
+
exceptionType: null,
|
|
17780
|
+
message: null,
|
|
17781
|
+
component: null,
|
|
17782
|
+
renderTree: null,
|
|
17783
|
+
delegateTarget: null,
|
|
17784
|
+
emitEvent: null,
|
|
17785
|
+
location: range
|
|
17786
|
+
},
|
|
17787
|
+
effects: [],
|
|
17788
|
+
location: range,
|
|
17789
|
+
isDefault: true
|
|
17790
|
+
}
|
|
17791
|
+
],
|
|
17792
|
+
readings: gaps.map((gap) => unreadableReading(gap, range)),
|
|
17793
|
+
dependencyCalls: [],
|
|
17794
|
+
declaredContract: null
|
|
17795
|
+
};
|
|
17155
17796
|
}
|
|
17156
17797
|
function blockConfiguresCall(pattern) {
|
|
17157
17798
|
const names = /* @__PURE__ */ new Set([
|
|
@@ -17192,12 +17833,15 @@ function branchesFor(body, range) {
|
|
|
17192
17833
|
}
|
|
17193
17834
|
function bodyOfMethod(method, storage) {
|
|
17194
17835
|
const effects = invocationEffects(method);
|
|
17195
|
-
const
|
|
17836
|
+
const extra = [
|
|
17837
|
+
...envReadEffects(method),
|
|
17838
|
+
...storage === void 0 ? [] : storageEffects(callsUnder(method), storage)
|
|
17839
|
+
];
|
|
17196
17840
|
return {
|
|
17197
17841
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
17198
17842
|
readings: [],
|
|
17199
17843
|
...effects.length > 0 ? { effects } : {},
|
|
17200
|
-
...
|
|
17844
|
+
...extra.length > 0 ? { extraEffects: extra } : {}
|
|
17201
17845
|
};
|
|
17202
17846
|
}
|
|
17203
17847
|
function callsUnder(node, found = []) {
|
|
@@ -17221,7 +17865,14 @@ function methodNotSettled(reason, range) {
|
|
|
17221
17865
|
}
|
|
17222
17866
|
function bodyFromLookup(found, range, subject, nothingThere, storage) {
|
|
17223
17867
|
const table = {
|
|
17224
|
-
found: (lookup) =>
|
|
17868
|
+
found: (lookup) => ({
|
|
17869
|
+
...bodyOfMethod(lookup.method, storage),
|
|
17870
|
+
reachSeed: {
|
|
17871
|
+
file: lookup.block.file,
|
|
17872
|
+
node: lookup.method,
|
|
17873
|
+
enclosingQualifiedName: lookup.block.info.qualifiedName
|
|
17874
|
+
}
|
|
17875
|
+
}),
|
|
17225
17876
|
unsettled: (lookup) => methodNotSettled(
|
|
17226
17877
|
`${subject} could be answered by a method ${lookup.reason}, so whether one exists was not settled here`,
|
|
17227
17878
|
range
|
|
@@ -17513,65 +18164,39 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
|
|
|
17513
18164
|
};
|
|
17514
18165
|
}
|
|
17515
18166
|
|
|
17516
|
-
// src/facts.ts
|
|
17517
|
-
function
|
|
17518
|
-
return `${filePath}:${
|
|
18167
|
+
// src/facts/values.ts
|
|
18168
|
+
function nodeId(filePath, node) {
|
|
18169
|
+
return `${filePath}:${node.startIndex}-${node.endIndex}`;
|
|
17519
18170
|
}
|
|
17520
|
-
function
|
|
17521
|
-
|
|
18171
|
+
function nameId(filePath, name) {
|
|
18172
|
+
return `${filePath}#${name}`;
|
|
17522
18173
|
}
|
|
17523
|
-
|
|
17524
|
-
|
|
17525
|
-
import fs2 from "fs";
|
|
17526
|
-
import path2 from "path";
|
|
17527
|
-
import { fileURLToPath } from "url";
|
|
17528
|
-
import { Language, Parser } from "web-tree-sitter";
|
|
17529
|
-
var GRAMMAR_PATH = path2.join(
|
|
17530
|
-
path2.dirname(fileURLToPath(import.meta.url)),
|
|
17531
|
-
"..",
|
|
17532
|
-
"grammar",
|
|
17533
|
-
"tree-sitter-ruby.wasm"
|
|
17534
|
-
);
|
|
17535
|
-
var languagePromise = null;
|
|
17536
|
-
function loadLanguage() {
|
|
17537
|
-
languagePromise ??= (async () => {
|
|
17538
|
-
if (!fs2.existsSync(GRAMMAR_PATH)) {
|
|
17539
|
-
throw new Error(
|
|
17540
|
-
`Ruby grammar not found at ${GRAMMAR_PATH}. See grammar/README.md.`
|
|
17541
|
-
);
|
|
17542
|
-
}
|
|
17543
|
-
await Parser.init();
|
|
17544
|
-
return Language.load(GRAMMAR_PATH);
|
|
17545
|
-
})();
|
|
17546
|
-
return languagePromise;
|
|
18174
|
+
function paramNameOf(param) {
|
|
18175
|
+
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
17547
18176
|
}
|
|
17548
|
-
|
|
17549
|
-
const
|
|
17550
|
-
const
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
|
|
17554
|
-
|
|
17555
|
-
throw new Error("tree-sitter produced no tree for this source");
|
|
18177
|
+
function declaredParamNames(method) {
|
|
18178
|
+
const params = field(method, "parameters");
|
|
18179
|
+
const declared = /* @__PURE__ */ new Set();
|
|
18180
|
+
for (const param of params === null ? [] : children3(params)) {
|
|
18181
|
+
const name = paramNameOf(param);
|
|
18182
|
+
if (name !== null) {
|
|
18183
|
+
declared.add(name.text);
|
|
17556
18184
|
}
|
|
17557
|
-
return tree;
|
|
17558
|
-
} finally {
|
|
17559
|
-
parser.delete();
|
|
17560
18185
|
}
|
|
18186
|
+
return declared;
|
|
17561
18187
|
}
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
|
|
17567
|
-
|
|
17568
|
-
|
|
17569
|
-
|
|
17570
|
-
|
|
17571
|
-
|
|
17572
|
-
}
|
|
17573
|
-
|
|
17574
|
-
return `${filePath}#${name}`;
|
|
18188
|
+
function readKey(filePath, node, enclosing) {
|
|
18189
|
+
if (node.type !== "identifier" && node.type !== "constant") {
|
|
18190
|
+
return nodeId(filePath, node);
|
|
18191
|
+
}
|
|
18192
|
+
const parent = node.parent;
|
|
18193
|
+
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
18194
|
+
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
18195
|
+
}
|
|
18196
|
+
if (enclosing !== null && declaredParamNames(enclosing).has(node.text)) {
|
|
18197
|
+
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
18198
|
+
}
|
|
18199
|
+
return nameId(filePath, node.text);
|
|
17575
18200
|
}
|
|
17576
18201
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
17577
18202
|
"string",
|
|
@@ -17733,6 +18358,9 @@ function emitExpressionFacts(emitter, node) {
|
|
|
17733
18358
|
if (WRITTEN_VALUE_TYPES.has(child.type)) {
|
|
17734
18359
|
add(emitter, "writtenValue", nodeId(emitter.filePath, child));
|
|
17735
18360
|
}
|
|
18361
|
+
if (child.type === "nil") {
|
|
18362
|
+
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
18363
|
+
}
|
|
17736
18364
|
});
|
|
17737
18365
|
}
|
|
17738
18366
|
function implicitReturn(body) {
|
|
@@ -17749,7 +18377,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
17749
18377
|
const declared = /* @__PURE__ */ new Set();
|
|
17750
18378
|
let position = 0;
|
|
17751
18379
|
for (const param of params === null ? [] : children3(params)) {
|
|
17752
|
-
const paramName =
|
|
18380
|
+
const paramName = paramNameOf(param);
|
|
17753
18381
|
if (paramName !== null) {
|
|
17754
18382
|
declared.add(paramName.text);
|
|
17755
18383
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
@@ -17844,6 +18472,10 @@ function emitClassFacts(emitter, cls) {
|
|
|
17844
18472
|
}
|
|
17845
18473
|
continue;
|
|
17846
18474
|
}
|
|
18475
|
+
if (NESTING_TYPES.has(statement.type)) {
|
|
18476
|
+
emitClassFacts(emitter, statement);
|
|
18477
|
+
continue;
|
|
18478
|
+
}
|
|
17847
18479
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
17848
18480
|
continue;
|
|
17849
18481
|
}
|
|
@@ -17867,7 +18499,7 @@ function emitValueFacts(db, filePath, root) {
|
|
|
17867
18499
|
};
|
|
17868
18500
|
const walk = (node) => {
|
|
17869
18501
|
for (const child of children3(node)) {
|
|
17870
|
-
if (child.type
|
|
18502
|
+
if (NESTING_TYPES.has(child.type)) {
|
|
17871
18503
|
declaresName(child, emitClassFacts(emitter, child));
|
|
17872
18504
|
continue;
|
|
17873
18505
|
}
|
|
@@ -17885,7 +18517,6 @@ function emitValueFacts(db, filePath, root) {
|
|
|
17885
18517
|
}
|
|
17886
18518
|
|
|
17887
18519
|
// src/facts/constants.ts
|
|
17888
|
-
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
17889
18520
|
function children4(node) {
|
|
17890
18521
|
return node.namedChildren.filter((child) => child !== null);
|
|
17891
18522
|
}
|
|
@@ -17963,16 +18594,18 @@ function collectFileConstants(filePath, root) {
|
|
|
17963
18594
|
}
|
|
17964
18595
|
};
|
|
17965
18596
|
walk(root, null, []);
|
|
17966
|
-
return { definitions, references };
|
|
18597
|
+
return { filePath, definitions, references };
|
|
17967
18598
|
}
|
|
17968
18599
|
function emitConstantBindings(db, perFile) {
|
|
17969
18600
|
const byName = /* @__PURE__ */ new Map();
|
|
18601
|
+
const fileOfDefinition = /* @__PURE__ */ new Map();
|
|
17970
18602
|
const files = [...perFile];
|
|
17971
18603
|
for (const file2 of files) {
|
|
17972
18604
|
for (const definition of file2.definitions) {
|
|
17973
18605
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
17974
18606
|
found.push(definition.key);
|
|
17975
18607
|
byName.set(definition.qualifiedName, found);
|
|
18608
|
+
fileOfDefinition.set(definition.key, file2.filePath);
|
|
17976
18609
|
}
|
|
17977
18610
|
}
|
|
17978
18611
|
const settles = (candidate) => {
|
|
@@ -17989,6 +18622,10 @@ function emitConstantBindings(db, perFile) {
|
|
|
17989
18622
|
const key = settles(candidate);
|
|
17990
18623
|
if (key !== null) {
|
|
17991
18624
|
db.add("binds", [reference.key, key]);
|
|
18625
|
+
const definedIn = fileOfDefinition.get(key);
|
|
18626
|
+
if (definedIn !== void 0 && definedIn !== file2.filePath) {
|
|
18627
|
+
db.add("rbConstantFrom", [file2.filePath, definedIn]);
|
|
18628
|
+
}
|
|
17992
18629
|
break;
|
|
17993
18630
|
}
|
|
17994
18631
|
}
|
|
@@ -17996,10 +18633,682 @@ function emitConstantBindings(db, perFile) {
|
|
|
17996
18633
|
}
|
|
17997
18634
|
}
|
|
17998
18635
|
|
|
18636
|
+
// src/facts/why.ts
|
|
18637
|
+
import fs4 from "fs";
|
|
18638
|
+
import path5 from "path";
|
|
18639
|
+
import { Database as Database4 } from "@suss/datalog";
|
|
18640
|
+
|
|
18641
|
+
// src/parser.ts
|
|
18642
|
+
import fs2 from "fs";
|
|
18643
|
+
import path2 from "path";
|
|
18644
|
+
import { fileURLToPath } from "url";
|
|
18645
|
+
import { Language, Parser } from "web-tree-sitter";
|
|
18646
|
+
var GRAMMAR_PATH = path2.join(
|
|
18647
|
+
path2.dirname(fileURLToPath(import.meta.url)),
|
|
18648
|
+
"..",
|
|
18649
|
+
"grammar",
|
|
18650
|
+
"tree-sitter-ruby.wasm"
|
|
18651
|
+
);
|
|
18652
|
+
var languagePromise = null;
|
|
18653
|
+
var loadedLanguage = null;
|
|
18654
|
+
function loadLanguage() {
|
|
18655
|
+
languagePromise ??= (async () => {
|
|
18656
|
+
if (!fs2.existsSync(GRAMMAR_PATH)) {
|
|
18657
|
+
throw new Error(
|
|
18658
|
+
`Ruby grammar not found at ${GRAMMAR_PATH}. See grammar/README.md.`
|
|
18659
|
+
);
|
|
18660
|
+
}
|
|
18661
|
+
await Parser.init();
|
|
18662
|
+
const language = await Language.load(GRAMMAR_PATH);
|
|
18663
|
+
loadedLanguage = language;
|
|
18664
|
+
return language;
|
|
18665
|
+
})();
|
|
18666
|
+
return languagePromise;
|
|
18667
|
+
}
|
|
18668
|
+
async function parseRuby(source) {
|
|
18669
|
+
const language = await loadLanguage();
|
|
18670
|
+
return parseWith(language, source);
|
|
18671
|
+
}
|
|
18672
|
+
async function preloadRubyGrammar() {
|
|
18673
|
+
await loadLanguage();
|
|
18674
|
+
}
|
|
18675
|
+
function parseRubySync(source) {
|
|
18676
|
+
if (loadedLanguage === null) {
|
|
18677
|
+
throw new Error(
|
|
18678
|
+
"the Ruby grammar has not been loaded yet; call preloadRubyGrammar first"
|
|
18679
|
+
);
|
|
18680
|
+
}
|
|
18681
|
+
return parseWith(loadedLanguage, source);
|
|
18682
|
+
}
|
|
18683
|
+
function parseWith(language, source) {
|
|
18684
|
+
const parser = new Parser();
|
|
18685
|
+
parser.setLanguage(language);
|
|
18686
|
+
try {
|
|
18687
|
+
const tree = parser.parse(source);
|
|
18688
|
+
if (tree === null) {
|
|
18689
|
+
throw new Error("tree-sitter produced no tree for this source");
|
|
18690
|
+
}
|
|
18691
|
+
return tree;
|
|
18692
|
+
} finally {
|
|
18693
|
+
parser.delete();
|
|
18694
|
+
}
|
|
18695
|
+
}
|
|
18696
|
+
|
|
18697
|
+
// src/project.ts
|
|
18698
|
+
import fs3 from "fs";
|
|
18699
|
+
import path4 from "path";
|
|
18700
|
+
import {
|
|
18701
|
+
disambiguateSummaryIds,
|
|
18702
|
+
linkCallsToSummaries,
|
|
18703
|
+
placeArgTargets as placeArgTargets2,
|
|
18704
|
+
placeCalleeParameters as placeCalleeParameters2,
|
|
18705
|
+
placeCalls as placeCalls2,
|
|
18706
|
+
recordParameterGaps as recordParameterGaps2,
|
|
18707
|
+
summaryIdFromParts,
|
|
18708
|
+
unfollowedCallGap as unfollowedCallGap2
|
|
18709
|
+
} from "@suss/behavioral-ir";
|
|
18710
|
+
import { Database as Database3 } from "@suss/datalog";
|
|
18711
|
+
import {
|
|
18712
|
+
assembleSummary as assembleSummary2,
|
|
18713
|
+
createCacheLayer,
|
|
18714
|
+
createTimer,
|
|
18715
|
+
moduleInitStructure,
|
|
18716
|
+
noopTimer,
|
|
18717
|
+
runDigest,
|
|
18718
|
+
stampModuleImports
|
|
18719
|
+
} from "@suss/extractor";
|
|
18720
|
+
|
|
18721
|
+
// src/diagnostics.ts
|
|
18722
|
+
import {
|
|
18723
|
+
buildUngatedExtractionReport,
|
|
18724
|
+
createPackTallies,
|
|
18725
|
+
tallyUnit
|
|
18726
|
+
} from "@suss/extractor";
|
|
18727
|
+
function buildRubyExtractionReport(args) {
|
|
18728
|
+
return buildUngatedExtractionReport({
|
|
18729
|
+
packs: args.packs.map((pack) => ({
|
|
18730
|
+
name: pack.name,
|
|
18731
|
+
version: pack.version ?? null,
|
|
18732
|
+
discovers: pack.discovery.length > 0
|
|
18733
|
+
})),
|
|
18734
|
+
tallies: args.tallies,
|
|
18735
|
+
filesWalked: args.filesWalked,
|
|
18736
|
+
summaries: args.summaries
|
|
18737
|
+
});
|
|
18738
|
+
}
|
|
18739
|
+
|
|
18740
|
+
// src/facts.ts
|
|
18741
|
+
import path3 from "path";
|
|
18742
|
+
import { SKIP_CHILDREN as SKIP_CHILDREN3, walkDescendants as walkDescendants3 } from "@suss/extractor";
|
|
18743
|
+
function unitKey(filePath, range, name) {
|
|
18744
|
+
return `${filePath}:${range.start}-${range.end}#${name}`;
|
|
18745
|
+
}
|
|
18746
|
+
function emitEntryFact(db, filePath, range, name) {
|
|
18747
|
+
db.add("entry", [unitKey(filePath, range, name)]);
|
|
18748
|
+
}
|
|
18749
|
+
function emitRequireFacts(db, filePath, root, known) {
|
|
18750
|
+
walkDescendants3(root, null, {
|
|
18751
|
+
at: (node) => {
|
|
18752
|
+
const target = requiredRelativePath(node);
|
|
18753
|
+
if (target === null) {
|
|
18754
|
+
return;
|
|
18755
|
+
}
|
|
18756
|
+
const resolved = path3.resolve(
|
|
18757
|
+
path3.dirname(filePath),
|
|
18758
|
+
target.endsWith(".rb") ? target : `${target}.rb`
|
|
18759
|
+
);
|
|
18760
|
+
if (known.has(resolved)) {
|
|
18761
|
+
db.add("rbRequires", [filePath, resolved]);
|
|
18762
|
+
}
|
|
18763
|
+
},
|
|
18764
|
+
into: (node) => node.type === "argument_list" ? SKIP_CHILDREN3 : null
|
|
18765
|
+
});
|
|
18766
|
+
}
|
|
18767
|
+
function requiredRelativePath(node) {
|
|
18768
|
+
if (node.type !== "call" || field(node, "receiver") !== null || field(node, "method")?.text !== "require_relative") {
|
|
18769
|
+
return null;
|
|
18770
|
+
}
|
|
18771
|
+
const { positional } = readCallArgs(field(node, "arguments"));
|
|
18772
|
+
return positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
18773
|
+
}
|
|
18774
|
+
|
|
18775
|
+
// src/reach/closure.ts
|
|
18776
|
+
import {
|
|
18777
|
+
functionCallBinding,
|
|
18778
|
+
placeArgTargets,
|
|
18779
|
+
placeCalleeParameters,
|
|
18780
|
+
placeCalls,
|
|
18781
|
+
recordParameterGaps,
|
|
18782
|
+
TargetPlacements,
|
|
18783
|
+
unfollowedCallGap,
|
|
18784
|
+
worthRecording
|
|
18785
|
+
} from "@suss/behavioral-ir";
|
|
18786
|
+
import { Database as Database2, evaluate as evaluate3, lit as lit2, rule as rule2, variable as v2 } from "@suss/datalog";
|
|
18787
|
+
import { assembleSummary } from "@suss/extractor";
|
|
18788
|
+
|
|
18789
|
+
// src/reach/resolveCallee.ts
|
|
18790
|
+
var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
|
|
18791
|
+
var stop = (reason) => ({
|
|
18792
|
+
kind: "stopped",
|
|
18793
|
+
reason
|
|
18794
|
+
});
|
|
18795
|
+
function followed(target) {
|
|
18796
|
+
return { kind: "followed", target };
|
|
18797
|
+
}
|
|
18798
|
+
async function resolveCallee(call, site, ctx) {
|
|
18799
|
+
const methodName = calleeMethodName(call);
|
|
18800
|
+
if (methodName === void 0) {
|
|
18801
|
+
return stop("noDeclaration");
|
|
18802
|
+
}
|
|
18803
|
+
if (DYNAMIC_SEND_NAMES.has(methodName)) {
|
|
18804
|
+
return stop("unsettledValue");
|
|
18805
|
+
}
|
|
18806
|
+
const receiver = field(call, "receiver");
|
|
18807
|
+
if (receiver === null || receiver.type === "self") {
|
|
18808
|
+
return resolveImplicitSelf(methodName, site, ctx);
|
|
18809
|
+
}
|
|
18810
|
+
if (receiver.type === "identifier" && methodName === "call" && site.ownParameters.includes(receiver.text)) {
|
|
18811
|
+
return stop("callerSupplied");
|
|
18812
|
+
}
|
|
18813
|
+
return resolveOnReceiver(receiver, methodName, site, ctx);
|
|
18814
|
+
}
|
|
18815
|
+
async function resolveMethodReference(name, site, ctx) {
|
|
18816
|
+
const resolved = await resolveImplicitSelf(name, site, ctx);
|
|
18817
|
+
return resolved.kind === "followed" ? resolved.target : null;
|
|
18818
|
+
}
|
|
18819
|
+
function leavesRoomForATopLevelMethod(reason) {
|
|
18820
|
+
return reason === "noDeclaration" || reason === "outsideRun";
|
|
18821
|
+
}
|
|
18822
|
+
async function resolveImplicitSelf(methodName, site, ctx) {
|
|
18823
|
+
if (site.enclosingQualifiedName !== null) {
|
|
18824
|
+
const onClass = await methodOnAncestryOf(
|
|
18825
|
+
site.enclosingQualifiedName,
|
|
18826
|
+
methodName,
|
|
18827
|
+
ctx
|
|
18828
|
+
);
|
|
18829
|
+
if (onClass.kind === "followed" || !leavesRoomForATopLevelMethod(onClass.reason)) {
|
|
18830
|
+
return onClass;
|
|
18831
|
+
}
|
|
18832
|
+
}
|
|
18833
|
+
return resolveTopLevelName(methodName, ctx);
|
|
18834
|
+
}
|
|
18835
|
+
function resolveTopLevelName(methodName, ctx) {
|
|
18836
|
+
const candidates = ctx.topLevelMethods.get(methodName);
|
|
18837
|
+
if (candidates === void 0 || candidates.length === 0) {
|
|
18838
|
+
return stop("noDeclaration");
|
|
18839
|
+
}
|
|
18840
|
+
if (candidates.length > 1) {
|
|
18841
|
+
return stop("multipleSources");
|
|
18842
|
+
}
|
|
18843
|
+
return followed(candidates[0]);
|
|
18844
|
+
}
|
|
18845
|
+
async function resolveOnReceiver(receiver, methodName, site, ctx) {
|
|
18846
|
+
if (receiver.type === "call") {
|
|
18847
|
+
const innerName = calleeMethodName(receiver);
|
|
18848
|
+
const innerReceiver = field(receiver, "receiver");
|
|
18849
|
+
if (innerName === "new" && innerReceiver !== null) {
|
|
18850
|
+
const classRef = classRefOf(innerReceiver, site, ctx);
|
|
18851
|
+
return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
|
|
18852
|
+
}
|
|
18853
|
+
const root = rootConstantOf(receiver);
|
|
18854
|
+
if (root === null) {
|
|
18855
|
+
return stop("unsettledValue");
|
|
18856
|
+
}
|
|
18857
|
+
return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
|
|
18858
|
+
}
|
|
18859
|
+
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
18860
|
+
const classRef = classRefOf(receiver, site, ctx);
|
|
18861
|
+
return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
|
|
18862
|
+
}
|
|
18863
|
+
return stop("unsettledValue");
|
|
18864
|
+
}
|
|
18865
|
+
function rootConstantOf(node) {
|
|
18866
|
+
if (node.type === "constant" || node.type === "scope_resolution") {
|
|
18867
|
+
return node;
|
|
18868
|
+
}
|
|
18869
|
+
if (node.type !== "call") {
|
|
18870
|
+
return null;
|
|
18871
|
+
}
|
|
18872
|
+
const receiver = field(node, "receiver");
|
|
18873
|
+
return receiver === null ? null : rootConstantOf(receiver);
|
|
18874
|
+
}
|
|
18875
|
+
function classRefOf(node, site, ctx) {
|
|
18876
|
+
if (node.type === "constant") {
|
|
18877
|
+
const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
|
|
18878
|
+
if (shadow !== null) {
|
|
18879
|
+
return shadow;
|
|
18880
|
+
}
|
|
18881
|
+
const qualified = qualifyConstantRef(node, site.nesting);
|
|
18882
|
+
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
18883
|
+
}
|
|
18884
|
+
if (node.type === "scope_resolution") {
|
|
18885
|
+
const qualified = qualifyConstantRef(node, site.nesting);
|
|
18886
|
+
return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
|
|
18887
|
+
}
|
|
18888
|
+
return null;
|
|
18889
|
+
}
|
|
18890
|
+
async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
|
|
18891
|
+
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
18892
|
+
if (ownBlocks === null) {
|
|
18893
|
+
return stop("outsideRun");
|
|
18894
|
+
}
|
|
18895
|
+
const ancestry = await ancestryOf(qualifiedName, ownBlocks, ctx.lookup);
|
|
18896
|
+
const found = methodInAncestry(ancestry, methodName);
|
|
18897
|
+
if (found.type === "found") {
|
|
18898
|
+
return followed(reachedMethod(found.method, found.block, methodName));
|
|
18899
|
+
}
|
|
18900
|
+
if (found.type === "unsettled") {
|
|
18901
|
+
return stop(
|
|
18902
|
+
found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
|
|
18903
|
+
);
|
|
18904
|
+
}
|
|
18905
|
+
return stop("noDeclaration");
|
|
18906
|
+
}
|
|
18907
|
+
function reachedMethod(node, block, name) {
|
|
18908
|
+
return {
|
|
18909
|
+
file: block.file,
|
|
18910
|
+
node,
|
|
18911
|
+
name,
|
|
18912
|
+
exportPath: [block.info.qualifiedName, name],
|
|
18913
|
+
enclosingQualifiedName: block.info.qualifiedName
|
|
18914
|
+
};
|
|
18915
|
+
}
|
|
18916
|
+
function singletonMethodOn(qualifiedName, methodName, ctx) {
|
|
18917
|
+
const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
|
|
18918
|
+
if (ownBlocks === null) {
|
|
18919
|
+
return stop("outsideRun");
|
|
18920
|
+
}
|
|
18921
|
+
for (const block of ownBlocks) {
|
|
18922
|
+
if (block.info.bodyNode === null) {
|
|
18923
|
+
continue;
|
|
18924
|
+
}
|
|
18925
|
+
const found = singletonMethodsByName(block.info.bodyNode).get(methodName);
|
|
18926
|
+
if (found !== void 0) {
|
|
18927
|
+
return followed(reachedMethod(found, block, methodName));
|
|
18928
|
+
}
|
|
18929
|
+
}
|
|
18930
|
+
return stop("noDeclaration");
|
|
18931
|
+
}
|
|
18932
|
+
|
|
18933
|
+
// src/reach/closure.ts
|
|
18934
|
+
var REACHABLE_RULES = [
|
|
18935
|
+
rule2("reachable", [v2("f")], [lit2("entry", v2("f"))]),
|
|
18936
|
+
rule2(
|
|
18937
|
+
"reachable",
|
|
18938
|
+
[v2("g")],
|
|
18939
|
+
[lit2("reachable", v2("f")), lit2("calls", v2("f"), v2("g"))]
|
|
18940
|
+
)
|
|
18941
|
+
];
|
|
18942
|
+
async function reachedFunctions(seeds, options) {
|
|
18943
|
+
const ctx = buildReachContext(options.files);
|
|
18944
|
+
const db = new Database2();
|
|
18945
|
+
const functionByKey = /* @__PURE__ */ new Map();
|
|
18946
|
+
const seedKeys = /* @__PURE__ */ new Set();
|
|
18947
|
+
const scanned = /* @__PURE__ */ new Set();
|
|
18948
|
+
const targetsByKey = /* @__PURE__ */ new Map();
|
|
18949
|
+
const stopsByKey = /* @__PURE__ */ new Map();
|
|
18950
|
+
const argTargetsByKey = /* @__PURE__ */ new Map();
|
|
18951
|
+
const parameterCallsByKey = /* @__PURE__ */ new Map();
|
|
18952
|
+
const passedPositions = /* @__PURE__ */ new Set();
|
|
18953
|
+
for (const seed of seeds) {
|
|
18954
|
+
seedKeys.add(seed.key);
|
|
18955
|
+
functionByKey.set(seed.key, {
|
|
18956
|
+
file: seed.file,
|
|
18957
|
+
node: seed.node,
|
|
18958
|
+
name: field(seed.node, "name")?.text ?? "<anon>",
|
|
18959
|
+
exportPath: [],
|
|
18960
|
+
enclosingQualifiedName: seed.enclosingQualifiedName
|
|
18961
|
+
});
|
|
18962
|
+
db.add("entry", [seed.key]);
|
|
18963
|
+
}
|
|
18964
|
+
for (; ; ) {
|
|
18965
|
+
evaluate3(db, REACHABLE_RULES);
|
|
18966
|
+
const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
|
|
18967
|
+
if (frontier.length === 0) {
|
|
18968
|
+
break;
|
|
18969
|
+
}
|
|
18970
|
+
for (const key of frontier) {
|
|
18971
|
+
scanned.add(key);
|
|
18972
|
+
const source = functionByKey.get(key);
|
|
18973
|
+
if (source === void 0) {
|
|
18974
|
+
continue;
|
|
18975
|
+
}
|
|
18976
|
+
const scan = await scanBody(source, ctx, options.displayPathOf);
|
|
18977
|
+
if (scan.stops.length > 0) {
|
|
18978
|
+
stopsByKey.set(key, scan.stops);
|
|
18979
|
+
}
|
|
18980
|
+
targetsByKey.set(key, scan.targets);
|
|
18981
|
+
argTargetsByKey.set(key, scan.argTargets);
|
|
18982
|
+
if (scan.parameterCalls.length > 0) {
|
|
18983
|
+
parameterCallsByKey.set(key, scan.parameterCalls);
|
|
18984
|
+
}
|
|
18985
|
+
for (const position of scan.passedPositions) {
|
|
18986
|
+
passedPositions.add(position);
|
|
18987
|
+
}
|
|
18988
|
+
for (const target of scan.followed) {
|
|
18989
|
+
const calleeKey = keyOf(target);
|
|
18990
|
+
if (!functionByKey.has(calleeKey)) {
|
|
18991
|
+
functionByKey.set(calleeKey, target);
|
|
18992
|
+
}
|
|
18993
|
+
db.add("calls", [key, calleeKey]);
|
|
18994
|
+
}
|
|
18995
|
+
}
|
|
18996
|
+
}
|
|
18997
|
+
const summaries = [];
|
|
18998
|
+
const summariesByKey = /* @__PURE__ */ new Map();
|
|
18999
|
+
for (const [keyAtom] of db.facts("reachable")) {
|
|
19000
|
+
const key = String(keyAtom);
|
|
19001
|
+
const target = functionByKey.get(key);
|
|
19002
|
+
if (seedKeys.has(key) || target === void 0) {
|
|
19003
|
+
continue;
|
|
19004
|
+
}
|
|
19005
|
+
const summary = assembleSummary(libraryUnit(target, options), {
|
|
19006
|
+
gapHandling: "permissive"
|
|
19007
|
+
});
|
|
19008
|
+
summary.confidence = { source: "inferred_static", level: "low" };
|
|
19009
|
+
summary.gaps.push(...(stopsByKey.get(key) ?? []).map(unfollowedCallGap));
|
|
19010
|
+
placeCalls(summary, targetsByKey.get(key));
|
|
19011
|
+
placeArgTargets(summary, argTargetsByKey.get(key));
|
|
19012
|
+
placeCalleeParameters(summary, parameterCallsByKey.get(key));
|
|
19013
|
+
summariesByKey.set(key, [summary]);
|
|
19014
|
+
summaries.push(summary);
|
|
19015
|
+
}
|
|
19016
|
+
recordParameterGaps(parameterCallsByKey, summariesByKey, passedPositions);
|
|
19017
|
+
return {
|
|
19018
|
+
summaries,
|
|
19019
|
+
targetsByKey,
|
|
19020
|
+
stopsByKey,
|
|
19021
|
+
argTargetsByKey,
|
|
19022
|
+
parameterCallsByKey,
|
|
19023
|
+
passedPositions
|
|
19024
|
+
};
|
|
19025
|
+
}
|
|
19026
|
+
function keyOf(target) {
|
|
19027
|
+
return nodeId(target.file, target.node);
|
|
19028
|
+
}
|
|
19029
|
+
function buildReachContext(files) {
|
|
19030
|
+
const blocksByQualifiedName = /* @__PURE__ */ new Map();
|
|
19031
|
+
const classes = [];
|
|
19032
|
+
for (const { file: file2, root } of files) {
|
|
19033
|
+
walkDefinitions(root, (info) => classes.push({ file: file2, info }));
|
|
19034
|
+
}
|
|
19035
|
+
const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
|
|
19036
|
+
for (const { file: file2, info } of classes) {
|
|
19037
|
+
const list = blocksByQualifiedName.get(info.qualifiedName) ?? [];
|
|
19038
|
+
list.push({ info, knownClasses, file: file2 });
|
|
19039
|
+
blocksByQualifiedName.set(info.qualifiedName, list);
|
|
19040
|
+
}
|
|
19041
|
+
const topLevelMethods = /* @__PURE__ */ new Map();
|
|
19042
|
+
for (const { file: file2, root } of files) {
|
|
19043
|
+
for (const method of topLevelMethodNodes(root)) {
|
|
19044
|
+
const name = field(method, "name")?.text;
|
|
19045
|
+
if (name === void 0) {
|
|
19046
|
+
continue;
|
|
19047
|
+
}
|
|
19048
|
+
const list = topLevelMethods.get(name) ?? [];
|
|
19049
|
+
list.push({
|
|
19050
|
+
file: file2,
|
|
19051
|
+
node: method,
|
|
19052
|
+
name,
|
|
19053
|
+
exportPath: [name],
|
|
19054
|
+
enclosingQualifiedName: null
|
|
19055
|
+
});
|
|
19056
|
+
topLevelMethods.set(name, list);
|
|
19057
|
+
}
|
|
19058
|
+
}
|
|
19059
|
+
const lookup = {
|
|
19060
|
+
root: "",
|
|
19061
|
+
pathConvention: "railsUnderscore",
|
|
19062
|
+
ancestryRootClassNames: [],
|
|
19063
|
+
// Every class the run defines is already in `blocksByQualifiedName`
|
|
19064
|
+
// above, so a name that misses there is outside the run and this
|
|
19065
|
+
// never has a file on disk to read.
|
|
19066
|
+
parsedFile: async () => null,
|
|
19067
|
+
localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
|
|
19068
|
+
};
|
|
19069
|
+
return { lookup, knownClasses, topLevelMethods };
|
|
19070
|
+
}
|
|
19071
|
+
function topLevelMethodNodes(root, found = []) {
|
|
19072
|
+
for (const child of bodyStatements(root)) {
|
|
19073
|
+
if (child.type === "method") {
|
|
19074
|
+
found.push(child);
|
|
19075
|
+
continue;
|
|
19076
|
+
}
|
|
19077
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
19078
|
+
continue;
|
|
19079
|
+
}
|
|
19080
|
+
topLevelMethodNodes(child, found);
|
|
19081
|
+
}
|
|
19082
|
+
return found;
|
|
19083
|
+
}
|
|
19084
|
+
function nestingOf(qualifiedName) {
|
|
19085
|
+
const parts = qualifiedName.split("::");
|
|
19086
|
+
const out = [];
|
|
19087
|
+
for (let depth = parts.length; depth >= 1; depth--) {
|
|
19088
|
+
out.push(parts.slice(0, depth).join("::"));
|
|
19089
|
+
}
|
|
19090
|
+
return out;
|
|
19091
|
+
}
|
|
19092
|
+
var EMPTY_SCAN = {
|
|
19093
|
+
followed: [],
|
|
19094
|
+
stops: [],
|
|
19095
|
+
targets: /* @__PURE__ */ new Map(),
|
|
19096
|
+
argTargets: /* @__PURE__ */ new Map(),
|
|
19097
|
+
parameterCalls: [],
|
|
19098
|
+
passedPositions: /* @__PURE__ */ new Set()
|
|
19099
|
+
};
|
|
19100
|
+
function methodReferenceSymbol(node) {
|
|
19101
|
+
const target = node.type === "block_argument" ? bodyStatements(node)[0] : node;
|
|
19102
|
+
if (target === void 0 || target.type !== "call" || field(target, "receiver") !== null || field(target, "method")?.text !== "method") {
|
|
19103
|
+
return null;
|
|
19104
|
+
}
|
|
19105
|
+
const args = field(target, "arguments");
|
|
19106
|
+
const argChildren = args === null ? [] : bodyStatements(args);
|
|
19107
|
+
return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
|
|
19108
|
+
}
|
|
19109
|
+
async function scanBody(source, ctx, displayPathOf) {
|
|
19110
|
+
const body = field(source.node, "body");
|
|
19111
|
+
if (body === null) {
|
|
19112
|
+
return EMPTY_SCAN;
|
|
19113
|
+
}
|
|
19114
|
+
const calls = withoutChainLinks(bodyCalls(body));
|
|
19115
|
+
const ownParameters = positionalParameters(source.node).map((p) => p.name);
|
|
19116
|
+
const site = {
|
|
19117
|
+
enclosingQualifiedName: source.enclosingQualifiedName,
|
|
19118
|
+
nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
|
|
19119
|
+
ownParameters
|
|
19120
|
+
};
|
|
19121
|
+
const followed2 = [];
|
|
19122
|
+
const stops = [];
|
|
19123
|
+
const placements = new TargetPlacements();
|
|
19124
|
+
const parameterCalls = [];
|
|
19125
|
+
const passedPositions = /* @__PURE__ */ new Set();
|
|
19126
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19127
|
+
const parameterCallsSeen = /* @__PURE__ */ new Set();
|
|
19128
|
+
const recordPassedArgs = async (call, callee, calleeKey) => {
|
|
19129
|
+
const args = field(call, "arguments");
|
|
19130
|
+
if (args === null) {
|
|
19131
|
+
return;
|
|
19132
|
+
}
|
|
19133
|
+
const argNodes = bodyStatements(args);
|
|
19134
|
+
for (let position = 0; position < argNodes.length; position += 1) {
|
|
19135
|
+
const symbol2 = methodReferenceSymbol(argNodes[position]);
|
|
19136
|
+
if (symbol2 === null) {
|
|
19137
|
+
continue;
|
|
19138
|
+
}
|
|
19139
|
+
const resolved = await resolveMethodReference(
|
|
19140
|
+
symbol2.text.slice(1),
|
|
19141
|
+
site,
|
|
19142
|
+
ctx
|
|
19143
|
+
);
|
|
19144
|
+
if (resolved === null) {
|
|
19145
|
+
continue;
|
|
19146
|
+
}
|
|
19147
|
+
const key = keyOf(resolved);
|
|
19148
|
+
if (!seen.has(key)) {
|
|
19149
|
+
seen.add(key);
|
|
19150
|
+
followed2.push(resolved);
|
|
19151
|
+
}
|
|
19152
|
+
placements.placeArg(callee, position, {
|
|
19153
|
+
file: displayPathOf(resolved.file),
|
|
19154
|
+
span: spanOf(resolved.node)
|
|
19155
|
+
});
|
|
19156
|
+
if (calleeKey !== null) {
|
|
19157
|
+
passedPositions.add(`${calleeKey}#${position}`);
|
|
19158
|
+
}
|
|
19159
|
+
}
|
|
19160
|
+
};
|
|
19161
|
+
for (const call of calls) {
|
|
19162
|
+
const callee = calleeText(call);
|
|
19163
|
+
const outcome = await resolveCallee(call, site, ctx);
|
|
19164
|
+
const placed = outcome.kind === "followed" ? {
|
|
19165
|
+
file: displayPathOf(outcome.target.file),
|
|
19166
|
+
span: spanOf(outcome.target.node)
|
|
19167
|
+
} : outcome.reason === "noDeclaration" ? null : { file: displayPathOf(source.file), span: spanOf(call) };
|
|
19168
|
+
placements.place(callee, placed);
|
|
19169
|
+
await recordPassedArgs(
|
|
19170
|
+
call,
|
|
19171
|
+
callee,
|
|
19172
|
+
outcome.kind === "followed" ? keyOf(outcome.target) : null
|
|
19173
|
+
);
|
|
19174
|
+
if (outcome.kind === "stopped") {
|
|
19175
|
+
const stopKey = `${outcome.reason}:${callee}`;
|
|
19176
|
+
if (!seen.has(stopKey) && worthRecording(outcome.reason)) {
|
|
19177
|
+
seen.add(stopKey);
|
|
19178
|
+
stops.push({ callee, reason: outcome.reason });
|
|
19179
|
+
}
|
|
19180
|
+
if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
|
|
19181
|
+
const receiverName = field(call, "receiver")?.text;
|
|
19182
|
+
const parameterIndex = receiverName === void 0 ? -1 : ownParameters.indexOf(receiverName);
|
|
19183
|
+
if (parameterIndex !== -1) {
|
|
19184
|
+
parameterCallsSeen.add(callee);
|
|
19185
|
+
parameterCalls.push({ callee, parameterIndex });
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
continue;
|
|
19189
|
+
}
|
|
19190
|
+
const key = keyOf(outcome.target);
|
|
19191
|
+
if (!seen.has(key)) {
|
|
19192
|
+
seen.add(key);
|
|
19193
|
+
followed2.push(outcome.target);
|
|
19194
|
+
}
|
|
19195
|
+
}
|
|
19196
|
+
return {
|
|
19197
|
+
followed: followed2,
|
|
19198
|
+
stops,
|
|
19199
|
+
targets: placements.targets,
|
|
19200
|
+
argTargets: placements.argTargets,
|
|
19201
|
+
parameterCalls,
|
|
19202
|
+
passedPositions
|
|
19203
|
+
};
|
|
19204
|
+
}
|
|
19205
|
+
function libraryUnit(target, options) {
|
|
19206
|
+
const { file: file2, node, name, exportPath } = target;
|
|
19207
|
+
const body = bodyOfMethod(node, options.storage);
|
|
19208
|
+
const range = rangeOf(node);
|
|
19209
|
+
return {
|
|
19210
|
+
identity: {
|
|
19211
|
+
name,
|
|
19212
|
+
nameKind: "binding",
|
|
19213
|
+
kind: "library",
|
|
19214
|
+
file: options.displayPathOf(file2),
|
|
19215
|
+
range,
|
|
19216
|
+
span: spanOf(node),
|
|
19217
|
+
exportName: exportPath[0] ?? name,
|
|
19218
|
+
exportPath
|
|
19219
|
+
},
|
|
19220
|
+
boundaryBinding: functionCallBinding({
|
|
19221
|
+
transport: "in-process",
|
|
19222
|
+
recognition: "reachable"
|
|
19223
|
+
}),
|
|
19224
|
+
parameters: positionalParameters(node),
|
|
19225
|
+
branches: [
|
|
19226
|
+
{
|
|
19227
|
+
conditions: [],
|
|
19228
|
+
terminal: {
|
|
19229
|
+
kind: "return",
|
|
19230
|
+
statusCode: null,
|
|
19231
|
+
body: null,
|
|
19232
|
+
exceptionType: null,
|
|
19233
|
+
message: null,
|
|
19234
|
+
component: null,
|
|
19235
|
+
renderTree: null,
|
|
19236
|
+
delegateTarget: null,
|
|
19237
|
+
emitEvent: null,
|
|
19238
|
+
location: range
|
|
19239
|
+
},
|
|
19240
|
+
effects: body.effects ?? [],
|
|
19241
|
+
...body.extraEffects === void 0 ? {} : { extraEffects: body.extraEffects },
|
|
19242
|
+
location: range,
|
|
19243
|
+
isDefault: true
|
|
19244
|
+
}
|
|
19245
|
+
],
|
|
19246
|
+
bodyContent: body.bodyContent ?? "absent",
|
|
19247
|
+
dependencyCalls: [],
|
|
19248
|
+
declaredContract: null
|
|
19249
|
+
};
|
|
19250
|
+
}
|
|
19251
|
+
function positionalParameters(node) {
|
|
19252
|
+
const parameters = field(node, "parameters");
|
|
19253
|
+
if (parameters === null) {
|
|
19254
|
+
return [];
|
|
19255
|
+
}
|
|
19256
|
+
const out = [];
|
|
19257
|
+
let position = 0;
|
|
19258
|
+
for (const param of bodyStatements(parameters)) {
|
|
19259
|
+
const name = parameterName(param);
|
|
19260
|
+
if (name === null) {
|
|
19261
|
+
continue;
|
|
19262
|
+
}
|
|
19263
|
+
out.push({ name, position, role: name, typeText: null });
|
|
19264
|
+
position += 1;
|
|
19265
|
+
}
|
|
19266
|
+
return out;
|
|
19267
|
+
}
|
|
19268
|
+
function parameterName(param) {
|
|
19269
|
+
if (param.type === "identifier") {
|
|
19270
|
+
return param.text;
|
|
19271
|
+
}
|
|
19272
|
+
const named = field(param, "name");
|
|
19273
|
+
return named !== null && named.type === "identifier" ? named.text : null;
|
|
19274
|
+
}
|
|
19275
|
+
|
|
19276
|
+
// src/version.ts
|
|
19277
|
+
import { createAdapterStamp } from "@suss/extractor";
|
|
19278
|
+
var ADAPTER_VERSION = "0.1.0";
|
|
19279
|
+
var adapterStamp = createAdapterStamp({
|
|
19280
|
+
moduleUrl: import.meta.url,
|
|
19281
|
+
version: ADAPTER_VERSION
|
|
19282
|
+
});
|
|
19283
|
+
|
|
17999
19284
|
// src/project.ts
|
|
18000
19285
|
async function extractRubyProject(options) {
|
|
18001
|
-
const
|
|
19286
|
+
const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
|
|
19287
|
+
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
19288
|
+
options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? path4.join(options.projectRoot, ".suss", "cache") : null)
|
|
19289
|
+
);
|
|
19290
|
+
const extractionCache = createCacheLayer(cacheDir);
|
|
19291
|
+
const packsDigest = adapterStamp.packsDigest(
|
|
19292
|
+
options.packs.map(
|
|
19293
|
+
(pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
|
|
19294
|
+
)
|
|
19295
|
+
);
|
|
19296
|
+
const cacheInput = {
|
|
19297
|
+
files: cacheDir === null ? [] : options.files,
|
|
19298
|
+
adapterPacksDigest: cacheDir === null ? packsDigest : runDigest(packsDigest, options.packs, options.files)
|
|
19299
|
+
};
|
|
19300
|
+
const lookup = await timer.timeAsync(
|
|
19301
|
+
"cache.lookup",
|
|
19302
|
+
() => extractionCache.lookup(cacheInput)
|
|
19303
|
+
);
|
|
19304
|
+
options.onCacheDiagnostic?.(lookup.diagnostic);
|
|
19305
|
+
if (lookup.kind === "hit") {
|
|
19306
|
+
options.onTiming?.(timer.report());
|
|
19307
|
+
return { summaries: lookup.summaries, facts: new Database3() };
|
|
19308
|
+
}
|
|
19309
|
+
const db = new Database3();
|
|
18002
19310
|
const summaries = [];
|
|
19311
|
+
const tallies = createPackTallies(options.packs);
|
|
18003
19312
|
const constants = [];
|
|
18004
19313
|
const cache = createFileCache(
|
|
18005
19314
|
(source) => parseRuby(source).then((tree) => tree.rootNode),
|
|
@@ -18007,33 +19316,178 @@ async function extractRubyProject(options) {
|
|
|
18007
19316
|
);
|
|
18008
19317
|
const parsed = [];
|
|
18009
19318
|
for (const file2 of options.files) {
|
|
18010
|
-
|
|
18011
|
-
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
19319
|
+
await timer.timeAsync("parse", async () => {
|
|
19320
|
+
const root = await cache.get(file2);
|
|
19321
|
+
if (root === null) {
|
|
19322
|
+
return;
|
|
19323
|
+
}
|
|
19324
|
+
parsed.push({ file: file2, root });
|
|
19325
|
+
emitValueFacts(db, file2, root);
|
|
19326
|
+
constants.push(collectFileConstants(file2, root));
|
|
19327
|
+
});
|
|
18017
19328
|
}
|
|
18018
|
-
|
|
19329
|
+
timer.time("discover", () => {
|
|
19330
|
+
emitConstantBindings(db, constants);
|
|
19331
|
+
const known = new Set(parsed.map(({ file: file2 }) => file2));
|
|
19332
|
+
for (const { file: file2, root } of parsed) {
|
|
19333
|
+
emitRequireFacts(db, file2, root, known);
|
|
19334
|
+
}
|
|
19335
|
+
});
|
|
18019
19336
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
19337
|
+
const displayPathOf = (file2) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file2) : file2;
|
|
19338
|
+
const seeds = [];
|
|
19339
|
+
const summariesBySeed = /* @__PURE__ */ new Map();
|
|
18020
19340
|
for (const { file: file2, root } of parsed) {
|
|
18021
|
-
const displayPath =
|
|
18022
|
-
const
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
19341
|
+
const displayPath = displayPathOf(file2);
|
|
19342
|
+
const seedByRaw = /* @__PURE__ */ new Map();
|
|
19343
|
+
const rawUnits = await timer.timeAsync(
|
|
19344
|
+
"discover",
|
|
19345
|
+
() => discoverUnits(root, {
|
|
19346
|
+
packs: options.packs,
|
|
19347
|
+
filePath: displayPath,
|
|
19348
|
+
absoluteFile: file2,
|
|
19349
|
+
cache,
|
|
19350
|
+
...storagePatterns.length > 0 ? { storage: { facts: db, patterns: storagePatterns } } : {},
|
|
19351
|
+
onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
|
|
19352
|
+
})
|
|
19353
|
+
);
|
|
18028
19354
|
for (const raw of rawUnits) {
|
|
18029
|
-
const summary =
|
|
19355
|
+
const summary = timer.time(
|
|
19356
|
+
"summarize",
|
|
19357
|
+
() => assembleSummary2(raw, { gapHandling: "permissive" })
|
|
19358
|
+
);
|
|
18030
19359
|
summary.confidence = { source: "inferred_static", level: "low" };
|
|
18031
19360
|
summaries.push(summary);
|
|
18032
19361
|
emitEntryFact(db, file2, raw.identity.range, raw.identity.name);
|
|
19362
|
+
tallyUnit(tallies, raw.boundaryBinding?.recognition);
|
|
19363
|
+
const seed = seedByRaw.get(raw);
|
|
19364
|
+
if (seed === void 0) {
|
|
19365
|
+
continue;
|
|
19366
|
+
}
|
|
19367
|
+
const key = nodeId(seed.file, seed.node);
|
|
19368
|
+
const sharing = summariesBySeed.get(key);
|
|
19369
|
+
if (sharing === void 0) {
|
|
19370
|
+
seeds.push({
|
|
19371
|
+
key,
|
|
19372
|
+
file: seed.file,
|
|
19373
|
+
node: seed.node,
|
|
19374
|
+
enclosingQualifiedName: seed.enclosingQualifiedName
|
|
19375
|
+
});
|
|
19376
|
+
summariesBySeed.set(key, [summary]);
|
|
19377
|
+
} else {
|
|
19378
|
+
sharing.push(summary);
|
|
19379
|
+
}
|
|
19380
|
+
}
|
|
19381
|
+
const loadTimeReads = timer.time("discover", () => envReadEffects(root));
|
|
19382
|
+
if (loadTimeReads.length > 0) {
|
|
19383
|
+
const summary = timer.time(
|
|
19384
|
+
"summarize",
|
|
19385
|
+
() => assembleSummary2(
|
|
19386
|
+
moduleInitStructure({
|
|
19387
|
+
name: path4.basename(displayPath),
|
|
19388
|
+
file: displayPath,
|
|
19389
|
+
range: rangeOf(root),
|
|
19390
|
+
effects: loadTimeReads
|
|
19391
|
+
}),
|
|
19392
|
+
{ gapHandling: "permissive" }
|
|
19393
|
+
)
|
|
19394
|
+
);
|
|
19395
|
+
summary.confidence = { source: "inferred_static", level: "low" };
|
|
19396
|
+
summaries.push(summary);
|
|
19397
|
+
}
|
|
19398
|
+
}
|
|
19399
|
+
for (const pack of options.packs) {
|
|
19400
|
+
for (const pattern of pack.discovery) {
|
|
19401
|
+
if (pattern.type !== "controllerActions") {
|
|
19402
|
+
continue;
|
|
19403
|
+
}
|
|
19404
|
+
const gaps = pattern.routingGaps?.() ?? [];
|
|
19405
|
+
if (gaps.length === 0) {
|
|
19406
|
+
continue;
|
|
19407
|
+
}
|
|
19408
|
+
const summary = timer.time(
|
|
19409
|
+
"summarize",
|
|
19410
|
+
() => assembleSummary2(routingGapUnit(pattern, gaps), {
|
|
19411
|
+
gapHandling: "permissive"
|
|
19412
|
+
})
|
|
19413
|
+
);
|
|
19414
|
+
summary.confidence = { source: "inferred_static", level: "low" };
|
|
19415
|
+
summaries.push(summary);
|
|
18033
19416
|
}
|
|
18034
19417
|
}
|
|
19418
|
+
const reached = await timer.timeAsync(
|
|
19419
|
+
"summarize",
|
|
19420
|
+
() => reachedFunctions(seeds, {
|
|
19421
|
+
files: parsed,
|
|
19422
|
+
displayPathOf,
|
|
19423
|
+
...storagePatterns.length > 0 ? { storage: { facts: db, patterns: storagePatterns } } : {}
|
|
19424
|
+
})
|
|
19425
|
+
);
|
|
19426
|
+
for (const [key, owners] of summariesBySeed) {
|
|
19427
|
+
for (const summary of owners) {
|
|
19428
|
+
summary.gaps.push(
|
|
19429
|
+
...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
|
|
19430
|
+
);
|
|
19431
|
+
placeCalls2(summary, reached.targetsByKey.get(key));
|
|
19432
|
+
placeArgTargets2(summary, reached.argTargetsByKey.get(key));
|
|
19433
|
+
placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
|
|
19434
|
+
}
|
|
19435
|
+
}
|
|
19436
|
+
recordParameterGaps2(
|
|
19437
|
+
reached.parameterCallsByKey,
|
|
19438
|
+
summariesBySeed,
|
|
19439
|
+
reached.passedPositions
|
|
19440
|
+
);
|
|
19441
|
+
summaries.push(...reached.summaries);
|
|
19442
|
+
const dependencies = fileDependenciesOf(db, displayPathOf);
|
|
19443
|
+
stampModuleImports(summaries, (file2) => dependencies.get(file2) ?? []);
|
|
19444
|
+
const idRoot = options.projectRoot ?? options.workspaceRoot;
|
|
19445
|
+
for (const summary of summaries) {
|
|
19446
|
+
const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : path4.resolve(options.workspaceRoot, summary.location.file);
|
|
19447
|
+
summary.identity.id = summaryIdFromParts({
|
|
19448
|
+
workspace: void 0,
|
|
19449
|
+
file: idRoot === void 0 ? absoluteFile : path4.relative(idRoot, absoluteFile),
|
|
19450
|
+
name: summary.identity.name,
|
|
19451
|
+
exportPath: summary.identity.exportPath
|
|
19452
|
+
});
|
|
19453
|
+
}
|
|
19454
|
+
disambiguateSummaryIds(summaries);
|
|
19455
|
+
linkCallsToSummaries(summaries);
|
|
19456
|
+
await timer.timeAsync("cache.write", async () => {
|
|
19457
|
+
if (cacheDir === null || summaries.length === 0) {
|
|
19458
|
+
return;
|
|
19459
|
+
}
|
|
19460
|
+
try {
|
|
19461
|
+
await extractionCache.write(cacheInput, summaries);
|
|
19462
|
+
} catch {
|
|
19463
|
+
}
|
|
19464
|
+
});
|
|
19465
|
+
options.onExtractionReport?.(
|
|
19466
|
+
buildRubyExtractionReport({
|
|
19467
|
+
packs: options.packs,
|
|
19468
|
+
tallies,
|
|
19469
|
+
filesWalked: options.files.length,
|
|
19470
|
+
summaries
|
|
19471
|
+
})
|
|
19472
|
+
);
|
|
19473
|
+
options.onTiming?.(timer.report());
|
|
18035
19474
|
return { summaries, facts: db };
|
|
18036
19475
|
}
|
|
19476
|
+
function fileDependenciesOf(db, displayPathOf) {
|
|
19477
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
19478
|
+
for (const relation of ["rbRequires", "rbConstantFrom"]) {
|
|
19479
|
+
for (const [from, to] of db.facts(relation)) {
|
|
19480
|
+
if (typeof from !== "string" || typeof to !== "string") {
|
|
19481
|
+
continue;
|
|
19482
|
+
}
|
|
19483
|
+
const key = displayPathOf(from);
|
|
19484
|
+
const seen = byFile.get(key) ?? [];
|
|
19485
|
+
seen.push(displayPathOf(to));
|
|
19486
|
+
byFile.set(key, seen);
|
|
19487
|
+
}
|
|
19488
|
+
}
|
|
19489
|
+
return byFile;
|
|
19490
|
+
}
|
|
18037
19491
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["vendor", "node_modules", "tmp", ".git"]);
|
|
18038
19492
|
function findRubyFiles(root) {
|
|
18039
19493
|
const found = [];
|
|
@@ -18041,32 +19495,306 @@ function findRubyFiles(root) {
|
|
|
18041
19495
|
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
18042
19496
|
if (entry.isDirectory()) {
|
|
18043
19497
|
if (!SKIPPED_DIRECTORIES.has(entry.name)) {
|
|
18044
|
-
walk(
|
|
19498
|
+
walk(path4.join(dir, entry.name));
|
|
18045
19499
|
}
|
|
18046
19500
|
continue;
|
|
18047
19501
|
}
|
|
18048
19502
|
if (entry.isFile() && entry.name.endsWith(".rb")) {
|
|
18049
|
-
found.push(
|
|
19503
|
+
found.push(path4.join(dir, entry.name));
|
|
18050
19504
|
}
|
|
18051
19505
|
}
|
|
18052
19506
|
};
|
|
18053
19507
|
walk(root);
|
|
18054
19508
|
return found.sort();
|
|
18055
19509
|
}
|
|
19510
|
+
|
|
19511
|
+
// src/facts/resolve.ts
|
|
19512
|
+
import {
|
|
19513
|
+
constant as constant2,
|
|
19514
|
+
deriveOnDemand as deriveOnDemand2,
|
|
19515
|
+
evaluate as evaluate4,
|
|
19516
|
+
lit as lit3,
|
|
19517
|
+
rule as rule3,
|
|
19518
|
+
variable as v3
|
|
19519
|
+
} from "@suss/datalog";
|
|
19520
|
+
var RUBY_RULES = [
|
|
19521
|
+
// `Loader.new` makes one of the class, which the shared rules already say
|
|
19522
|
+
// about calling a class. Ruby writes it as a method read off the constant
|
|
19523
|
+
// instead, so the read is what steps to the class here.
|
|
19524
|
+
rule3(
|
|
19525
|
+
"stepsTo",
|
|
19526
|
+
[v3("x"), v3("cls"), VALUE_STEP],
|
|
19527
|
+
[
|
|
19528
|
+
lit3("readsProperty", v3("x"), v3("o"), constant2("new")),
|
|
19529
|
+
lit3("comesTo", v3("o"), v3("cls")),
|
|
19530
|
+
lit3("objectValue", v3("cls"))
|
|
19531
|
+
]
|
|
19532
|
+
)
|
|
19533
|
+
];
|
|
19534
|
+
var RESOLUTION_PROGRAM = deriveOnDemand2(
|
|
19535
|
+
[...RESOLUTION_RULES, ...RUBY_RULES, ...RESOLUTION_QUESTIONS],
|
|
19536
|
+
ANSWER_RELATIONS
|
|
19537
|
+
);
|
|
19538
|
+
|
|
19539
|
+
// src/facts/why.ts
|
|
19540
|
+
var WITNESS_RULES = [...RESOLUTION_RULES, ...RUBY_RULES];
|
|
19541
|
+
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
19542
|
+
function namedChildrenOf(node) {
|
|
19543
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
19544
|
+
}
|
|
19545
|
+
function enclosingMethod(node) {
|
|
19546
|
+
let current = node.parent;
|
|
19547
|
+
while (current !== null) {
|
|
19548
|
+
if (METHOD_TYPES2.has(current.type)) {
|
|
19549
|
+
return current;
|
|
19550
|
+
}
|
|
19551
|
+
current = current.parent;
|
|
19552
|
+
}
|
|
19553
|
+
return null;
|
|
19554
|
+
}
|
|
19555
|
+
function indexFile(file2, root, locations) {
|
|
19556
|
+
const walk = (node) => {
|
|
19557
|
+
locations.set(nodeId(file2, node), { file: file2, node });
|
|
19558
|
+
if (node.type === "constant") {
|
|
19559
|
+
locations.set(`${file2}#${node.text}`, { file: file2, node });
|
|
19560
|
+
}
|
|
19561
|
+
if (node.type === "assignment") {
|
|
19562
|
+
const left = field(node, "left");
|
|
19563
|
+
if (left !== null && left.type === "identifier") {
|
|
19564
|
+
locations.set(`${file2}#${left.text}`, { file: file2, node: left });
|
|
19565
|
+
}
|
|
19566
|
+
}
|
|
19567
|
+
for (const child of namedChildrenOf(node)) {
|
|
19568
|
+
walk(child);
|
|
19569
|
+
}
|
|
19570
|
+
};
|
|
19571
|
+
walk(root);
|
|
19572
|
+
}
|
|
19573
|
+
function displayNameOf(node) {
|
|
19574
|
+
if (node.type === "identifier" || node.type === "constant") {
|
|
19575
|
+
return node.text;
|
|
19576
|
+
}
|
|
19577
|
+
if (node.type === "method" || node.type === "singleton_method") {
|
|
19578
|
+
return field(node, "name")?.text ?? node.text;
|
|
19579
|
+
}
|
|
19580
|
+
if (node.type === "class" || node.type === "module") {
|
|
19581
|
+
return field(node, "name")?.text ?? node.text;
|
|
19582
|
+
}
|
|
19583
|
+
const text = node.text.split("\n")[0].trim();
|
|
19584
|
+
return text.length > 40 ? `${text.slice(0, 40)}...` : text;
|
|
19585
|
+
}
|
|
19586
|
+
function width(node) {
|
|
19587
|
+
return node.endIndex - node.startIndex;
|
|
19588
|
+
}
|
|
19589
|
+
var RubyWhySession = class {
|
|
19590
|
+
root;
|
|
19591
|
+
db = new Database4();
|
|
19592
|
+
locations = /* @__PURE__ */ new Map();
|
|
19593
|
+
trees = /* @__PURE__ */ new Map();
|
|
19594
|
+
constructor(options) {
|
|
19595
|
+
this.root = path5.resolve(options.dir);
|
|
19596
|
+
const constants = [];
|
|
19597
|
+
for (const file2 of findRubyFiles(this.root)) {
|
|
19598
|
+
const source = fs4.readFileSync(file2, "utf8");
|
|
19599
|
+
const root = parseRubySync(source).rootNode;
|
|
19600
|
+
this.trees.set(file2, root);
|
|
19601
|
+
emitValueFacts(this.db, file2, root);
|
|
19602
|
+
constants.push(collectFileConstants(file2, root));
|
|
19603
|
+
indexFile(file2, root, this.locations);
|
|
19604
|
+
}
|
|
19605
|
+
emitConstantBindings(this.db, constants);
|
|
19606
|
+
}
|
|
19607
|
+
/**
|
|
19608
|
+
* The smallest expression on `line` of `file` whose text is exactly
|
|
19609
|
+
* `text`, or null.
|
|
19610
|
+
*/
|
|
19611
|
+
findExpression(file2, line, text) {
|
|
19612
|
+
const root = this.rootOf(file2);
|
|
19613
|
+
if (root === null) {
|
|
19614
|
+
return null;
|
|
19615
|
+
}
|
|
19616
|
+
let found = null;
|
|
19617
|
+
const visit = (node) => {
|
|
19618
|
+
if (node.startPosition.row + 1 === line && node.text === text) {
|
|
19619
|
+
if (found === null || width(node) <= width(found)) {
|
|
19620
|
+
found = node;
|
|
19621
|
+
}
|
|
19622
|
+
}
|
|
19623
|
+
for (const child of namedChildrenOf(node)) {
|
|
19624
|
+
visit(child);
|
|
19625
|
+
}
|
|
19626
|
+
};
|
|
19627
|
+
visit(root);
|
|
19628
|
+
return found === null ? null : { file: this.pathOf(file2), node: found };
|
|
19629
|
+
}
|
|
19630
|
+
/**
|
|
19631
|
+
* The callee of the call written as `calleeText` between `startLine`
|
|
19632
|
+
* and `endLine` of `file`, or null.
|
|
19633
|
+
*/
|
|
19634
|
+
findCallee(file2, startLine, endLine, calleeText2) {
|
|
19635
|
+
const root = this.rootOf(file2);
|
|
19636
|
+
if (root === null) {
|
|
19637
|
+
return null;
|
|
19638
|
+
}
|
|
19639
|
+
let found = null;
|
|
19640
|
+
const visit = (node) => {
|
|
19641
|
+
if (node.type === "call") {
|
|
19642
|
+
const line = node.startPosition.row + 1;
|
|
19643
|
+
const callee = field(node, "method");
|
|
19644
|
+
if (line >= startLine && line <= endLine && callee !== null && callee.text === calleeText2) {
|
|
19645
|
+
found = callee;
|
|
19646
|
+
}
|
|
19647
|
+
}
|
|
19648
|
+
for (const child of namedChildrenOf(node)) {
|
|
19649
|
+
visit(child);
|
|
19650
|
+
}
|
|
19651
|
+
};
|
|
19652
|
+
visit(root);
|
|
19653
|
+
return found === null ? null : { file: this.pathOf(file2), node: found };
|
|
19654
|
+
}
|
|
19655
|
+
/**
|
|
19656
|
+
* Why `value` resolves to what it does: the witness proof, flattened
|
|
19657
|
+
* to the chain and rendered. Null when the value does not resolve,
|
|
19658
|
+
* which the caller says in its own words.
|
|
19659
|
+
*/
|
|
19660
|
+
explain(value, options = {}) {
|
|
19661
|
+
return explainResolvedKey({
|
|
19662
|
+
db: this.db,
|
|
19663
|
+
rules: WITNESS_RULES,
|
|
19664
|
+
key: readKey(value.file, value.node, enclosingMethod(value.node)),
|
|
19665
|
+
locate: (key) => this.locate(key),
|
|
19666
|
+
displayPath: (key) => this.displayPath(key),
|
|
19667
|
+
...options
|
|
19668
|
+
});
|
|
19669
|
+
}
|
|
19670
|
+
locate(key) {
|
|
19671
|
+
const located = this.locations.get(key);
|
|
19672
|
+
if (located === void 0) {
|
|
19673
|
+
return null;
|
|
19674
|
+
}
|
|
19675
|
+
return {
|
|
19676
|
+
name: displayNameOf(located.node),
|
|
19677
|
+
file: this.displayPath(located.file),
|
|
19678
|
+
line: located.node.startPosition.row + 1
|
|
19679
|
+
};
|
|
19680
|
+
}
|
|
19681
|
+
rootOf(file2) {
|
|
19682
|
+
return this.trees.get(this.pathOf(file2)) ?? null;
|
|
19683
|
+
}
|
|
19684
|
+
pathOf(file2) {
|
|
19685
|
+
return path5.isAbsolute(file2) ? file2 : path5.resolve(this.root, file2);
|
|
19686
|
+
}
|
|
19687
|
+
/** A file path said relative to the root, or as given when it is outside the root. */
|
|
19688
|
+
displayPath(key) {
|
|
19689
|
+
if (key.startsWith(this.root)) {
|
|
19690
|
+
return path5.relative(this.root, key);
|
|
19691
|
+
}
|
|
19692
|
+
return key;
|
|
19693
|
+
}
|
|
19694
|
+
};
|
|
19695
|
+
|
|
19696
|
+
// src/stubEvidence.ts
|
|
19697
|
+
import fs5 from "fs";
|
|
19698
|
+
import path6 from "path";
|
|
19699
|
+
import { SKIP_CHILDREN as SKIP_CHILDREN4, walkDescendants as walkDescendants4 } from "@suss/extractor";
|
|
19700
|
+
function bareLetters(name) {
|
|
19701
|
+
return name.replaceAll(/[-_]/g, "").toLowerCase();
|
|
19702
|
+
}
|
|
19703
|
+
function isSpelledFrom(qualifiedName, packageName) {
|
|
19704
|
+
const wanted = bareLetters(packageName);
|
|
19705
|
+
const constants = qualifiedName.split("::");
|
|
19706
|
+
return constants.some(
|
|
19707
|
+
(_, end) => bareLetters(constants.slice(0, end + 1).join("")) === wanted
|
|
19708
|
+
);
|
|
19709
|
+
}
|
|
19710
|
+
function isPackageRequire(target, packageName) {
|
|
19711
|
+
const bundlerName = packageName.replaceAll("-", "_");
|
|
19712
|
+
return target === bundlerName || target.startsWith(`${bundlerName}/`) || target === packageName || target.startsWith(`${packageName}/`);
|
|
19713
|
+
}
|
|
19714
|
+
function requireTarget(node) {
|
|
19715
|
+
if (node.type !== "call" || field(node, "receiver") !== null || field(node, "method")?.text !== "require" && field(node, "method")?.text !== "require_relative") {
|
|
19716
|
+
return null;
|
|
19717
|
+
}
|
|
19718
|
+
const { positional } = readCallArgs(field(node, "arguments"));
|
|
19719
|
+
return positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
19720
|
+
}
|
|
19721
|
+
function requireSitesIn(root, file2) {
|
|
19722
|
+
const found = [];
|
|
19723
|
+
walkDescendants4(root, null, {
|
|
19724
|
+
at: (node) => {
|
|
19725
|
+
const target = requireTarget(node);
|
|
19726
|
+
if (target !== null) {
|
|
19727
|
+
found.push({ target, file: file2, line: rangeOf(node).start });
|
|
19728
|
+
}
|
|
19729
|
+
},
|
|
19730
|
+
into: (node) => node.type === "argument_list" ? SKIP_CHILDREN4 : null
|
|
19731
|
+
});
|
|
19732
|
+
return found;
|
|
19733
|
+
}
|
|
19734
|
+
function extendsSitesIn(root, file2, packageName) {
|
|
19735
|
+
const found = [];
|
|
19736
|
+
walkClasses(root, (info) => {
|
|
19737
|
+
if (info.superclassQualifiedName !== null && isSpelledFrom(info.superclassQualifiedName, packageName)) {
|
|
19738
|
+
found.push({
|
|
19739
|
+
className: info.qualifiedName,
|
|
19740
|
+
superclassName: info.superclassQualifiedName,
|
|
19741
|
+
file: file2,
|
|
19742
|
+
line: rangeOf(info.node).start
|
|
19743
|
+
});
|
|
19744
|
+
}
|
|
19745
|
+
});
|
|
19746
|
+
return found;
|
|
19747
|
+
}
|
|
19748
|
+
async function rubyStubEvidence(options) {
|
|
19749
|
+
const requires = [];
|
|
19750
|
+
const extendsSites = [];
|
|
19751
|
+
for (const file2 of findRubyFiles(options.directory)) {
|
|
19752
|
+
const source = fs5.readFileSync(file2, "utf8");
|
|
19753
|
+
const tree = await parseRuby(source);
|
|
19754
|
+
const displayPath = path6.relative(options.directory, file2);
|
|
19755
|
+
for (const site of requireSitesIn(tree.rootNode, displayPath)) {
|
|
19756
|
+
if (isPackageRequire(site.target, options.packageName)) {
|
|
19757
|
+
requires.push(site);
|
|
19758
|
+
}
|
|
19759
|
+
}
|
|
19760
|
+
extendsSites.push(
|
|
19761
|
+
...extendsSitesIn(tree.rootNode, displayPath, options.packageName)
|
|
19762
|
+
);
|
|
19763
|
+
}
|
|
19764
|
+
return { requires, extendsSites };
|
|
19765
|
+
}
|
|
18056
19766
|
export {
|
|
19767
|
+
ADAPTER_VERSION,
|
|
19768
|
+
RubyWhySession,
|
|
18057
19769
|
ancestryOf,
|
|
19770
|
+
bodyStatements,
|
|
19771
|
+
booleanLiteralValue,
|
|
19772
|
+
collectFileConstants,
|
|
18058
19773
|
createFileCache,
|
|
18059
19774
|
discoverUnits,
|
|
19775
|
+
emitConstantBindings,
|
|
18060
19776
|
emitEntryFact,
|
|
19777
|
+
emitValueFacts,
|
|
18061
19778
|
extractRubyProject,
|
|
19779
|
+
field,
|
|
18062
19780
|
findRubyFiles,
|
|
18063
19781
|
graphqlTypeNameFromQualified,
|
|
19782
|
+
hashKeySymbolName,
|
|
18064
19783
|
inheritedStatements,
|
|
18065
19784
|
methodInAncestry,
|
|
19785
|
+
nodeId,
|
|
18066
19786
|
parseRuby,
|
|
19787
|
+
parseRubySync,
|
|
19788
|
+
preloadRubyGrammar,
|
|
18067
19789
|
qualifyConstantRef,
|
|
18068
19790
|
reachDefinition,
|
|
19791
|
+
readCallArgs,
|
|
19792
|
+
readKey,
|
|
18069
19793
|
resolveConstantFile,
|
|
19794
|
+
rubyStubEvidence,
|
|
19795
|
+
runStatements,
|
|
19796
|
+
stringLiteralValue,
|
|
19797
|
+
symbolValue,
|
|
18070
19798
|
typeShapeFromNode,
|
|
18071
19799
|
underscoreConstantPath,
|
|
18072
19800
|
unitKey,
|