@suss/adapter-ruby 0.21.0 → 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 +2335 -570
- package/dist/index.d.cts +280 -8
- package/dist/index.d.ts +280 -8
- package/dist/index.js +2018 -230
- 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
|
}
|
|
@@ -15268,7 +15475,7 @@ function date4(params) {
|
|
|
15268
15475
|
// ../../../node_modules/zod/v4/classic/external.js
|
|
15269
15476
|
config(en_default());
|
|
15270
15477
|
|
|
15271
|
-
// ../../ir-core/dist/chunk-
|
|
15478
|
+
// ../../ir-core/dist/chunk-PPQWK62O.js
|
|
15272
15479
|
function gqlIdentityKey(typeName, fieldName) {
|
|
15273
15480
|
return `gql:${typeName}.${fieldName}`;
|
|
15274
15481
|
}
|
|
@@ -15554,6 +15761,42 @@ var metricSemantics = defineBoundarySemantics({
|
|
|
15554
15761
|
}
|
|
15555
15762
|
}
|
|
15556
15763
|
});
|
|
15764
|
+
var HOLE_SPLIT = /(\{[^}]*\})/;
|
|
15765
|
+
function isHole(piece) {
|
|
15766
|
+
return piece.startsWith("{") && piece.endsWith("}");
|
|
15767
|
+
}
|
|
15768
|
+
function parseBoundaryName(name) {
|
|
15769
|
+
const parts = name.split(HOLE_SPLIT).filter((piece) => piece !== "").map(
|
|
15770
|
+
(piece) => isHole(piece) ? { type: "hole", label: piece.slice(1, -1) } : { type: "text", text: piece }
|
|
15771
|
+
);
|
|
15772
|
+
const only = parts.length === 1 ? parts[0] : void 0;
|
|
15773
|
+
if (only !== void 0 && only.type === "hole") {
|
|
15774
|
+
return { type: "reference", path: only.label.split(".") };
|
|
15775
|
+
}
|
|
15776
|
+
if (parts.some((part) => part.type === "hole")) {
|
|
15777
|
+
return { type: "pattern", parts };
|
|
15778
|
+
}
|
|
15779
|
+
return { type: "literal", value: name };
|
|
15780
|
+
}
|
|
15781
|
+
function patternHole(label) {
|
|
15782
|
+
return `{${label}}`;
|
|
15783
|
+
}
|
|
15784
|
+
function namesNothing(name) {
|
|
15785
|
+
return parseBoundaryName(name).type === "reference";
|
|
15786
|
+
}
|
|
15787
|
+
function referenceOf(name) {
|
|
15788
|
+
if (name.type !== "reference") {
|
|
15789
|
+
return null;
|
|
15790
|
+
}
|
|
15791
|
+
const root = name.path[0];
|
|
15792
|
+
if (root === void 0 || name.path.some((part) => part === "")) {
|
|
15793
|
+
return null;
|
|
15794
|
+
}
|
|
15795
|
+
return { root, fields: name.path.slice(1) };
|
|
15796
|
+
}
|
|
15797
|
+
function referenceFromName(name) {
|
|
15798
|
+
return referenceOf(parseBoundaryName(name));
|
|
15799
|
+
}
|
|
15557
15800
|
var SCHEME_ORIGIN = /^[a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/]*/;
|
|
15558
15801
|
var PROTOCOL_RELATIVE_ORIGIN = /^\/\/[^/]*/;
|
|
15559
15802
|
function pathAfterOrigin(text) {
|
|
@@ -15580,12 +15823,12 @@ var RestSemanticsSchema = external_exports.object({
|
|
|
15580
15823
|
declaredResponses: external_exports.array(external_exports.number()).optional()
|
|
15581
15824
|
});
|
|
15582
15825
|
var OPENING_HOLE = /^\{([^{}]+)\}(.*)$/;
|
|
15583
|
-
function
|
|
15584
|
-
const
|
|
15585
|
-
return
|
|
15826
|
+
function baseUrlReference(semantics) {
|
|
15827
|
+
const label = OPENING_HOLE.exec(semantics.path ?? "")?.[1];
|
|
15828
|
+
return label === void 0 ? null : referenceFromName(patternHole(label));
|
|
15586
15829
|
}
|
|
15587
|
-
function normalizePath(
|
|
15588
|
-
let normalized =
|
|
15830
|
+
function normalizePath(path7) {
|
|
15831
|
+
let normalized = path7.replace(/:([a-zA-Z_]\w*)/g, "{$1}");
|
|
15589
15832
|
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
15590
15833
|
normalized = normalized.slice(0, -1);
|
|
15591
15834
|
}
|
|
@@ -15595,8 +15838,8 @@ function normalizePath(path4) {
|
|
|
15595
15838
|
);
|
|
15596
15839
|
return normalized;
|
|
15597
15840
|
}
|
|
15598
|
-
function pathShape(
|
|
15599
|
-
return normalizePath(
|
|
15841
|
+
function pathShape(path7) {
|
|
15842
|
+
return normalizePath(path7).replace(/\{[^}]*\}/g, "{}");
|
|
15600
15843
|
}
|
|
15601
15844
|
function methodsAgree(a, b) {
|
|
15602
15845
|
if (a === null || b === null) {
|
|
@@ -15674,8 +15917,8 @@ var restSemantics = defineBoundarySemantics({
|
|
|
15674
15917
|
return null;
|
|
15675
15918
|
}
|
|
15676
15919
|
const method = semantics.method === null ? "ANY" : semantics.method.toUpperCase();
|
|
15677
|
-
const
|
|
15678
|
-
return `${method} ${
|
|
15920
|
+
const path7 = semantics.path === null ? "?" : normalizePath(semantics.path);
|
|
15921
|
+
return `${method} ${path7}`;
|
|
15679
15922
|
},
|
|
15680
15923
|
/**
|
|
15681
15924
|
* A route with both halves stated decides for itself: the method
|
|
@@ -15684,14 +15927,14 @@ var restSemantics = defineBoundarySemantics({
|
|
|
15684
15927
|
* half might still be the one that handles the request, and nothing
|
|
15685
15928
|
* here can settle that, so it abstains instead of refusing.
|
|
15686
15929
|
*/
|
|
15687
|
-
servesRequest(semantics, method,
|
|
15930
|
+
servesRequest(semantics, method, path7) {
|
|
15688
15931
|
if (semantics.method === null || semantics.path === null) {
|
|
15689
15932
|
return "unknown";
|
|
15690
15933
|
}
|
|
15691
15934
|
if (!methodsAgree(semantics.method, method.toUpperCase())) {
|
|
15692
15935
|
return "nomatch";
|
|
15693
15936
|
}
|
|
15694
|
-
return routePathAdmits(semantics.path,
|
|
15937
|
+
return routePathAdmits(semantics.path, path7) ? "match" : "nomatch";
|
|
15695
15938
|
},
|
|
15696
15939
|
/**
|
|
15697
15940
|
* A route the registration pattern admits is inside it. A route
|
|
@@ -15716,22 +15959,20 @@ var restSemantics = defineBoundarySemantics({
|
|
|
15716
15959
|
* route parameter, and `/orders/{id}` means every id rather than a
|
|
15717
15960
|
* variable somebody set.
|
|
15718
15961
|
*/
|
|
15719
|
-
groundName(semantics,
|
|
15720
|
-
|
|
15962
|
+
groundName(semantics, deployment) {
|
|
15963
|
+
const reference = baseUrlReference(semantics);
|
|
15964
|
+
if (reference === null) {
|
|
15721
15965
|
return null;
|
|
15722
15966
|
}
|
|
15723
|
-
const
|
|
15724
|
-
if (opening === null) {
|
|
15725
|
-
return null;
|
|
15726
|
-
}
|
|
15727
|
-
const [, label, rest] = opening;
|
|
15728
|
-
const base = deployedAs(variableOf(label));
|
|
15967
|
+
const base = deployment.setTo(reference);
|
|
15729
15968
|
if (base === null) {
|
|
15730
15969
|
return null;
|
|
15731
15970
|
}
|
|
15971
|
+
const rest = OPENING_HOLE.exec(semantics.path ?? "")?.[2] ?? "";
|
|
15732
15972
|
const grounded = pathAfterOrigin(`${base}${rest}`);
|
|
15733
15973
|
return { ...semantics, path: grounded === "" ? "/" : grounded };
|
|
15734
15974
|
},
|
|
15975
|
+
nameReference: baseUrlReference,
|
|
15735
15976
|
ruleBoundary: {
|
|
15736
15977
|
// "METHOD /path": one leading token, then an absolute path.
|
|
15737
15978
|
claims(raw) {
|
|
@@ -15807,6 +16048,9 @@ var StorageSemanticsSchema = external_exports.object({
|
|
|
15807
16048
|
*/
|
|
15808
16049
|
accessPath: external_exports.string().nullable()
|
|
15809
16050
|
});
|
|
16051
|
+
function containerReference(semantics) {
|
|
16052
|
+
return semantics.container === null ? null : referenceFromName(semantics.container);
|
|
16053
|
+
}
|
|
15810
16054
|
var storageSemantics = defineBoundarySemantics({
|
|
15811
16055
|
name: "storage",
|
|
15812
16056
|
schema: StorageSemanticsSchema,
|
|
@@ -15823,7 +16067,21 @@ var storageSemantics = defineBoundarySemantics({
|
|
|
15823
16067
|
exchangesHttpResponses: false,
|
|
15824
16068
|
reportsUnpairedItself: false,
|
|
15825
16069
|
identityKey: () => null,
|
|
15826
|
-
displayLabel: storageLabel
|
|
16070
|
+
displayLabel: storageLabel,
|
|
16071
|
+
/**
|
|
16072
|
+
* A container the source states as a variable is whatever the
|
|
16073
|
+
* deployment sets that variable to. `{SUBSCRIBERS_TABLE}` in the
|
|
16074
|
+
* code and `prod-subscribers-v1` in the manifest are one table.
|
|
16075
|
+
*/
|
|
16076
|
+
groundName(semantics, deployment) {
|
|
16077
|
+
const reference = containerReference(semantics);
|
|
16078
|
+
if (reference === null) {
|
|
16079
|
+
return null;
|
|
16080
|
+
}
|
|
16081
|
+
const name = deployment.setTo(reference);
|
|
16082
|
+
return name === null ? null : { ...semantics, container: name };
|
|
16083
|
+
},
|
|
16084
|
+
nameReference: containerReference
|
|
15827
16085
|
}
|
|
15828
16086
|
});
|
|
15829
16087
|
function storageLabel(semantics) {
|
|
@@ -15838,6 +16096,9 @@ var UnitInvocationSemanticsSchema = DeployableUnitSchema.extend({
|
|
|
15838
16096
|
name: external_exports.literal("unit-invocation"),
|
|
15839
16097
|
instanceName: DeployableUnitSchema.shape.instanceName.nullable()
|
|
15840
16098
|
});
|
|
16099
|
+
function calleeReference(semantics) {
|
|
16100
|
+
return semantics.instanceName === null ? null : referenceFromName(semantics.instanceName);
|
|
16101
|
+
}
|
|
15841
16102
|
var unitInvocationSemantics = defineBoundarySemantics({
|
|
15842
16103
|
name: "unit-invocation",
|
|
15843
16104
|
schema: UnitInvocationSemanticsSchema,
|
|
@@ -15860,8 +16121,13 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
15860
16121
|
* being reported as a problem here.
|
|
15861
16122
|
*/
|
|
15862
16123
|
reportsUnpairedItself: false,
|
|
16124
|
+
/**
|
|
16125
|
+
* A callee the source states only as a variable is not a name for
|
|
16126
|
+
* anything until the deployment fills it in, so it keys as nothing
|
|
16127
|
+
* rather than as a bucket that would agree with itself alone.
|
|
16128
|
+
*/
|
|
15863
16129
|
identityKey(semantics) {
|
|
15864
|
-
if (semantics.instanceName === null) {
|
|
16130
|
+
if (semantics.instanceName === null || namesNothing(semantics.instanceName)) {
|
|
15865
16131
|
return null;
|
|
15866
16132
|
}
|
|
15867
16133
|
return unitIdentityKey(
|
|
@@ -15877,7 +16143,23 @@ var unitInvocationSemantics = defineBoundarySemantics({
|
|
|
15877
16143
|
semantics.deploymentTarget,
|
|
15878
16144
|
semantics.instanceName
|
|
15879
16145
|
);
|
|
15880
|
-
}
|
|
16146
|
+
},
|
|
16147
|
+
/**
|
|
16148
|
+
* A call that reads its callee's name out of the environment
|
|
16149
|
+
* reaches whichever resource the template wires that variable to.
|
|
16150
|
+
* The logical id is the answer rather than the deployed name,
|
|
16151
|
+
* because the invoked unit's own summary is keyed by the logical
|
|
16152
|
+
* id and neither side ever writes the other's string.
|
|
16153
|
+
*/
|
|
16154
|
+
groundName(semantics, deployment) {
|
|
16155
|
+
const reference = calleeReference(semantics);
|
|
16156
|
+
if (reference === null) {
|
|
16157
|
+
return null;
|
|
16158
|
+
}
|
|
16159
|
+
const logicalId = deployment.pointsAt(reference);
|
|
16160
|
+
return logicalId === null ? null : { ...semantics, instanceName: logicalId };
|
|
16161
|
+
},
|
|
16162
|
+
nameReference: calleeReference
|
|
15881
16163
|
}
|
|
15882
16164
|
});
|
|
15883
16165
|
var SemanticsSchema = external_exports.discriminatedUnion("name", [
|
|
@@ -16071,7 +16353,291 @@ function storageBinding(opts) {
|
|
|
16071
16353
|
|
|
16072
16354
|
// ../../resolution/dist/index.js
|
|
16073
16355
|
import { ruleLabel } from "@suss/datalog";
|
|
16356
|
+
import { Database, evaluate, proofOf, witnesses } from "@suss/datalog";
|
|
16074
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
|
+
}
|
|
16075
16641
|
var VALUE_STEP = constant("value");
|
|
16076
16642
|
var NAMESPACE_IMPORT_NAME = "*";
|
|
16077
16643
|
var NAMESPACE_IMPORT = constant(NAMESPACE_IMPORT_NAME);
|
|
@@ -16185,9 +16751,9 @@ var RESOLUTION_RULES = [
|
|
|
16185
16751
|
[lit("invokes", v("r"), v("f")), lit("returnsValue", v("f"), v("ret"))],
|
|
16186
16752
|
"call result"
|
|
16187
16753
|
),
|
|
16188
|
-
// Where a walk gets to, and whether it ran a call on the way.
|
|
16189
|
-
//
|
|
16190
|
-
//
|
|
16754
|
+
// Where a walk gets to, and whether it ran a call on the way. The walk
|
|
16755
|
+
// so far comes first, so demand stays on the value asked about; the
|
|
16756
|
+
// README says what the other order cost.
|
|
16191
16757
|
rule(
|
|
16192
16758
|
"reaches",
|
|
16193
16759
|
[v("x"), v("z"), v("kind")],
|
|
@@ -16197,27 +16763,27 @@ var RESOLUTION_RULES = [
|
|
|
16197
16763
|
"reaches",
|
|
16198
16764
|
[v("x"), v("z"), v("kind")],
|
|
16199
16765
|
[
|
|
16200
|
-
lit("
|
|
16201
|
-
lit("
|
|
16766
|
+
lit("reaches", v("x"), v("y"), VALUE_STEP),
|
|
16767
|
+
lit("stepsTo", v("y"), v("z"), v("kind"))
|
|
16202
16768
|
]
|
|
16203
16769
|
),
|
|
16204
|
-
// Two rules rather than one leaving the
|
|
16205
|
-
//
|
|
16770
|
+
// Two rules rather than one leaving the next step's kind free. A free
|
|
16771
|
+
// kind is a second question about the same relation, and a
|
|
16206
16772
|
// demand-driven run then derives both to answer either.
|
|
16207
16773
|
rule(
|
|
16208
16774
|
"reaches",
|
|
16209
16775
|
[v("x"), v("z"), RESULT_STEP],
|
|
16210
16776
|
[
|
|
16211
|
-
lit("
|
|
16212
|
-
lit("
|
|
16777
|
+
lit("reaches", v("x"), v("y"), RESULT_STEP),
|
|
16778
|
+
lit("stepsTo", v("y"), v("z"), VALUE_STEP)
|
|
16213
16779
|
]
|
|
16214
16780
|
),
|
|
16215
16781
|
rule(
|
|
16216
16782
|
"reaches",
|
|
16217
16783
|
[v("x"), v("z"), RESULT_STEP],
|
|
16218
16784
|
[
|
|
16219
|
-
lit("
|
|
16220
|
-
lit("
|
|
16785
|
+
lit("reaches", v("x"), v("y"), RESULT_STEP),
|
|
16786
|
+
lit("stepsTo", v("y"), v("z"), RESULT_STEP)
|
|
16221
16787
|
]
|
|
16222
16788
|
),
|
|
16223
16789
|
// What a value comes down to: the walk that never ran a call, stopped
|
|
@@ -16263,6 +16829,18 @@ var RESOLUTION_RULES = [
|
|
|
16263
16829
|
[v("x"), v("z")],
|
|
16264
16830
|
[lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
|
|
16265
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
|
+
),
|
|
16266
16844
|
// What one call site put in a parameter, told apart from what the
|
|
16267
16845
|
// other callers passed.
|
|
16268
16846
|
rule(
|
|
@@ -16563,29 +17141,14 @@ var RESOLUTION_QUESTIONS = [
|
|
|
16563
17141
|
// here demands reaches or callsInto. The chains are closures with
|
|
16564
17142
|
// no depth bound: an alias run of any length resolves.
|
|
16565
17143
|
rule("callOriginChain", [v("x"), v("x")], [lit("wantedCallOrigin", v("x"))]),
|
|
17144
|
+
// The chain with at least one hop taken, which is what tells a
|
|
17145
|
+
// destructured binding apart from asking about a node directly. The
|
|
17146
|
+
// whole chain is the seed plus those hops.
|
|
16566
17147
|
rule(
|
|
16567
17148
|
"callOriginChain",
|
|
16568
17149
|
[v("x"), v("z")],
|
|
16569
|
-
[lit("
|
|
16570
|
-
),
|
|
16571
|
-
rule(
|
|
16572
|
-
"callOriginChain",
|
|
16573
|
-
[v("x"), v("z")],
|
|
16574
|
-
[
|
|
16575
|
-
lit("callOriginChain", v("x"), v("y")),
|
|
16576
|
-
lit("endsHolding", v("y"), v("z"))
|
|
16577
|
-
]
|
|
16578
|
-
),
|
|
16579
|
-
rule(
|
|
16580
|
-
"callOriginChain",
|
|
16581
|
-
[v("x"), v("z")],
|
|
16582
|
-
[
|
|
16583
|
-
lit("callOriginChain", v("x"), v("y")),
|
|
16584
|
-
lit("fallbackBranch", v("y"), v("z"))
|
|
16585
|
-
]
|
|
17150
|
+
[lit("callOriginChainStepped", v("x"), v("z"))]
|
|
16586
17151
|
),
|
|
16587
|
-
// The chain with at least one hop taken, which is what tells a
|
|
16588
|
-
// destructured binding apart from asking about a node directly.
|
|
16589
17152
|
rule(
|
|
16590
17153
|
"callOriginChainStepped",
|
|
16591
17154
|
[v("x"), v("z")],
|
|
@@ -16839,11 +17402,11 @@ var ANCESTRY_PROGRAM = deriveOnDemand(
|
|
|
16839
17402
|
);
|
|
16840
17403
|
function reachesBase(facts, classKey, bases) {
|
|
16841
17404
|
facts.add("wantedAncestry", [classKey]);
|
|
16842
|
-
|
|
17405
|
+
evaluate2(facts, ANCESTRY_PROGRAM.rules);
|
|
16843
17406
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
16844
17407
|
}
|
|
16845
|
-
function classBehind(facts,
|
|
16846
|
-
const wanted = `#${
|
|
17408
|
+
function classBehind(facts, constant3) {
|
|
17409
|
+
const wanted = `#${constant3.text}`;
|
|
16847
17410
|
const bound = new Set(
|
|
16848
17411
|
facts.facts("binds").filter((row) => String(row[0]).endsWith(wanted)).map((row) => String(row[1]))
|
|
16849
17412
|
);
|
|
@@ -16886,11 +17449,11 @@ function storageEffects(calls, options) {
|
|
|
16886
17449
|
if (partOfOne.has(call.id)) {
|
|
16887
17450
|
continue;
|
|
16888
17451
|
}
|
|
16889
|
-
const
|
|
16890
|
-
if (
|
|
17452
|
+
const constant3 = rootConstant(call);
|
|
17453
|
+
if (constant3 === null) {
|
|
16891
17454
|
continue;
|
|
16892
17455
|
}
|
|
16893
|
-
const classKey = classBehind(options.facts,
|
|
17456
|
+
const classKey = classBehind(options.facts, constant3);
|
|
16894
17457
|
if (classKey === void 0) {
|
|
16895
17458
|
continue;
|
|
16896
17459
|
}
|
|
@@ -16908,7 +17471,7 @@ function storageEffects(calls, options) {
|
|
|
16908
17471
|
recognition: "ruby-storage",
|
|
16909
17472
|
storageSystem: pattern.storageSystem,
|
|
16910
17473
|
scope: pattern.baseClasses[0] ?? "",
|
|
16911
|
-
container:
|
|
17474
|
+
container: constant3.text
|
|
16912
17475
|
}),
|
|
16913
17476
|
callee: call.text,
|
|
16914
17477
|
interaction: {
|
|
@@ -17024,7 +17587,8 @@ async function discoverUnits(root, options) {
|
|
|
17024
17587
|
const knownClasses = new Set(classes.map((info) => info.qualifiedName));
|
|
17025
17588
|
const fileBlocks = classes.map((info) => ({
|
|
17026
17589
|
info,
|
|
17027
|
-
knownClasses
|
|
17590
|
+
knownClasses,
|
|
17591
|
+
file: options.absoluteFile ?? options.filePath
|
|
17028
17592
|
}));
|
|
17029
17593
|
const units = [];
|
|
17030
17594
|
for (const info of classes) {
|
|
@@ -17048,16 +17612,17 @@ async function discoverUnits(root, options) {
|
|
|
17048
17612
|
}
|
|
17049
17613
|
return units;
|
|
17050
17614
|
}
|
|
17051
|
-
function reachesConfiguredBase(ancestry, self,
|
|
17615
|
+
function reachesConfiguredBase(ancestry, self, baseClassNames) {
|
|
17052
17616
|
return ancestry.some(
|
|
17053
|
-
(entry) => entry.name !== self &&
|
|
17617
|
+
(entry) => entry.name !== self && baseClassNames.includes(entry.name)
|
|
17054
17618
|
);
|
|
17055
17619
|
}
|
|
17056
17620
|
function unitsFor(pattern, pack, info, ownBlocks, fileBlocks, options) {
|
|
17057
17621
|
const table = {
|
|
17058
|
-
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)
|
|
17059
17624
|
};
|
|
17060
|
-
return
|
|
17625
|
+
return dispatchByType2(table, pattern);
|
|
17061
17626
|
}
|
|
17062
17627
|
async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlocks, options) {
|
|
17063
17628
|
if (info.bodyNode === null || pattern.baseClassNames.includes(info.qualifiedName)) {
|
|
@@ -17070,7 +17635,7 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
|
|
|
17070
17635
|
options.storage
|
|
17071
17636
|
);
|
|
17072
17637
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, ctx.lookup);
|
|
17073
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern)) {
|
|
17638
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
17074
17639
|
return [];
|
|
17075
17640
|
}
|
|
17076
17641
|
const typeName = graphqlTypeNameFromQualified(
|
|
@@ -17089,19 +17654,155 @@ async function graphqlObjectFieldUnits(pattern, pack, info, ownBlocks, fileBlock
|
|
|
17089
17654
|
declsByName.set(decl.fieldName, decl);
|
|
17090
17655
|
}
|
|
17091
17656
|
}
|
|
17092
|
-
return [...declsByName.values()].map(
|
|
17093
|
-
|
|
17094
|
-
|
|
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
|
+
});
|
|
17095
17664
|
}
|
|
17096
|
-
function
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
17101
|
-
|
|
17102
|
-
|
|
17103
|
-
|
|
17104
|
-
|
|
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
|
+
};
|
|
17796
|
+
}
|
|
17797
|
+
function blockConfiguresCall(pattern) {
|
|
17798
|
+
const names = /* @__PURE__ */ new Set([
|
|
17799
|
+
pattern.fieldCallName,
|
|
17800
|
+
pattern.typeCallName,
|
|
17801
|
+
pattern.argumentCallName
|
|
17802
|
+
]);
|
|
17803
|
+
return (call) => {
|
|
17804
|
+
const method = field(call, "method")?.text;
|
|
17805
|
+
return field(call, "receiver") === null && method !== void 0 && names.has(method);
|
|
17105
17806
|
};
|
|
17106
17807
|
}
|
|
17107
17808
|
function branchesFor(body, range) {
|
|
@@ -17132,12 +17833,15 @@ function branchesFor(body, range) {
|
|
|
17132
17833
|
}
|
|
17133
17834
|
function bodyOfMethod(method, storage) {
|
|
17134
17835
|
const effects = invocationEffects(method);
|
|
17135
|
-
const
|
|
17836
|
+
const extra = [
|
|
17837
|
+
...envReadEffects(method),
|
|
17838
|
+
...storage === void 0 ? [] : storageEffects(callsUnder(method), storage)
|
|
17839
|
+
];
|
|
17136
17840
|
return {
|
|
17137
17841
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
17138
17842
|
readings: [],
|
|
17139
17843
|
...effects.length > 0 ? { effects } : {},
|
|
17140
|
-
...
|
|
17844
|
+
...extra.length > 0 ? { extraEffects: extra } : {}
|
|
17141
17845
|
};
|
|
17142
17846
|
}
|
|
17143
17847
|
function callsUnder(node, found = []) {
|
|
@@ -17161,14 +17865,21 @@ function methodNotSettled(reason, range) {
|
|
|
17161
17865
|
}
|
|
17162
17866
|
function bodyFromLookup(found, range, subject, nothingThere, storage) {
|
|
17163
17867
|
const table = {
|
|
17164
|
-
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
|
+
}),
|
|
17165
17876
|
unsettled: (lookup) => methodNotSettled(
|
|
17166
17877
|
`${subject} could be answered by a method ${lookup.reason}, so whether one exists was not settled here`,
|
|
17167
17878
|
range
|
|
17168
17879
|
),
|
|
17169
17880
|
none: () => nothingThere
|
|
17170
17881
|
};
|
|
17171
|
-
return
|
|
17882
|
+
return dispatchByType2(table, found);
|
|
17172
17883
|
}
|
|
17173
17884
|
async function readFieldCall(stmt, scope, ctx, ancestry) {
|
|
17174
17885
|
if (stmt.type !== "call" || field(stmt, "receiver") !== null) {
|
|
@@ -17453,59 +18164,6 @@ function buildFieldUnit(pack, typeName, decl, filePath) {
|
|
|
17453
18164
|
};
|
|
17454
18165
|
}
|
|
17455
18166
|
|
|
17456
|
-
// src/facts.ts
|
|
17457
|
-
function unitKey(filePath, range, name) {
|
|
17458
|
-
return `${filePath}:${range.start}-${range.end}#${name}`;
|
|
17459
|
-
}
|
|
17460
|
-
function emitEntryFact(db, filePath, range, name) {
|
|
17461
|
-
db.add("entry", [unitKey(filePath, range, name)]);
|
|
17462
|
-
}
|
|
17463
|
-
|
|
17464
|
-
// src/parser.ts
|
|
17465
|
-
import fs2 from "fs";
|
|
17466
|
-
import path2 from "path";
|
|
17467
|
-
import { fileURLToPath } from "url";
|
|
17468
|
-
import { Language, Parser } from "web-tree-sitter";
|
|
17469
|
-
var GRAMMAR_PATH = path2.join(
|
|
17470
|
-
path2.dirname(fileURLToPath(import.meta.url)),
|
|
17471
|
-
"..",
|
|
17472
|
-
"grammar",
|
|
17473
|
-
"tree-sitter-ruby.wasm"
|
|
17474
|
-
);
|
|
17475
|
-
var languagePromise = null;
|
|
17476
|
-
function loadLanguage() {
|
|
17477
|
-
languagePromise ??= (async () => {
|
|
17478
|
-
if (!fs2.existsSync(GRAMMAR_PATH)) {
|
|
17479
|
-
throw new Error(
|
|
17480
|
-
`Ruby grammar not found at ${GRAMMAR_PATH}. See grammar/README.md.`
|
|
17481
|
-
);
|
|
17482
|
-
}
|
|
17483
|
-
await Parser.init();
|
|
17484
|
-
return Language.load(GRAMMAR_PATH);
|
|
17485
|
-
})();
|
|
17486
|
-
return languagePromise;
|
|
17487
|
-
}
|
|
17488
|
-
async function parseRuby(source) {
|
|
17489
|
-
const language = await loadLanguage();
|
|
17490
|
-
const parser = new Parser();
|
|
17491
|
-
parser.setLanguage(language);
|
|
17492
|
-
try {
|
|
17493
|
-
const tree = parser.parse(source);
|
|
17494
|
-
if (tree === null) {
|
|
17495
|
-
throw new Error("tree-sitter produced no tree for this source");
|
|
17496
|
-
}
|
|
17497
|
-
return tree;
|
|
17498
|
-
} finally {
|
|
17499
|
-
parser.delete();
|
|
17500
|
-
}
|
|
17501
|
-
}
|
|
17502
|
-
|
|
17503
|
-
// src/project.ts
|
|
17504
|
-
import fs3 from "fs";
|
|
17505
|
-
import path3 from "path";
|
|
17506
|
-
import { Database } from "@suss/datalog";
|
|
17507
|
-
import { assembleSummary } from "@suss/extractor";
|
|
17508
|
-
|
|
17509
18167
|
// src/facts/values.ts
|
|
17510
18168
|
function nodeId(filePath, node) {
|
|
17511
18169
|
return `${filePath}:${node.startIndex}-${node.endIndex}`;
|
|
@@ -17513,6 +18171,33 @@ function nodeId(filePath, node) {
|
|
|
17513
18171
|
function nameId(filePath, name) {
|
|
17514
18172
|
return `${filePath}#${name}`;
|
|
17515
18173
|
}
|
|
18174
|
+
function paramNameOf(param) {
|
|
18175
|
+
return param.type === "identifier" ? param : field(param, "name") ?? null;
|
|
18176
|
+
}
|
|
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);
|
|
18184
|
+
}
|
|
18185
|
+
}
|
|
18186
|
+
return declared;
|
|
18187
|
+
}
|
|
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);
|
|
18200
|
+
}
|
|
17516
18201
|
var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
17517
18202
|
"string",
|
|
17518
18203
|
"integer",
|
|
@@ -17673,6 +18358,9 @@ function emitExpressionFacts(emitter, node) {
|
|
|
17673
18358
|
if (WRITTEN_VALUE_TYPES.has(child.type)) {
|
|
17674
18359
|
add(emitter, "writtenValue", nodeId(emitter.filePath, child));
|
|
17675
18360
|
}
|
|
18361
|
+
if (child.type === "nil") {
|
|
18362
|
+
add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
|
|
18363
|
+
}
|
|
17676
18364
|
});
|
|
17677
18365
|
}
|
|
17678
18366
|
function implicitReturn(body) {
|
|
@@ -17689,7 +18377,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
17689
18377
|
const declared = /* @__PURE__ */ new Set();
|
|
17690
18378
|
let position = 0;
|
|
17691
18379
|
for (const param of params === null ? [] : children3(params)) {
|
|
17692
|
-
const paramName =
|
|
18380
|
+
const paramName = paramNameOf(param);
|
|
17693
18381
|
if (paramName !== null) {
|
|
17694
18382
|
declared.add(paramName.text);
|
|
17695
18383
|
const paramKey = `${funcKey}#${paramName.text}`;
|
|
@@ -17784,6 +18472,10 @@ function emitClassFacts(emitter, cls) {
|
|
|
17784
18472
|
}
|
|
17785
18473
|
continue;
|
|
17786
18474
|
}
|
|
18475
|
+
if (NESTING_TYPES.has(statement.type)) {
|
|
18476
|
+
emitClassFacts(emitter, statement);
|
|
18477
|
+
continue;
|
|
18478
|
+
}
|
|
17787
18479
|
if (!METHOD_TYPES.has(statement.type)) {
|
|
17788
18480
|
continue;
|
|
17789
18481
|
}
|
|
@@ -17807,7 +18499,7 @@ function emitValueFacts(db, filePath, root) {
|
|
|
17807
18499
|
};
|
|
17808
18500
|
const walk = (node) => {
|
|
17809
18501
|
for (const child of children3(node)) {
|
|
17810
|
-
if (child.type
|
|
18502
|
+
if (NESTING_TYPES.has(child.type)) {
|
|
17811
18503
|
declaresName(child, emitClassFacts(emitter, child));
|
|
17812
18504
|
continue;
|
|
17813
18505
|
}
|
|
@@ -17825,7 +18517,6 @@ function emitValueFacts(db, filePath, root) {
|
|
|
17825
18517
|
}
|
|
17826
18518
|
|
|
17827
18519
|
// src/facts/constants.ts
|
|
17828
|
-
var NESTING_TYPES = /* @__PURE__ */ new Set(["class", "module"]);
|
|
17829
18520
|
function children4(node) {
|
|
17830
18521
|
return node.namedChildren.filter((child) => child !== null);
|
|
17831
18522
|
}
|
|
@@ -17903,16 +18594,18 @@ function collectFileConstants(filePath, root) {
|
|
|
17903
18594
|
}
|
|
17904
18595
|
};
|
|
17905
18596
|
walk(root, null, []);
|
|
17906
|
-
return { definitions, references };
|
|
18597
|
+
return { filePath, definitions, references };
|
|
17907
18598
|
}
|
|
17908
18599
|
function emitConstantBindings(db, perFile) {
|
|
17909
18600
|
const byName = /* @__PURE__ */ new Map();
|
|
18601
|
+
const fileOfDefinition = /* @__PURE__ */ new Map();
|
|
17910
18602
|
const files = [...perFile];
|
|
17911
18603
|
for (const file2 of files) {
|
|
17912
18604
|
for (const definition of file2.definitions) {
|
|
17913
18605
|
const found = byName.get(definition.qualifiedName) ?? [];
|
|
17914
18606
|
found.push(definition.key);
|
|
17915
18607
|
byName.set(definition.qualifiedName, found);
|
|
18608
|
+
fileOfDefinition.set(definition.key, file2.filePath);
|
|
17916
18609
|
}
|
|
17917
18610
|
}
|
|
17918
18611
|
const settles = (candidate) => {
|
|
@@ -17929,6 +18622,10 @@ function emitConstantBindings(db, perFile) {
|
|
|
17929
18622
|
const key = settles(candidate);
|
|
17930
18623
|
if (key !== null) {
|
|
17931
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
|
+
}
|
|
17932
18629
|
break;
|
|
17933
18630
|
}
|
|
17934
18631
|
}
|
|
@@ -17936,10 +18633,682 @@ function emitConstantBindings(db, perFile) {
|
|
|
17936
18633
|
}
|
|
17937
18634
|
}
|
|
17938
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
|
+
|
|
17939
19284
|
// src/project.ts
|
|
17940
19285
|
async function extractRubyProject(options) {
|
|
17941
|
-
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();
|
|
17942
19310
|
const summaries = [];
|
|
19311
|
+
const tallies = createPackTallies(options.packs);
|
|
17943
19312
|
const constants = [];
|
|
17944
19313
|
const cache = createFileCache(
|
|
17945
19314
|
(source) => parseRuby(source).then((tree) => tree.rootNode),
|
|
@@ -17947,33 +19316,178 @@ async function extractRubyProject(options) {
|
|
|
17947
19316
|
);
|
|
17948
19317
|
const parsed = [];
|
|
17949
19318
|
for (const file2 of options.files) {
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
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
|
+
});
|
|
17957
19328
|
}
|
|
17958
|
-
|
|
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
|
+
});
|
|
17959
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();
|
|
17960
19340
|
for (const { file: file2, root } of parsed) {
|
|
17961
|
-
const displayPath =
|
|
17962
|
-
const
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
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
|
+
);
|
|
17968
19354
|
for (const raw of rawUnits) {
|
|
17969
|
-
const summary =
|
|
19355
|
+
const summary = timer.time(
|
|
19356
|
+
"summarize",
|
|
19357
|
+
() => assembleSummary2(raw, { gapHandling: "permissive" })
|
|
19358
|
+
);
|
|
17970
19359
|
summary.confidence = { source: "inferred_static", level: "low" };
|
|
17971
19360
|
summaries.push(summary);
|
|
17972
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);
|
|
17973
19397
|
}
|
|
17974
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);
|
|
19416
|
+
}
|
|
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());
|
|
17975
19474
|
return { summaries, facts: db };
|
|
17976
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
|
+
}
|
|
17977
19491
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["vendor", "node_modules", "tmp", ".git"]);
|
|
17978
19492
|
function findRubyFiles(root) {
|
|
17979
19493
|
const found = [];
|
|
@@ -17981,32 +19495,306 @@ function findRubyFiles(root) {
|
|
|
17981
19495
|
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
17982
19496
|
if (entry.isDirectory()) {
|
|
17983
19497
|
if (!SKIPPED_DIRECTORIES.has(entry.name)) {
|
|
17984
|
-
walk(
|
|
19498
|
+
walk(path4.join(dir, entry.name));
|
|
17985
19499
|
}
|
|
17986
19500
|
continue;
|
|
17987
19501
|
}
|
|
17988
19502
|
if (entry.isFile() && entry.name.endsWith(".rb")) {
|
|
17989
|
-
found.push(
|
|
19503
|
+
found.push(path4.join(dir, entry.name));
|
|
17990
19504
|
}
|
|
17991
19505
|
}
|
|
17992
19506
|
};
|
|
17993
19507
|
walk(root);
|
|
17994
19508
|
return found.sort();
|
|
17995
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
|
+
}
|
|
17996
19766
|
export {
|
|
19767
|
+
ADAPTER_VERSION,
|
|
19768
|
+
RubyWhySession,
|
|
17997
19769
|
ancestryOf,
|
|
19770
|
+
bodyStatements,
|
|
19771
|
+
booleanLiteralValue,
|
|
19772
|
+
collectFileConstants,
|
|
17998
19773
|
createFileCache,
|
|
17999
19774
|
discoverUnits,
|
|
19775
|
+
emitConstantBindings,
|
|
18000
19776
|
emitEntryFact,
|
|
19777
|
+
emitValueFacts,
|
|
18001
19778
|
extractRubyProject,
|
|
19779
|
+
field,
|
|
18002
19780
|
findRubyFiles,
|
|
18003
19781
|
graphqlTypeNameFromQualified,
|
|
19782
|
+
hashKeySymbolName,
|
|
18004
19783
|
inheritedStatements,
|
|
18005
19784
|
methodInAncestry,
|
|
19785
|
+
nodeId,
|
|
18006
19786
|
parseRuby,
|
|
19787
|
+
parseRubySync,
|
|
19788
|
+
preloadRubyGrammar,
|
|
18007
19789
|
qualifyConstantRef,
|
|
18008
19790
|
reachDefinition,
|
|
19791
|
+
readCallArgs,
|
|
19792
|
+
readKey,
|
|
18009
19793
|
resolveConstantFile,
|
|
19794
|
+
rubyStubEvidence,
|
|
19795
|
+
runStatements,
|
|
19796
|
+
stringLiteralValue,
|
|
19797
|
+
symbolValue,
|
|
18010
19798
|
typeShapeFromNode,
|
|
18011
19799
|
underscoreConstantPath,
|
|
18012
19800
|
unitKey,
|