@suss/adapter-ruby 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +305 -112
- package/dist/index.d.cts +75 -16
- package/dist/index.d.ts +75 -16
- package/dist/index.js +305 -113
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
inheritedStatements: () => inheritedStatements,
|
|
52
52
|
methodDefinitionsIn: () => methodDefinitionsIn,
|
|
53
53
|
methodInAncestry: () => methodInAncestry,
|
|
54
|
+
nestedStatements: () => nestedStatements,
|
|
54
55
|
nodeId: () => nodeId,
|
|
55
56
|
parseRuby: () => parseRuby,
|
|
56
57
|
parseRubySync: () => parseRubySync,
|
|
@@ -94,6 +95,45 @@ function field(node, name) {
|
|
|
94
95
|
function bodyStatements(body) {
|
|
95
96
|
return body.namedChildren.filter((child) => child !== null);
|
|
96
97
|
}
|
|
98
|
+
var BRANCH_FIELDS = {
|
|
99
|
+
if: ["consequence", "alternative"],
|
|
100
|
+
unless: ["consequence", "alternative"],
|
|
101
|
+
elsif: ["consequence", "alternative"],
|
|
102
|
+
if_modifier: ["body"],
|
|
103
|
+
unless_modifier: ["body"],
|
|
104
|
+
while: ["body"],
|
|
105
|
+
until: ["body"],
|
|
106
|
+
while_modifier: ["body"],
|
|
107
|
+
until_modifier: ["body"],
|
|
108
|
+
when: ["body"],
|
|
109
|
+
rescue: ["body"]
|
|
110
|
+
};
|
|
111
|
+
var STATEMENT_LISTS = /* @__PURE__ */ new Set(["then", "else", "do", "ensure", "begin"]);
|
|
112
|
+
var CLAUSE_TYPES = /* @__PURE__ */ new Set(["elsif", "else", "when", "rescue", "ensure"]);
|
|
113
|
+
function nestedStatements(stmt) {
|
|
114
|
+
const branchFields = BRANCH_FIELDS[stmt.type];
|
|
115
|
+
if (branchFields !== void 0) {
|
|
116
|
+
return branchFields.flatMap((name) => {
|
|
117
|
+
const branch = field(stmt, name);
|
|
118
|
+
return branch === null ? [] : statementsRunBy(branch);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (STATEMENT_LISTS.has(stmt.type)) {
|
|
122
|
+
return bodyStatements(stmt).flatMap(
|
|
123
|
+
(child) => CLAUSE_TYPES.has(child.type) ? nestedStatements(child) : [child]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (stmt.type === "case") {
|
|
127
|
+
return bodyStatements(stmt).filter((child) => CLAUSE_TYPES.has(child.type)).flatMap(nestedStatements);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
function statementsRunBy(branch) {
|
|
132
|
+
if (STATEMENT_LISTS.has(branch.type) || CLAUSE_TYPES.has(branch.type)) {
|
|
133
|
+
return nestedStatements(branch);
|
|
134
|
+
}
|
|
135
|
+
return [branch];
|
|
136
|
+
}
|
|
97
137
|
var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
98
138
|
"method",
|
|
99
139
|
"singleton_method",
|
|
@@ -157,8 +197,8 @@ var VISIBILITY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
157
197
|
"protected",
|
|
158
198
|
"public"
|
|
159
199
|
]);
|
|
160
|
-
function visibilityKeyword(
|
|
161
|
-
return VISIBILITY_KEYWORDS.has(
|
|
200
|
+
function visibilityKeyword(text2) {
|
|
201
|
+
return VISIBILITY_KEYWORDS.has(text2) ? text2 : null;
|
|
162
202
|
}
|
|
163
203
|
function setVisibility(visibility, name, keyword) {
|
|
164
204
|
if (keyword === "public") {
|
|
@@ -281,14 +321,31 @@ var NodeMap = class extends import_extractor.IdMap {
|
|
|
281
321
|
// src/constantPath.ts
|
|
282
322
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
283
323
|
var import_node_path = __toESM(require("path"), 1);
|
|
284
|
-
function underscoreConstantPath(qualifiedName) {
|
|
285
|
-
return qualifiedName.replaceAll("::", "/").replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
324
|
+
function underscoreConstantPath(qualifiedName, acronyms = []) {
|
|
325
|
+
return lowerAcronyms(qualifiedName.replaceAll("::", "/"), acronyms).replace(/([A-Z\d]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replaceAll("-", "_").toLowerCase();
|
|
326
|
+
}
|
|
327
|
+
function lowerAcronyms(word, acronyms) {
|
|
328
|
+
if (acronyms.length === 0) {
|
|
329
|
+
return word;
|
|
330
|
+
}
|
|
331
|
+
const alternatives = acronyms.map(escapeRegExp).join("|");
|
|
332
|
+
const acronymRegex = new RegExp(
|
|
333
|
+
`(?:(?<=([A-Za-z\\d]))|\\b)(${alternatives})(?=\\b|[^a-z])`,
|
|
334
|
+
"g"
|
|
335
|
+
);
|
|
336
|
+
return word.replace(
|
|
337
|
+
acronymRegex,
|
|
338
|
+
(_match, before, acronym) => `${before === void 0 ? "" : "_"}${acronym.toLowerCase()}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
function escapeRegExp(text2) {
|
|
342
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
286
343
|
}
|
|
287
344
|
var PATH_CONVENTIONS = {
|
|
288
345
|
railsUnderscore: underscoreConstantPath
|
|
289
346
|
};
|
|
290
|
-
function resolveConstantFile(root, qualifiedName, convention) {
|
|
291
|
-
const relative = `${PATH_CONVENTIONS[convention](qualifiedName)}.rb`;
|
|
347
|
+
function resolveConstantFile(root, qualifiedName, convention, acronyms = []) {
|
|
348
|
+
const relative = `${PATH_CONVENTIONS[convention](qualifiedName, acronyms)}.rb`;
|
|
292
349
|
for (const candidate of [root, ...autoloadDirectories(root)]) {
|
|
293
350
|
const file = import_node_path.default.join(candidate, relative);
|
|
294
351
|
if (import_node_fs.default.existsSync(file)) {
|
|
@@ -308,7 +365,7 @@ function autoloadDirectories(root) {
|
|
|
308
365
|
// src/scope.ts
|
|
309
366
|
function qualifyConstantRef(node, nesting) {
|
|
310
367
|
if (node.type === "scope_resolution") {
|
|
311
|
-
return node
|
|
368
|
+
return compoundName(node);
|
|
312
369
|
}
|
|
313
370
|
if (node.type === "constant") {
|
|
314
371
|
const innermost = nesting[0] ?? null;
|
|
@@ -318,13 +375,16 @@ function qualifyConstantRef(node, nesting) {
|
|
|
318
375
|
}
|
|
319
376
|
function constantRefCandidates(node, nesting) {
|
|
320
377
|
if (node.type === "scope_resolution") {
|
|
321
|
-
return [node
|
|
378
|
+
return [compoundName(node)];
|
|
322
379
|
}
|
|
323
380
|
if (node.type === "constant") {
|
|
324
381
|
return [...nesting.map((level) => `${level}::${node.text}`), node.text];
|
|
325
382
|
}
|
|
326
383
|
return [];
|
|
327
384
|
}
|
|
385
|
+
function compoundName(node) {
|
|
386
|
+
return node.text.replace(/^::/, "");
|
|
387
|
+
}
|
|
328
388
|
function shadowingClassFor(node, nesting, knownClasses) {
|
|
329
389
|
if (node.type !== "constant") {
|
|
330
390
|
return null;
|
|
@@ -359,12 +419,19 @@ function walkBody(statements, nesting, visit) {
|
|
|
359
419
|
}
|
|
360
420
|
}
|
|
361
421
|
function ownIdentity(nameNode, outerNesting) {
|
|
362
|
-
const qualifiedName =
|
|
422
|
+
const qualifiedName = definedName(nameNode, outerNesting);
|
|
363
423
|
if (qualifiedName === null) {
|
|
364
424
|
return null;
|
|
365
425
|
}
|
|
366
426
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
367
427
|
}
|
|
428
|
+
function definedName(nameNode, outerNesting) {
|
|
429
|
+
const innermost = outerNesting[0] ?? null;
|
|
430
|
+
if (nameNode.type === "scope_resolution" && innermost !== null && !nameNode.text.startsWith("::")) {
|
|
431
|
+
return `${innermost}::${nameNode.text}`;
|
|
432
|
+
}
|
|
433
|
+
return qualifyConstantRef(nameNode, outerNesting);
|
|
434
|
+
}
|
|
368
435
|
function visitClass(node, nesting, visit) {
|
|
369
436
|
const nameNode = field(node, "name");
|
|
370
437
|
const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
|
|
@@ -428,7 +495,8 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
428
495
|
const filePath = resolveConstantFile(
|
|
429
496
|
lookup.root,
|
|
430
497
|
qualifiedName,
|
|
431
|
-
lookup.pathConvention
|
|
498
|
+
lookup.pathConvention,
|
|
499
|
+
lookup.acronyms
|
|
432
500
|
);
|
|
433
501
|
if (filePath === null) {
|
|
434
502
|
return null;
|
|
@@ -470,7 +538,10 @@ async function chainOf(self, lookup, active) {
|
|
|
470
538
|
async function superclassChain(self, lookup, active) {
|
|
471
539
|
const candidates = superclassCandidatesOf(self.blocks);
|
|
472
540
|
for (const candidate of candidates) {
|
|
473
|
-
if (lookup.ancestryRootClassNames.includes(candidate)
|
|
541
|
+
if (lookup.ancestryRootClassNames.includes(candidate)) {
|
|
542
|
+
return [{ type: "root", name: candidate }];
|
|
543
|
+
}
|
|
544
|
+
if (active.has(candidate)) {
|
|
474
545
|
return [];
|
|
475
546
|
}
|
|
476
547
|
const blocks = await definitionOf(candidate, lookup);
|
|
@@ -548,6 +619,9 @@ function superclassCandidatesOf(blocks) {
|
|
|
548
619
|
}
|
|
549
620
|
function methodInAncestry(ancestry, name) {
|
|
550
621
|
for (const entry of ancestry) {
|
|
622
|
+
if (entry.type === "root") {
|
|
623
|
+
return { type: "none" };
|
|
624
|
+
}
|
|
551
625
|
if (entry.type === "unfollowed") {
|
|
552
626
|
return {
|
|
553
627
|
type: "unsettled",
|
|
@@ -850,7 +924,7 @@ var Lowerer = class {
|
|
|
850
924
|
return [nested];
|
|
851
925
|
}
|
|
852
926
|
/**
|
|
853
|
-
* `unless ok` runs its body when `ok` is false, so it
|
|
927
|
+
* `unless ok` runs its body when `ok` is false, so it lowers to an `if` on
|
|
854
928
|
* the same test with the two arms the other way around. Without the swap
|
|
855
929
|
* a summary would say a branch runs on the test that skips it.
|
|
856
930
|
*/
|
|
@@ -866,7 +940,7 @@ var Lowerer = class {
|
|
|
866
940
|
exitKind: exitKindOf(node)
|
|
867
941
|
};
|
|
868
942
|
}
|
|
869
|
-
/** `render :gone if expired?` gates one statement on one test, so it
|
|
943
|
+
/** `render :gone if expired?` gates one statement on one test, so it lowers to an if with one arm, on whichever side the test puts it. */
|
|
870
944
|
lowerIfModifier(node) {
|
|
871
945
|
const body = field(node, "body");
|
|
872
946
|
const gated = body === null ? [] : [this.lower(body)];
|
|
@@ -2620,11 +2694,18 @@ function bindEvaluator(db, nodes) {
|
|
|
2620
2694
|
)
|
|
2621
2695
|
);
|
|
2622
2696
|
}
|
|
2623
|
-
function evaluatedValue(node, db) {
|
|
2624
|
-
|
|
2697
|
+
function evaluatedValue(node, db, bindings) {
|
|
2698
|
+
const evaluator = evaluatorFor(node, db);
|
|
2699
|
+
if (bindings === void 0) {
|
|
2700
|
+
return (0, import_values2.force)(evaluator.evaluate(node));
|
|
2701
|
+
}
|
|
2702
|
+
const supplied = new Map(
|
|
2703
|
+
[...bindings].map(([name, value]) => [name, (0, import_values2.text)(value)])
|
|
2704
|
+
);
|
|
2705
|
+
return (0, import_values2.force)(evaluator.evaluate(node, { bindings: supplied }));
|
|
2625
2706
|
}
|
|
2626
|
-
function stringValueOf(node, db) {
|
|
2627
|
-
return (0, import_values2.literalOf)(evaluatedValue(node, db));
|
|
2707
|
+
function stringValueOf(node, db, bindings) {
|
|
2708
|
+
return (0, import_values2.literalOf)(evaluatedValue(node, db, bindings));
|
|
2628
2709
|
}
|
|
2629
2710
|
function methodDefinitionsIn(file, root) {
|
|
2630
2711
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -3044,8 +3125,11 @@ function receiverPrefix(receiver, method, root, pattern, options) {
|
|
|
3044
3125
|
function trimmed(path7) {
|
|
3045
3126
|
return path7 === "/" ? "" : path7.replace(/\/+$/, "");
|
|
3046
3127
|
}
|
|
3047
|
-
function namesConstant(receiver,
|
|
3048
|
-
|
|
3128
|
+
function namesConstant(receiver, constantName2) {
|
|
3129
|
+
if (receiver.type === "constant") {
|
|
3130
|
+
return receiver.text === constantName2;
|
|
3131
|
+
}
|
|
3132
|
+
return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
|
|
3049
3133
|
}
|
|
3050
3134
|
function builtBy(name, method, root, pattern) {
|
|
3051
3135
|
const builders = pattern.receiverBuilders ?? [];
|
|
@@ -3282,8 +3366,7 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3282
3366
|
skipNames.set(form.skippedBy, form.name);
|
|
3283
3367
|
}
|
|
3284
3368
|
}
|
|
3285
|
-
|
|
3286
|
-
const skips = [];
|
|
3369
|
+
let declared = [];
|
|
3287
3370
|
for (const { statement } of inheritedStatements(ancestry)) {
|
|
3288
3371
|
const called = calledName(statement);
|
|
3289
3372
|
if (called === null) {
|
|
@@ -3291,16 +3374,21 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3291
3374
|
}
|
|
3292
3375
|
const form = byName.get(called);
|
|
3293
3376
|
if (form !== void 0) {
|
|
3294
|
-
|
|
3377
|
+
for (const declaration of declarationsOf(statement, form)) {
|
|
3378
|
+
declared = [
|
|
3379
|
+
...declared.filter((earlier) => !sameFilter(earlier, declaration)),
|
|
3380
|
+
declaration
|
|
3381
|
+
];
|
|
3382
|
+
}
|
|
3295
3383
|
continue;
|
|
3296
3384
|
}
|
|
3297
3385
|
const skipped = skipNames.get(called);
|
|
3298
3386
|
if (skipped !== void 0) {
|
|
3299
|
-
|
|
3387
|
+
declared = applySkips(declared, skipsOf(statement, skipped));
|
|
3300
3388
|
}
|
|
3301
3389
|
}
|
|
3302
3390
|
const resolved = [];
|
|
3303
|
-
for (const declaration of
|
|
3391
|
+
for (const declaration of declared) {
|
|
3304
3392
|
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
3305
3393
|
if (found.type !== "found") {
|
|
3306
3394
|
continue;
|
|
@@ -3413,6 +3501,9 @@ function skipsOf(statement, filterName) {
|
|
|
3413
3501
|
const actions = namesUnder(args.keyword.only);
|
|
3414
3502
|
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
3415
3503
|
}
|
|
3504
|
+
function sameFilter(one, other) {
|
|
3505
|
+
return one.filter.name === other.filter.name && one.methodName === other.methodName;
|
|
3506
|
+
}
|
|
3416
3507
|
function applySkips(declared, skips) {
|
|
3417
3508
|
const left = [];
|
|
3418
3509
|
for (const declaration of declared) {
|
|
@@ -3477,6 +3568,12 @@ function rootConstant(node) {
|
|
|
3477
3568
|
}
|
|
3478
3569
|
return rootConstant(receiver);
|
|
3479
3570
|
}
|
|
3571
|
+
function isConstant(node) {
|
|
3572
|
+
return node.type === "constant" || node.type === "scope_resolution";
|
|
3573
|
+
}
|
|
3574
|
+
function constantName(constant2) {
|
|
3575
|
+
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3576
|
+
}
|
|
3480
3577
|
var ANCESTRY_PROGRAM = (0, import_datalog2.deriveOnDemand)(
|
|
3481
3578
|
[...import_resolution3.RESOLUTION_RULES, ...import_resolution3.RESOLUTION_QUESTIONS],
|
|
3482
3579
|
import_resolution3.ANSWER_RELATIONS
|
|
@@ -3486,13 +3583,22 @@ function reachesBase(facts, classKey, bases) {
|
|
|
3486
3583
|
(0, import_datalog2.evaluate)(facts, ANCESTRY_PROGRAM.rules);
|
|
3487
3584
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3488
3585
|
}
|
|
3489
|
-
function classBehind(facts, constant2) {
|
|
3490
|
-
const
|
|
3586
|
+
function classBehind(facts, file, constant2) {
|
|
3587
|
+
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
3491
3588
|
const bound = new Set(
|
|
3492
|
-
facts.
|
|
3589
|
+
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
3493
3590
|
);
|
|
3494
3591
|
return bound.size === 1 ? [...bound][0] : void 0;
|
|
3495
3592
|
}
|
|
3593
|
+
function modelPattern(constant2, file, options) {
|
|
3594
|
+
const classKey = classBehind(options.facts, file, constant2);
|
|
3595
|
+
if (classKey === void 0) {
|
|
3596
|
+
return void 0;
|
|
3597
|
+
}
|
|
3598
|
+
return options.patterns.find(
|
|
3599
|
+
(candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
|
|
3600
|
+
);
|
|
3601
|
+
}
|
|
3496
3602
|
function selectorOf(node) {
|
|
3497
3603
|
const picked = [];
|
|
3498
3604
|
const walk = (current) => {
|
|
@@ -3514,7 +3620,87 @@ function selectorOf(node) {
|
|
|
3514
3620
|
walk(node);
|
|
3515
3621
|
return [...new Set(picked)];
|
|
3516
3622
|
}
|
|
3517
|
-
function
|
|
3623
|
+
function storageEffect(call, model, pattern, kind, selector) {
|
|
3624
|
+
const operation = methodOf(call);
|
|
3625
|
+
return {
|
|
3626
|
+
type: "interaction",
|
|
3627
|
+
binding: (0, import_ir_core.storageBinding)({
|
|
3628
|
+
recognition: "ruby-storage",
|
|
3629
|
+
storageSystem: pattern.storageSystem,
|
|
3630
|
+
scope: "default",
|
|
3631
|
+
container: constantName(model)
|
|
3632
|
+
}),
|
|
3633
|
+
callee: call.text,
|
|
3634
|
+
interaction: {
|
|
3635
|
+
class: "storage-access",
|
|
3636
|
+
kind,
|
|
3637
|
+
fields: [],
|
|
3638
|
+
operation,
|
|
3639
|
+
...selector.length > 0 ? { selector } : {}
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
function modelCallEffects(call, file, options) {
|
|
3644
|
+
const constant2 = rootConstant(call);
|
|
3645
|
+
if (constant2 === null) {
|
|
3646
|
+
return [];
|
|
3647
|
+
}
|
|
3648
|
+
const pattern = modelPattern(constant2, file, options);
|
|
3649
|
+
if (pattern === void 0) {
|
|
3650
|
+
return [];
|
|
3651
|
+
}
|
|
3652
|
+
const kind = pattern.writes.includes(methodOf(call)) ? "write" : "read";
|
|
3653
|
+
return [storageEffect(call, constant2, pattern, kind, selectorOf(call))];
|
|
3654
|
+
}
|
|
3655
|
+
function isLoader(node, loader) {
|
|
3656
|
+
if (node === null) {
|
|
3657
|
+
return false;
|
|
3658
|
+
}
|
|
3659
|
+
if (node.type === "identifier") {
|
|
3660
|
+
return node.text === loader.loader;
|
|
3661
|
+
}
|
|
3662
|
+
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
3663
|
+
}
|
|
3664
|
+
function loaderPick(call, loader) {
|
|
3665
|
+
const method = methodOf(call);
|
|
3666
|
+
const receiver = receiverOf(call);
|
|
3667
|
+
if (receiver === null) {
|
|
3668
|
+
return loader.shortcuts.includes(method) ? call : null;
|
|
3669
|
+
}
|
|
3670
|
+
if (!loader.reads.includes(method) || receiver.type !== "call") {
|
|
3671
|
+
return null;
|
|
3672
|
+
}
|
|
3673
|
+
const picks = methodOf(receiver) === loader.pick && isLoader(receiverOf(receiver), loader);
|
|
3674
|
+
return picks ? receiver : null;
|
|
3675
|
+
}
|
|
3676
|
+
function loaderCallEffects(call, file, options) {
|
|
3677
|
+
const effects = [];
|
|
3678
|
+
for (const loader of options.loaders ?? []) {
|
|
3679
|
+
const pick = loaderPick(call, loader);
|
|
3680
|
+
if (pick === null) {
|
|
3681
|
+
continue;
|
|
3682
|
+
}
|
|
3683
|
+
const args = field(pick, "arguments");
|
|
3684
|
+
for (const argument of args === null ? [] : children3(args)) {
|
|
3685
|
+
if (!isConstant(argument)) {
|
|
3686
|
+
continue;
|
|
3687
|
+
}
|
|
3688
|
+
const pattern = modelPattern(argument, file, options);
|
|
3689
|
+
if (pattern !== void 0) {
|
|
3690
|
+
effects.push(storageEffect(call, argument, pattern, "read", []));
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
return effects;
|
|
3695
|
+
}
|
|
3696
|
+
function effectsOfCall(call, file, options) {
|
|
3697
|
+
const onModel = modelCallEffects(call, file, options);
|
|
3698
|
+
return onModel.length > 0 ? onModel : loaderCallEffects(call, file, options);
|
|
3699
|
+
}
|
|
3700
|
+
function storageClaims(call, file, options) {
|
|
3701
|
+
return options.patterns.length > 0 && effectsOfCall(call, file, options).length > 0;
|
|
3702
|
+
}
|
|
3703
|
+
function storageEffects(calls, file, options) {
|
|
3518
3704
|
if (options.patterns.length === 0) {
|
|
3519
3705
|
return [];
|
|
3520
3706
|
}
|
|
@@ -3530,39 +3716,7 @@ function storageEffects(calls, options) {
|
|
|
3530
3716
|
if (partOfOne.has(call.id)) {
|
|
3531
3717
|
continue;
|
|
3532
3718
|
}
|
|
3533
|
-
|
|
3534
|
-
if (constant2 === null) {
|
|
3535
|
-
continue;
|
|
3536
|
-
}
|
|
3537
|
-
const classKey = classBehind(options.facts, constant2);
|
|
3538
|
-
if (classKey === void 0) {
|
|
3539
|
-
continue;
|
|
3540
|
-
}
|
|
3541
|
-
const pattern = options.patterns.find(
|
|
3542
|
-
(candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
|
|
3543
|
-
);
|
|
3544
|
-
if (pattern === void 0) {
|
|
3545
|
-
continue;
|
|
3546
|
-
}
|
|
3547
|
-
const operation = methodOf(call);
|
|
3548
|
-
const picked = selectorOf(call);
|
|
3549
|
-
effects.push({
|
|
3550
|
-
type: "interaction",
|
|
3551
|
-
binding: (0, import_ir_core.storageBinding)({
|
|
3552
|
-
recognition: "ruby-storage",
|
|
3553
|
-
storageSystem: pattern.storageSystem,
|
|
3554
|
-
scope: pattern.baseClasses[0] ?? "",
|
|
3555
|
-
container: constant2.text
|
|
3556
|
-
}),
|
|
3557
|
-
callee: call.text,
|
|
3558
|
-
interaction: {
|
|
3559
|
-
class: "storage-access",
|
|
3560
|
-
kind: pattern.writes.includes(operation) ? "write" : "read",
|
|
3561
|
-
fields: [],
|
|
3562
|
-
operation,
|
|
3563
|
-
...picked.length > 0 ? { selector: picked } : {}
|
|
3564
|
-
}
|
|
3565
|
-
});
|
|
3719
|
+
effects.push(...effectsOfCall(call, file, options));
|
|
3566
3720
|
}
|
|
3567
3721
|
return effects;
|
|
3568
3722
|
}
|
|
@@ -3604,7 +3758,7 @@ function typeShapeFromNode(node, ctx) {
|
|
|
3604
3758
|
};
|
|
3605
3759
|
}
|
|
3606
3760
|
if (node.type === "scope_resolution") {
|
|
3607
|
-
const qualified = node
|
|
3761
|
+
const qualified = compoundName(node);
|
|
3608
3762
|
const scalar = ctx.scalars[scalarLookupName(qualified, ctx.scalarNamePrefixes)];
|
|
3609
3763
|
if (scalar !== void 0) {
|
|
3610
3764
|
return scalar;
|
|
@@ -3641,6 +3795,7 @@ function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
|
|
|
3641
3795
|
lookup: {
|
|
3642
3796
|
root: pattern.root,
|
|
3643
3797
|
pathConvention: pattern.pathConvention,
|
|
3798
|
+
acronyms: pattern.acronyms ?? [],
|
|
3644
3799
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3645
3800
|
parsedFile: (absPath) => cache.get(absPath),
|
|
3646
3801
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
@@ -3755,12 +3910,16 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3755
3910
|
const lookup = {
|
|
3756
3911
|
root: pattern.root,
|
|
3757
3912
|
pathConvention: pattern.pathConvention,
|
|
3913
|
+
acronyms: pattern.acronyms ?? [],
|
|
3758
3914
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3759
3915
|
parsedFile: (absPath) => options.cache.get(absPath),
|
|
3760
3916
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
3761
3917
|
};
|
|
3762
3918
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, lookup);
|
|
3763
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName,
|
|
3919
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, [
|
|
3920
|
+
...pattern.baseClassNames,
|
|
3921
|
+
...pattern.ancestryRootClassNames
|
|
3922
|
+
])) {
|
|
3764
3923
|
return [];
|
|
3765
3924
|
}
|
|
3766
3925
|
const filters = controllerFilters(pattern, ancestry);
|
|
@@ -3771,7 +3930,7 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3771
3930
|
filter,
|
|
3772
3931
|
pattern,
|
|
3773
3932
|
displayPath,
|
|
3774
|
-
bodyOfMethod(filter.method, options)
|
|
3933
|
+
bodyOfMethod(filter.method, filter.file, options)
|
|
3775
3934
|
);
|
|
3776
3935
|
units.push(raw);
|
|
3777
3936
|
options.onReachSeed?.(raw, {
|
|
@@ -3780,47 +3939,77 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3780
3939
|
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3781
3940
|
});
|
|
3782
3941
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3942
|
+
const emitAction = (actionName, method, block) => {
|
|
3943
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
3944
|
+
(filter) => filterReference(
|
|
3945
|
+
filter,
|
|
3946
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
3947
|
+
)
|
|
3948
|
+
);
|
|
3949
|
+
const raw = buildControllerActionUnit(
|
|
3950
|
+
pack,
|
|
3951
|
+
pattern,
|
|
3952
|
+
info.qualifiedName,
|
|
3953
|
+
actionName,
|
|
3954
|
+
method,
|
|
3955
|
+
{
|
|
3956
|
+
display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
|
|
3957
|
+
absolute: block.file
|
|
3958
|
+
},
|
|
3959
|
+
options,
|
|
3960
|
+
around
|
|
3961
|
+
);
|
|
3962
|
+
units.push(raw);
|
|
3963
|
+
options.onReachSeed?.(raw, {
|
|
3964
|
+
file: block.file,
|
|
3965
|
+
node: method,
|
|
3966
|
+
enclosingQualifiedName: block.info.qualifiedName
|
|
3967
|
+
});
|
|
3968
|
+
};
|
|
3969
|
+
const own = /* @__PURE__ */ new Set();
|
|
3970
|
+
for (const [actionName, method, block] of publicInstanceMethods(ownBlocks)) {
|
|
3971
|
+
own.add(actionName);
|
|
3972
|
+
emitAction(actionName, method, block);
|
|
3973
|
+
}
|
|
3974
|
+
const seen = new Set(own);
|
|
3975
|
+
for (const entry of ancestry) {
|
|
3976
|
+
if (entry.type !== "bodies" || entry.name === info.qualifiedName) {
|
|
3785
3977
|
continue;
|
|
3786
3978
|
}
|
|
3787
|
-
const
|
|
3788
|
-
|
|
3789
|
-
block.info.bodyNode
|
|
3979
|
+
for (const [actionName, method, block] of publicInstanceMethods(
|
|
3980
|
+
entry.blocks
|
|
3790
3981
|
)) {
|
|
3791
|
-
if (
|
|
3982
|
+
if (seen.has(actionName)) {
|
|
3792
3983
|
continue;
|
|
3793
3984
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
);
|
|
3800
|
-
const raw = buildControllerActionUnit(
|
|
3801
|
-
pack,
|
|
3802
|
-
pattern,
|
|
3803
|
-
info.qualifiedName,
|
|
3804
|
-
actionName,
|
|
3805
|
-
method,
|
|
3806
|
-
options.filePath,
|
|
3807
|
-
options,
|
|
3808
|
-
around
|
|
3809
|
-
);
|
|
3810
|
-
units.push(raw);
|
|
3811
|
-
options.onReachSeed?.(raw, {
|
|
3812
|
-
file: block.file,
|
|
3813
|
-
node: method,
|
|
3814
|
-
enclosingQualifiedName: info.qualifiedName
|
|
3815
|
-
});
|
|
3985
|
+
seen.add(actionName);
|
|
3986
|
+
if (pattern.routeFor(info.qualifiedName, actionName) === null) {
|
|
3987
|
+
continue;
|
|
3988
|
+
}
|
|
3989
|
+
emitAction(actionName, method, block);
|
|
3816
3990
|
}
|
|
3817
3991
|
}
|
|
3818
3992
|
return units;
|
|
3819
3993
|
}
|
|
3820
|
-
function
|
|
3994
|
+
function publicInstanceMethods(blocks) {
|
|
3995
|
+
const found = [];
|
|
3996
|
+
for (const block of blocks) {
|
|
3997
|
+
if (block.info.bodyNode === null) {
|
|
3998
|
+
continue;
|
|
3999
|
+
}
|
|
4000
|
+
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
4001
|
+
for (const [name, method] of instanceMethodsByName(block.info.bodyNode)) {
|
|
4002
|
+
if ((visibility.get(name) ?? "public") === "public") {
|
|
4003
|
+
found.push([name, method, block]);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
return found;
|
|
4008
|
+
}
|
|
4009
|
+
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, file, bodyRead, wrappers = []) {
|
|
3821
4010
|
const range = rangeOf(method);
|
|
3822
4011
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
3823
|
-
const body = bodyOfMethod(method, bodyRead);
|
|
4012
|
+
const body = bodyOfMethod(method, file.absolute, bodyRead);
|
|
3824
4013
|
const perResponse = responseBranches(
|
|
3825
4014
|
method,
|
|
3826
4015
|
pattern,
|
|
@@ -3832,7 +4021,7 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3832
4021
|
name: actionName,
|
|
3833
4022
|
nameKind: "binding",
|
|
3834
4023
|
kind: "handler",
|
|
3835
|
-
file:
|
|
4024
|
+
file: file.display,
|
|
3836
4025
|
range,
|
|
3837
4026
|
span: spanOf(method),
|
|
3838
4027
|
exportName: actionName,
|
|
@@ -3951,12 +4140,12 @@ function branchesFor(body, range) {
|
|
|
3951
4140
|
}
|
|
3952
4141
|
];
|
|
3953
4142
|
}
|
|
3954
|
-
function bodyOfMethod(method, bodyRead = {}) {
|
|
4143
|
+
function bodyOfMethod(method, file, bodyRead = {}) {
|
|
3955
4144
|
const effects = invocationEffects(method, bodyRead.inheritedMethods);
|
|
3956
4145
|
const storage = bodyRead.storage;
|
|
3957
4146
|
const extra = [
|
|
3958
4147
|
...envReadEffects(method),
|
|
3959
|
-
...storage === void 0 ? [] : storageEffects(callsUnder2(method), storage)
|
|
4148
|
+
...storage === void 0 ? [] : storageEffects(callsUnder2(method), file, storage)
|
|
3960
4149
|
];
|
|
3961
4150
|
return {
|
|
3962
4151
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
@@ -3987,7 +4176,7 @@ function methodNotSettled(reason, range) {
|
|
|
3987
4176
|
function bodyFromLookup(found, range, subject, nothingThere, bodyRead) {
|
|
3988
4177
|
const table = {
|
|
3989
4178
|
found: (lookup) => ({
|
|
3990
|
-
...bodyOfMethod(lookup.method, bodyRead),
|
|
4179
|
+
...bodyOfMethod(lookup.method, lookup.block.file, bodyRead),
|
|
3991
4180
|
reachSeed: {
|
|
3992
4181
|
file: lookup.block.file,
|
|
3993
4182
|
node: lookup.method,
|
|
@@ -4351,11 +4540,10 @@ function collectFileConstants(filePath, root) {
|
|
|
4351
4540
|
if ((node.type === "constant" || node.type === "scope_resolution") && !isDeclaration(node, parent)) {
|
|
4352
4541
|
const written = writtenName(node);
|
|
4353
4542
|
if (written !== null) {
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
written,
|
|
4357
|
-
|
|
4358
|
-
});
|
|
4543
|
+
const key = node.type === "constant" ? `${filePath}#${node.text}` : nodeId(filePath, node);
|
|
4544
|
+
references.push(
|
|
4545
|
+
written.startsWith("::") ? { key, written: written.slice(2), nesting: [] } : { key, written, nesting }
|
|
4546
|
+
);
|
|
4359
4547
|
}
|
|
4360
4548
|
}
|
|
4361
4549
|
for (const child of children4(node)) {
|
|
@@ -4916,7 +5104,8 @@ async function scanBody(source, ctx, options) {
|
|
|
4916
5104
|
);
|
|
4917
5105
|
if (outcome.kind === "stopped") {
|
|
4918
5106
|
const stopKey = `${outcome.reason}:${callee}`;
|
|
4919
|
-
|
|
5107
|
+
const claimed = options.storage !== void 0 && storageClaims(call, source.file, options.storage);
|
|
5108
|
+
if (!seen.has(stopKey) && !claimed && (0, import_behavioral_ir4.worthRecording)(outcome.reason)) {
|
|
4920
5109
|
seen.add(stopKey);
|
|
4921
5110
|
stops.push({ callee, reason: outcome.reason });
|
|
4922
5111
|
}
|
|
@@ -4947,7 +5136,7 @@ async function scanBody(source, ctx, options) {
|
|
|
4947
5136
|
}
|
|
4948
5137
|
function libraryUnit(target, options) {
|
|
4949
5138
|
const { file, node, name, exportPath } = target;
|
|
4950
|
-
const body = bodyOfMethod(node, options);
|
|
5139
|
+
const body = bodyOfMethod(node, file, options);
|
|
4951
5140
|
const range = rangeOf(node);
|
|
4952
5141
|
return {
|
|
4953
5142
|
identity: {
|
|
@@ -5040,7 +5229,8 @@ function inheritedMethodsIn(packs) {
|
|
|
5040
5229
|
return found;
|
|
5041
5230
|
}
|
|
5042
5231
|
function alreadyDiscovered(seen, raw) {
|
|
5043
|
-
const
|
|
5232
|
+
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
5233
|
+
const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
|
|
5044
5234
|
if (seen.has(key)) {
|
|
5045
5235
|
return true;
|
|
5046
5236
|
}
|
|
@@ -5104,6 +5294,8 @@ async function extractRubyProject(options) {
|
|
|
5104
5294
|
bindEvaluator(db, { files: parsed, definitions });
|
|
5105
5295
|
});
|
|
5106
5296
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
5297
|
+
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
5298
|
+
const storage = storagePatterns.length > 0 ? { facts: db, patterns: storagePatterns, loaders: loaderPatterns } : void 0;
|
|
5107
5299
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
5108
5300
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? import_node_path4.default.relative(options.workspaceRoot, file) : file;
|
|
5109
5301
|
const seeds = [];
|
|
@@ -5119,7 +5311,7 @@ async function extractRubyProject(options) {
|
|
|
5119
5311
|
filePath: displayPath,
|
|
5120
5312
|
absoluteFile: file,
|
|
5121
5313
|
cache,
|
|
5122
|
-
...
|
|
5314
|
+
...storage === void 0 ? {} : { storage },
|
|
5123
5315
|
inheritedMethods,
|
|
5124
5316
|
displayPathOf,
|
|
5125
5317
|
facts: db,
|
|
@@ -5198,7 +5390,7 @@ async function extractRubyProject(options) {
|
|
|
5198
5390
|
() => reachedFunctions(seeds, {
|
|
5199
5391
|
files: parsed,
|
|
5200
5392
|
displayPathOf,
|
|
5201
|
-
...
|
|
5393
|
+
...storage === void 0 ? {} : { storage },
|
|
5202
5394
|
inheritedMethods
|
|
5203
5395
|
})
|
|
5204
5396
|
);
|
|
@@ -5337,8 +5529,8 @@ function displayNameOf(node) {
|
|
|
5337
5529
|
if (node.type === "class" || node.type === "module") {
|
|
5338
5530
|
return field(node, "name")?.text ?? node.text;
|
|
5339
5531
|
}
|
|
5340
|
-
const
|
|
5341
|
-
return
|
|
5532
|
+
const text2 = node.text.split("\n")[0].trim();
|
|
5533
|
+
return text2.length > 40 ? `${text2.slice(0, 40)}...` : text2;
|
|
5342
5534
|
}
|
|
5343
5535
|
function width(node) {
|
|
5344
5536
|
return node.endIndex - node.startIndex;
|
|
@@ -5365,14 +5557,14 @@ var RubyWhySession = class {
|
|
|
5365
5557
|
* The smallest expression on `line` of `file` whose text is exactly
|
|
5366
5558
|
* `text`, or null.
|
|
5367
5559
|
*/
|
|
5368
|
-
findExpression(file, line,
|
|
5560
|
+
findExpression(file, line, text2) {
|
|
5369
5561
|
const root = this.rootOf(file);
|
|
5370
5562
|
if (root === null) {
|
|
5371
5563
|
return null;
|
|
5372
5564
|
}
|
|
5373
5565
|
let found = null;
|
|
5374
5566
|
const visit = (node) => {
|
|
5375
|
-
if (node.startPosition.row + 1 === line && node.text ===
|
|
5567
|
+
if (node.startPosition.row + 1 === line && node.text === text2) {
|
|
5376
5568
|
if (found === null || width(node) <= width(found)) {
|
|
5377
5569
|
found = node;
|
|
5378
5570
|
}
|
|
@@ -5543,6 +5735,7 @@ async function rubyStubEvidence(options) {
|
|
|
5543
5735
|
inheritedStatements,
|
|
5544
5736
|
methodDefinitionsIn,
|
|
5545
5737
|
methodInAncestry,
|
|
5738
|
+
nestedStatements,
|
|
5546
5739
|
nodeId,
|
|
5547
5740
|
parseRuby,
|
|
5548
5741
|
parseRubySync,
|