@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.js
CHANGED
|
@@ -15,6 +15,45 @@ function field(node, name) {
|
|
|
15
15
|
function bodyStatements(body) {
|
|
16
16
|
return body.namedChildren.filter((child) => child !== null);
|
|
17
17
|
}
|
|
18
|
+
var BRANCH_FIELDS = {
|
|
19
|
+
if: ["consequence", "alternative"],
|
|
20
|
+
unless: ["consequence", "alternative"],
|
|
21
|
+
elsif: ["consequence", "alternative"],
|
|
22
|
+
if_modifier: ["body"],
|
|
23
|
+
unless_modifier: ["body"],
|
|
24
|
+
while: ["body"],
|
|
25
|
+
until: ["body"],
|
|
26
|
+
while_modifier: ["body"],
|
|
27
|
+
until_modifier: ["body"],
|
|
28
|
+
when: ["body"],
|
|
29
|
+
rescue: ["body"]
|
|
30
|
+
};
|
|
31
|
+
var STATEMENT_LISTS = /* @__PURE__ */ new Set(["then", "else", "do", "ensure", "begin"]);
|
|
32
|
+
var CLAUSE_TYPES = /* @__PURE__ */ new Set(["elsif", "else", "when", "rescue", "ensure"]);
|
|
33
|
+
function nestedStatements(stmt) {
|
|
34
|
+
const branchFields = BRANCH_FIELDS[stmt.type];
|
|
35
|
+
if (branchFields !== void 0) {
|
|
36
|
+
return branchFields.flatMap((name) => {
|
|
37
|
+
const branch = field(stmt, name);
|
|
38
|
+
return branch === null ? [] : statementsRunBy(branch);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (STATEMENT_LISTS.has(stmt.type)) {
|
|
42
|
+
return bodyStatements(stmt).flatMap(
|
|
43
|
+
(child) => CLAUSE_TYPES.has(child.type) ? nestedStatements(child) : [child]
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (stmt.type === "case") {
|
|
47
|
+
return bodyStatements(stmt).filter((child) => CLAUSE_TYPES.has(child.type)).flatMap(nestedStatements);
|
|
48
|
+
}
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
function statementsRunBy(branch) {
|
|
52
|
+
if (STATEMENT_LISTS.has(branch.type) || CLAUSE_TYPES.has(branch.type)) {
|
|
53
|
+
return nestedStatements(branch);
|
|
54
|
+
}
|
|
55
|
+
return [branch];
|
|
56
|
+
}
|
|
18
57
|
var OWN_BODY_TYPES = /* @__PURE__ */ new Set([
|
|
19
58
|
"method",
|
|
20
59
|
"singleton_method",
|
|
@@ -78,8 +117,8 @@ var VISIBILITY_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
78
117
|
"protected",
|
|
79
118
|
"public"
|
|
80
119
|
]);
|
|
81
|
-
function visibilityKeyword(
|
|
82
|
-
return VISIBILITY_KEYWORDS.has(
|
|
120
|
+
function visibilityKeyword(text2) {
|
|
121
|
+
return VISIBILITY_KEYWORDS.has(text2) ? text2 : null;
|
|
83
122
|
}
|
|
84
123
|
function setVisibility(visibility, name, keyword) {
|
|
85
124
|
if (keyword === "public") {
|
|
@@ -202,14 +241,31 @@ var NodeMap = class extends IdMap {
|
|
|
202
241
|
// src/constantPath.ts
|
|
203
242
|
import fs from "fs";
|
|
204
243
|
import path from "path";
|
|
205
|
-
function underscoreConstantPath(qualifiedName) {
|
|
206
|
-
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();
|
|
244
|
+
function underscoreConstantPath(qualifiedName, acronyms = []) {
|
|
245
|
+
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();
|
|
246
|
+
}
|
|
247
|
+
function lowerAcronyms(word, acronyms) {
|
|
248
|
+
if (acronyms.length === 0) {
|
|
249
|
+
return word;
|
|
250
|
+
}
|
|
251
|
+
const alternatives = acronyms.map(escapeRegExp).join("|");
|
|
252
|
+
const acronymRegex = new RegExp(
|
|
253
|
+
`(?:(?<=([A-Za-z\\d]))|\\b)(${alternatives})(?=\\b|[^a-z])`,
|
|
254
|
+
"g"
|
|
255
|
+
);
|
|
256
|
+
return word.replace(
|
|
257
|
+
acronymRegex,
|
|
258
|
+
(_match, before, acronym) => `${before === void 0 ? "" : "_"}${acronym.toLowerCase()}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function escapeRegExp(text2) {
|
|
262
|
+
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
207
263
|
}
|
|
208
264
|
var PATH_CONVENTIONS = {
|
|
209
265
|
railsUnderscore: underscoreConstantPath
|
|
210
266
|
};
|
|
211
|
-
function resolveConstantFile(root, qualifiedName, convention) {
|
|
212
|
-
const relative = `${PATH_CONVENTIONS[convention](qualifiedName)}.rb`;
|
|
267
|
+
function resolveConstantFile(root, qualifiedName, convention, acronyms = []) {
|
|
268
|
+
const relative = `${PATH_CONVENTIONS[convention](qualifiedName, acronyms)}.rb`;
|
|
213
269
|
for (const candidate of [root, ...autoloadDirectories(root)]) {
|
|
214
270
|
const file = path.join(candidate, relative);
|
|
215
271
|
if (fs.existsSync(file)) {
|
|
@@ -229,7 +285,7 @@ function autoloadDirectories(root) {
|
|
|
229
285
|
// src/scope.ts
|
|
230
286
|
function qualifyConstantRef(node, nesting) {
|
|
231
287
|
if (node.type === "scope_resolution") {
|
|
232
|
-
return node
|
|
288
|
+
return compoundName(node);
|
|
233
289
|
}
|
|
234
290
|
if (node.type === "constant") {
|
|
235
291
|
const innermost = nesting[0] ?? null;
|
|
@@ -239,13 +295,16 @@ function qualifyConstantRef(node, nesting) {
|
|
|
239
295
|
}
|
|
240
296
|
function constantRefCandidates(node, nesting) {
|
|
241
297
|
if (node.type === "scope_resolution") {
|
|
242
|
-
return [node
|
|
298
|
+
return [compoundName(node)];
|
|
243
299
|
}
|
|
244
300
|
if (node.type === "constant") {
|
|
245
301
|
return [...nesting.map((level) => `${level}::${node.text}`), node.text];
|
|
246
302
|
}
|
|
247
303
|
return [];
|
|
248
304
|
}
|
|
305
|
+
function compoundName(node) {
|
|
306
|
+
return node.text.replace(/^::/, "");
|
|
307
|
+
}
|
|
249
308
|
function shadowingClassFor(node, nesting, knownClasses) {
|
|
250
309
|
if (node.type !== "constant") {
|
|
251
310
|
return null;
|
|
@@ -280,12 +339,19 @@ function walkBody(statements, nesting, visit) {
|
|
|
280
339
|
}
|
|
281
340
|
}
|
|
282
341
|
function ownIdentity(nameNode, outerNesting) {
|
|
283
|
-
const qualifiedName =
|
|
342
|
+
const qualifiedName = definedName(nameNode, outerNesting);
|
|
284
343
|
if (qualifiedName === null) {
|
|
285
344
|
return null;
|
|
286
345
|
}
|
|
287
346
|
return { qualifiedName, bodyNesting: [qualifiedName, ...outerNesting] };
|
|
288
347
|
}
|
|
348
|
+
function definedName(nameNode, outerNesting) {
|
|
349
|
+
const innermost = outerNesting[0] ?? null;
|
|
350
|
+
if (nameNode.type === "scope_resolution" && innermost !== null && !nameNode.text.startsWith("::")) {
|
|
351
|
+
return `${innermost}::${nameNode.text}`;
|
|
352
|
+
}
|
|
353
|
+
return qualifyConstantRef(nameNode, outerNesting);
|
|
354
|
+
}
|
|
289
355
|
function visitClass(node, nesting, visit) {
|
|
290
356
|
const nameNode = field(node, "name");
|
|
291
357
|
const identity = nameNode !== null ? ownIdentity(nameNode, nesting) : null;
|
|
@@ -349,7 +415,8 @@ async function reachDefinition(qualifiedName, lookup) {
|
|
|
349
415
|
const filePath = resolveConstantFile(
|
|
350
416
|
lookup.root,
|
|
351
417
|
qualifiedName,
|
|
352
|
-
lookup.pathConvention
|
|
418
|
+
lookup.pathConvention,
|
|
419
|
+
lookup.acronyms
|
|
353
420
|
);
|
|
354
421
|
if (filePath === null) {
|
|
355
422
|
return null;
|
|
@@ -391,7 +458,10 @@ async function chainOf(self, lookup, active) {
|
|
|
391
458
|
async function superclassChain(self, lookup, active) {
|
|
392
459
|
const candidates = superclassCandidatesOf(self.blocks);
|
|
393
460
|
for (const candidate of candidates) {
|
|
394
|
-
if (lookup.ancestryRootClassNames.includes(candidate)
|
|
461
|
+
if (lookup.ancestryRootClassNames.includes(candidate)) {
|
|
462
|
+
return [{ type: "root", name: candidate }];
|
|
463
|
+
}
|
|
464
|
+
if (active.has(candidate)) {
|
|
395
465
|
return [];
|
|
396
466
|
}
|
|
397
467
|
const blocks = await definitionOf(candidate, lookup);
|
|
@@ -469,6 +539,9 @@ function superclassCandidatesOf(blocks) {
|
|
|
469
539
|
}
|
|
470
540
|
function methodInAncestry(ancestry, name) {
|
|
471
541
|
for (const entry of ancestry) {
|
|
542
|
+
if (entry.type === "root") {
|
|
543
|
+
return { type: "none" };
|
|
544
|
+
}
|
|
472
545
|
if (entry.type === "unfollowed") {
|
|
473
546
|
return {
|
|
474
547
|
type: "unsettled",
|
|
@@ -775,7 +848,7 @@ var Lowerer = class {
|
|
|
775
848
|
return [nested];
|
|
776
849
|
}
|
|
777
850
|
/**
|
|
778
|
-
* `unless ok` runs its body when `ok` is false, so it
|
|
851
|
+
* `unless ok` runs its body when `ok` is false, so it lowers to an `if` on
|
|
779
852
|
* the same test with the two arms the other way around. Without the swap
|
|
780
853
|
* a summary would say a branch runs on the test that skips it.
|
|
781
854
|
*/
|
|
@@ -791,7 +864,7 @@ var Lowerer = class {
|
|
|
791
864
|
exitKind: exitKindOf(node)
|
|
792
865
|
};
|
|
793
866
|
}
|
|
794
|
-
/** `render :gone if expired?` gates one statement on one test, so it
|
|
867
|
+
/** `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. */
|
|
795
868
|
lowerIfModifier(node) {
|
|
796
869
|
const body = field(node, "body");
|
|
797
870
|
const gated = body === null ? [] : [this.lower(body)];
|
|
@@ -1204,7 +1277,7 @@ function calledMethod(node) {
|
|
|
1204
1277
|
|
|
1205
1278
|
// src/values/evaluator.ts
|
|
1206
1279
|
import { nodeOfKey } from "@suss/resolution";
|
|
1207
|
-
import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
|
|
1280
|
+
import { Evaluator, force, literalOf as literalOf2, text } from "@suss/values";
|
|
1208
1281
|
|
|
1209
1282
|
// src/facts/resolve.ts
|
|
1210
1283
|
import {
|
|
@@ -2582,11 +2655,18 @@ function bindEvaluator(db, nodes) {
|
|
|
2582
2655
|
)
|
|
2583
2656
|
);
|
|
2584
2657
|
}
|
|
2585
|
-
function evaluatedValue(node, db) {
|
|
2586
|
-
|
|
2658
|
+
function evaluatedValue(node, db, bindings) {
|
|
2659
|
+
const evaluator = evaluatorFor(node, db);
|
|
2660
|
+
if (bindings === void 0) {
|
|
2661
|
+
return force(evaluator.evaluate(node));
|
|
2662
|
+
}
|
|
2663
|
+
const supplied = new Map(
|
|
2664
|
+
[...bindings].map(([name, value]) => [name, text(value)])
|
|
2665
|
+
);
|
|
2666
|
+
return force(evaluator.evaluate(node, { bindings: supplied }));
|
|
2587
2667
|
}
|
|
2588
|
-
function stringValueOf(node, db) {
|
|
2589
|
-
return literalOf2(evaluatedValue(node, db));
|
|
2668
|
+
function stringValueOf(node, db, bindings) {
|
|
2669
|
+
return literalOf2(evaluatedValue(node, db, bindings));
|
|
2590
2670
|
}
|
|
2591
2671
|
function methodDefinitionsIn(file, root) {
|
|
2592
2672
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -3006,8 +3086,11 @@ function receiverPrefix(receiver, method, root, pattern, options) {
|
|
|
3006
3086
|
function trimmed(path7) {
|
|
3007
3087
|
return path7 === "/" ? "" : path7.replace(/\/+$/, "");
|
|
3008
3088
|
}
|
|
3009
|
-
function namesConstant(receiver,
|
|
3010
|
-
|
|
3089
|
+
function namesConstant(receiver, constantName2) {
|
|
3090
|
+
if (receiver.type === "constant") {
|
|
3091
|
+
return receiver.text === constantName2;
|
|
3092
|
+
}
|
|
3093
|
+
return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
|
|
3011
3094
|
}
|
|
3012
3095
|
function builtBy(name, method, root, pattern) {
|
|
3013
3096
|
const builders = pattern.receiverBuilders ?? [];
|
|
@@ -3244,8 +3327,7 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3244
3327
|
skipNames.set(form.skippedBy, form.name);
|
|
3245
3328
|
}
|
|
3246
3329
|
}
|
|
3247
|
-
|
|
3248
|
-
const skips = [];
|
|
3330
|
+
let declared = [];
|
|
3249
3331
|
for (const { statement } of inheritedStatements(ancestry)) {
|
|
3250
3332
|
const called = calledName(statement);
|
|
3251
3333
|
if (called === null) {
|
|
@@ -3253,16 +3335,21 @@ function controllerFilters(pattern, ancestry) {
|
|
|
3253
3335
|
}
|
|
3254
3336
|
const form = byName.get(called);
|
|
3255
3337
|
if (form !== void 0) {
|
|
3256
|
-
|
|
3338
|
+
for (const declaration of declarationsOf(statement, form)) {
|
|
3339
|
+
declared = [
|
|
3340
|
+
...declared.filter((earlier) => !sameFilter(earlier, declaration)),
|
|
3341
|
+
declaration
|
|
3342
|
+
];
|
|
3343
|
+
}
|
|
3257
3344
|
continue;
|
|
3258
3345
|
}
|
|
3259
3346
|
const skipped = skipNames.get(called);
|
|
3260
3347
|
if (skipped !== void 0) {
|
|
3261
|
-
|
|
3348
|
+
declared = applySkips(declared, skipsOf(statement, skipped));
|
|
3262
3349
|
}
|
|
3263
3350
|
}
|
|
3264
3351
|
const resolved = [];
|
|
3265
|
-
for (const declaration of
|
|
3352
|
+
for (const declaration of declared) {
|
|
3266
3353
|
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
3267
3354
|
if (found.type !== "found") {
|
|
3268
3355
|
continue;
|
|
@@ -3375,6 +3462,9 @@ function skipsOf(statement, filterName) {
|
|
|
3375
3462
|
const actions = namesUnder(args.keyword.only);
|
|
3376
3463
|
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
3377
3464
|
}
|
|
3465
|
+
function sameFilter(one, other) {
|
|
3466
|
+
return one.filter.name === other.filter.name && one.methodName === other.methodName;
|
|
3467
|
+
}
|
|
3378
3468
|
function applySkips(declared, skips) {
|
|
3379
3469
|
const left = [];
|
|
3380
3470
|
for (const declaration of declared) {
|
|
@@ -3443,6 +3533,12 @@ function rootConstant(node) {
|
|
|
3443
3533
|
}
|
|
3444
3534
|
return rootConstant(receiver);
|
|
3445
3535
|
}
|
|
3536
|
+
function isConstant(node) {
|
|
3537
|
+
return node.type === "constant" || node.type === "scope_resolution";
|
|
3538
|
+
}
|
|
3539
|
+
function constantName(constant2) {
|
|
3540
|
+
return constant2.type === "constant" ? constant2.text : compoundName(constant2);
|
|
3541
|
+
}
|
|
3446
3542
|
var ANCESTRY_PROGRAM = deriveOnDemand2(
|
|
3447
3543
|
[...RESOLUTION_RULES2, ...RESOLUTION_QUESTIONS2],
|
|
3448
3544
|
ANSWER_RELATIONS2
|
|
@@ -3452,13 +3548,22 @@ function reachesBase(facts, classKey, bases) {
|
|
|
3452
3548
|
evaluate2(facts, ANCESTRY_PROGRAM.rules);
|
|
3453
3549
|
return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
|
|
3454
3550
|
}
|
|
3455
|
-
function classBehind(facts, constant2) {
|
|
3456
|
-
const
|
|
3551
|
+
function classBehind(facts, file, constant2) {
|
|
3552
|
+
const key = constant2.type === "constant" ? `${file}#${constant2.text}` : nodeId(file, constant2);
|
|
3457
3553
|
const bound = new Set(
|
|
3458
|
-
facts.
|
|
3554
|
+
facts.lookup("binds", 0, key).map((row) => String(row[1]))
|
|
3459
3555
|
);
|
|
3460
3556
|
return bound.size === 1 ? [...bound][0] : void 0;
|
|
3461
3557
|
}
|
|
3558
|
+
function modelPattern(constant2, file, options) {
|
|
3559
|
+
const classKey = classBehind(options.facts, file, constant2);
|
|
3560
|
+
if (classKey === void 0) {
|
|
3561
|
+
return void 0;
|
|
3562
|
+
}
|
|
3563
|
+
return options.patterns.find(
|
|
3564
|
+
(candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3462
3567
|
function selectorOf(node) {
|
|
3463
3568
|
const picked = [];
|
|
3464
3569
|
const walk = (current) => {
|
|
@@ -3480,7 +3585,87 @@ function selectorOf(node) {
|
|
|
3480
3585
|
walk(node);
|
|
3481
3586
|
return [...new Set(picked)];
|
|
3482
3587
|
}
|
|
3483
|
-
function
|
|
3588
|
+
function storageEffect(call, model, pattern, kind, selector) {
|
|
3589
|
+
const operation = methodOf(call);
|
|
3590
|
+
return {
|
|
3591
|
+
type: "interaction",
|
|
3592
|
+
binding: storageBinding({
|
|
3593
|
+
recognition: "ruby-storage",
|
|
3594
|
+
storageSystem: pattern.storageSystem,
|
|
3595
|
+
scope: "default",
|
|
3596
|
+
container: constantName(model)
|
|
3597
|
+
}),
|
|
3598
|
+
callee: call.text,
|
|
3599
|
+
interaction: {
|
|
3600
|
+
class: "storage-access",
|
|
3601
|
+
kind,
|
|
3602
|
+
fields: [],
|
|
3603
|
+
operation,
|
|
3604
|
+
...selector.length > 0 ? { selector } : {}
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
}
|
|
3608
|
+
function modelCallEffects(call, file, options) {
|
|
3609
|
+
const constant2 = rootConstant(call);
|
|
3610
|
+
if (constant2 === null) {
|
|
3611
|
+
return [];
|
|
3612
|
+
}
|
|
3613
|
+
const pattern = modelPattern(constant2, file, options);
|
|
3614
|
+
if (pattern === void 0) {
|
|
3615
|
+
return [];
|
|
3616
|
+
}
|
|
3617
|
+
const kind = pattern.writes.includes(methodOf(call)) ? "write" : "read";
|
|
3618
|
+
return [storageEffect(call, constant2, pattern, kind, selectorOf(call))];
|
|
3619
|
+
}
|
|
3620
|
+
function isLoader(node, loader) {
|
|
3621
|
+
if (node === null) {
|
|
3622
|
+
return false;
|
|
3623
|
+
}
|
|
3624
|
+
if (node.type === "identifier") {
|
|
3625
|
+
return node.text === loader.loader;
|
|
3626
|
+
}
|
|
3627
|
+
return node.type === "call" && receiverOf(node) === null && methodOf(node) === loader.loader;
|
|
3628
|
+
}
|
|
3629
|
+
function loaderPick(call, loader) {
|
|
3630
|
+
const method = methodOf(call);
|
|
3631
|
+
const receiver = receiverOf(call);
|
|
3632
|
+
if (receiver === null) {
|
|
3633
|
+
return loader.shortcuts.includes(method) ? call : null;
|
|
3634
|
+
}
|
|
3635
|
+
if (!loader.reads.includes(method) || receiver.type !== "call") {
|
|
3636
|
+
return null;
|
|
3637
|
+
}
|
|
3638
|
+
const picks = methodOf(receiver) === loader.pick && isLoader(receiverOf(receiver), loader);
|
|
3639
|
+
return picks ? receiver : null;
|
|
3640
|
+
}
|
|
3641
|
+
function loaderCallEffects(call, file, options) {
|
|
3642
|
+
const effects = [];
|
|
3643
|
+
for (const loader of options.loaders ?? []) {
|
|
3644
|
+
const pick = loaderPick(call, loader);
|
|
3645
|
+
if (pick === null) {
|
|
3646
|
+
continue;
|
|
3647
|
+
}
|
|
3648
|
+
const args = field(pick, "arguments");
|
|
3649
|
+
for (const argument of args === null ? [] : children3(args)) {
|
|
3650
|
+
if (!isConstant(argument)) {
|
|
3651
|
+
continue;
|
|
3652
|
+
}
|
|
3653
|
+
const pattern = modelPattern(argument, file, options);
|
|
3654
|
+
if (pattern !== void 0) {
|
|
3655
|
+
effects.push(storageEffect(call, argument, pattern, "read", []));
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
return effects;
|
|
3660
|
+
}
|
|
3661
|
+
function effectsOfCall(call, file, options) {
|
|
3662
|
+
const onModel = modelCallEffects(call, file, options);
|
|
3663
|
+
return onModel.length > 0 ? onModel : loaderCallEffects(call, file, options);
|
|
3664
|
+
}
|
|
3665
|
+
function storageClaims(call, file, options) {
|
|
3666
|
+
return options.patterns.length > 0 && effectsOfCall(call, file, options).length > 0;
|
|
3667
|
+
}
|
|
3668
|
+
function storageEffects(calls, file, options) {
|
|
3484
3669
|
if (options.patterns.length === 0) {
|
|
3485
3670
|
return [];
|
|
3486
3671
|
}
|
|
@@ -3496,39 +3681,7 @@ function storageEffects(calls, options) {
|
|
|
3496
3681
|
if (partOfOne.has(call.id)) {
|
|
3497
3682
|
continue;
|
|
3498
3683
|
}
|
|
3499
|
-
|
|
3500
|
-
if (constant2 === null) {
|
|
3501
|
-
continue;
|
|
3502
|
-
}
|
|
3503
|
-
const classKey = classBehind(options.facts, constant2);
|
|
3504
|
-
if (classKey === void 0) {
|
|
3505
|
-
continue;
|
|
3506
|
-
}
|
|
3507
|
-
const pattern = options.patterns.find(
|
|
3508
|
-
(candidate) => reachesBase(options.facts, classKey, candidate.baseClasses)
|
|
3509
|
-
);
|
|
3510
|
-
if (pattern === void 0) {
|
|
3511
|
-
continue;
|
|
3512
|
-
}
|
|
3513
|
-
const operation = methodOf(call);
|
|
3514
|
-
const picked = selectorOf(call);
|
|
3515
|
-
effects.push({
|
|
3516
|
-
type: "interaction",
|
|
3517
|
-
binding: storageBinding({
|
|
3518
|
-
recognition: "ruby-storage",
|
|
3519
|
-
storageSystem: pattern.storageSystem,
|
|
3520
|
-
scope: pattern.baseClasses[0] ?? "",
|
|
3521
|
-
container: constant2.text
|
|
3522
|
-
}),
|
|
3523
|
-
callee: call.text,
|
|
3524
|
-
interaction: {
|
|
3525
|
-
class: "storage-access",
|
|
3526
|
-
kind: pattern.writes.includes(operation) ? "write" : "read",
|
|
3527
|
-
fields: [],
|
|
3528
|
-
operation,
|
|
3529
|
-
...picked.length > 0 ? { selector: picked } : {}
|
|
3530
|
-
}
|
|
3531
|
-
});
|
|
3684
|
+
effects.push(...effectsOfCall(call, file, options));
|
|
3532
3685
|
}
|
|
3533
3686
|
return effects;
|
|
3534
3687
|
}
|
|
@@ -3570,7 +3723,7 @@ function typeShapeFromNode(node, ctx) {
|
|
|
3570
3723
|
};
|
|
3571
3724
|
}
|
|
3572
3725
|
if (node.type === "scope_resolution") {
|
|
3573
|
-
const qualified = node
|
|
3726
|
+
const qualified = compoundName(node);
|
|
3574
3727
|
const scalar = ctx.scalars[scalarLookupName(qualified, ctx.scalarNamePrefixes)];
|
|
3575
3728
|
if (scalar !== void 0) {
|
|
3576
3729
|
return scalar;
|
|
@@ -3607,6 +3760,7 @@ function fieldReadContext(pattern, cache, fileBlocks, bodyRead) {
|
|
|
3607
3760
|
lookup: {
|
|
3608
3761
|
root: pattern.root,
|
|
3609
3762
|
pathConvention: pattern.pathConvention,
|
|
3763
|
+
acronyms: pattern.acronyms ?? [],
|
|
3610
3764
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3611
3765
|
parsedFile: (absPath) => cache.get(absPath),
|
|
3612
3766
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
@@ -3721,12 +3875,16 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3721
3875
|
const lookup = {
|
|
3722
3876
|
root: pattern.root,
|
|
3723
3877
|
pathConvention: pattern.pathConvention,
|
|
3878
|
+
acronyms: pattern.acronyms ?? [],
|
|
3724
3879
|
ancestryRootClassNames: pattern.ancestryRootClassNames,
|
|
3725
3880
|
parsedFile: (absPath) => options.cache.get(absPath),
|
|
3726
3881
|
localDefinition: (name) => sameFileBlocks(name, fileBlocks)
|
|
3727
3882
|
};
|
|
3728
3883
|
const ancestry = await ancestryOf(info.qualifiedName, ownBlocks, lookup);
|
|
3729
|
-
if (!reachesConfiguredBase(ancestry, info.qualifiedName,
|
|
3884
|
+
if (!reachesConfiguredBase(ancestry, info.qualifiedName, [
|
|
3885
|
+
...pattern.baseClassNames,
|
|
3886
|
+
...pattern.ancestryRootClassNames
|
|
3887
|
+
])) {
|
|
3730
3888
|
return [];
|
|
3731
3889
|
}
|
|
3732
3890
|
const filters = controllerFilters(pattern, ancestry);
|
|
@@ -3737,7 +3895,7 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3737
3895
|
filter,
|
|
3738
3896
|
pattern,
|
|
3739
3897
|
displayPath,
|
|
3740
|
-
bodyOfMethod(filter.method, options)
|
|
3898
|
+
bodyOfMethod(filter.method, filter.file, options)
|
|
3741
3899
|
);
|
|
3742
3900
|
units.push(raw);
|
|
3743
3901
|
options.onReachSeed?.(raw, {
|
|
@@ -3746,47 +3904,77 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3746
3904
|
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3747
3905
|
});
|
|
3748
3906
|
}
|
|
3749
|
-
|
|
3750
|
-
|
|
3907
|
+
const emitAction = (actionName, method, block) => {
|
|
3908
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
3909
|
+
(filter) => filterReference(
|
|
3910
|
+
filter,
|
|
3911
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
3912
|
+
)
|
|
3913
|
+
);
|
|
3914
|
+
const raw = buildControllerActionUnit(
|
|
3915
|
+
pack,
|
|
3916
|
+
pattern,
|
|
3917
|
+
info.qualifiedName,
|
|
3918
|
+
actionName,
|
|
3919
|
+
method,
|
|
3920
|
+
{
|
|
3921
|
+
display: block.file === (options.absoluteFile ?? options.filePath) ? options.filePath : options.displayPathOf?.(block.file) ?? block.file,
|
|
3922
|
+
absolute: block.file
|
|
3923
|
+
},
|
|
3924
|
+
options,
|
|
3925
|
+
around
|
|
3926
|
+
);
|
|
3927
|
+
units.push(raw);
|
|
3928
|
+
options.onReachSeed?.(raw, {
|
|
3929
|
+
file: block.file,
|
|
3930
|
+
node: method,
|
|
3931
|
+
enclosingQualifiedName: block.info.qualifiedName
|
|
3932
|
+
});
|
|
3933
|
+
};
|
|
3934
|
+
const own = /* @__PURE__ */ new Set();
|
|
3935
|
+
for (const [actionName, method, block] of publicInstanceMethods(ownBlocks)) {
|
|
3936
|
+
own.add(actionName);
|
|
3937
|
+
emitAction(actionName, method, block);
|
|
3938
|
+
}
|
|
3939
|
+
const seen = new Set(own);
|
|
3940
|
+
for (const entry of ancestry) {
|
|
3941
|
+
if (entry.type !== "bodies" || entry.name === info.qualifiedName) {
|
|
3751
3942
|
continue;
|
|
3752
3943
|
}
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3755
|
-
block.info.bodyNode
|
|
3944
|
+
for (const [actionName, method, block] of publicInstanceMethods(
|
|
3945
|
+
entry.blocks
|
|
3756
3946
|
)) {
|
|
3757
|
-
if (
|
|
3947
|
+
if (seen.has(actionName)) {
|
|
3758
3948
|
continue;
|
|
3759
3949
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
);
|
|
3766
|
-
const raw = buildControllerActionUnit(
|
|
3767
|
-
pack,
|
|
3768
|
-
pattern,
|
|
3769
|
-
info.qualifiedName,
|
|
3770
|
-
actionName,
|
|
3771
|
-
method,
|
|
3772
|
-
options.filePath,
|
|
3773
|
-
options,
|
|
3774
|
-
around
|
|
3775
|
-
);
|
|
3776
|
-
units.push(raw);
|
|
3777
|
-
options.onReachSeed?.(raw, {
|
|
3778
|
-
file: block.file,
|
|
3779
|
-
node: method,
|
|
3780
|
-
enclosingQualifiedName: info.qualifiedName
|
|
3781
|
-
});
|
|
3950
|
+
seen.add(actionName);
|
|
3951
|
+
if (pattern.routeFor(info.qualifiedName, actionName) === null) {
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
emitAction(actionName, method, block);
|
|
3782
3955
|
}
|
|
3783
3956
|
}
|
|
3784
3957
|
return units;
|
|
3785
3958
|
}
|
|
3786
|
-
function
|
|
3959
|
+
function publicInstanceMethods(blocks) {
|
|
3960
|
+
const found = [];
|
|
3961
|
+
for (const block of blocks) {
|
|
3962
|
+
if (block.info.bodyNode === null) {
|
|
3963
|
+
continue;
|
|
3964
|
+
}
|
|
3965
|
+
const visibility = instanceMethodVisibility(block.info.bodyNode);
|
|
3966
|
+
for (const [name, method] of instanceMethodsByName(block.info.bodyNode)) {
|
|
3967
|
+
if ((visibility.get(name) ?? "public") === "public") {
|
|
3968
|
+
found.push([name, method, block]);
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
return found;
|
|
3973
|
+
}
|
|
3974
|
+
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, file, bodyRead, wrappers = []) {
|
|
3787
3975
|
const range = rangeOf(method);
|
|
3788
3976
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
3789
|
-
const body = bodyOfMethod(method, bodyRead);
|
|
3977
|
+
const body = bodyOfMethod(method, file.absolute, bodyRead);
|
|
3790
3978
|
const perResponse = responseBranches(
|
|
3791
3979
|
method,
|
|
3792
3980
|
pattern,
|
|
@@ -3798,7 +3986,7 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3798
3986
|
name: actionName,
|
|
3799
3987
|
nameKind: "binding",
|
|
3800
3988
|
kind: "handler",
|
|
3801
|
-
file:
|
|
3989
|
+
file: file.display,
|
|
3802
3990
|
range,
|
|
3803
3991
|
span: spanOf(method),
|
|
3804
3992
|
exportName: actionName,
|
|
@@ -3917,12 +4105,12 @@ function branchesFor(body, range) {
|
|
|
3917
4105
|
}
|
|
3918
4106
|
];
|
|
3919
4107
|
}
|
|
3920
|
-
function bodyOfMethod(method, bodyRead = {}) {
|
|
4108
|
+
function bodyOfMethod(method, file, bodyRead = {}) {
|
|
3921
4109
|
const effects = invocationEffects(method, bodyRead.inheritedMethods);
|
|
3922
4110
|
const storage = bodyRead.storage;
|
|
3923
4111
|
const extra = [
|
|
3924
4112
|
...envReadEffects(method),
|
|
3925
|
-
...storage === void 0 ? [] : storageEffects(callsUnder2(method), storage)
|
|
4113
|
+
...storage === void 0 ? [] : storageEffects(callsUnder2(method), file, storage)
|
|
3926
4114
|
];
|
|
3927
4115
|
return {
|
|
3928
4116
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
@@ -3953,7 +4141,7 @@ function methodNotSettled(reason, range) {
|
|
|
3953
4141
|
function bodyFromLookup(found, range, subject, nothingThere, bodyRead) {
|
|
3954
4142
|
const table = {
|
|
3955
4143
|
found: (lookup) => ({
|
|
3956
|
-
...bodyOfMethod(lookup.method, bodyRead),
|
|
4144
|
+
...bodyOfMethod(lookup.method, lookup.block.file, bodyRead),
|
|
3957
4145
|
reachSeed: {
|
|
3958
4146
|
file: lookup.block.file,
|
|
3959
4147
|
node: lookup.method,
|
|
@@ -4317,11 +4505,10 @@ function collectFileConstants(filePath, root) {
|
|
|
4317
4505
|
if ((node.type === "constant" || node.type === "scope_resolution") && !isDeclaration(node, parent)) {
|
|
4318
4506
|
const written = writtenName(node);
|
|
4319
4507
|
if (written !== null) {
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
written,
|
|
4323
|
-
|
|
4324
|
-
});
|
|
4508
|
+
const key = node.type === "constant" ? `${filePath}#${node.text}` : nodeId(filePath, node);
|
|
4509
|
+
references.push(
|
|
4510
|
+
written.startsWith("::") ? { key, written: written.slice(2), nesting: [] } : { key, written, nesting }
|
|
4511
|
+
);
|
|
4325
4512
|
}
|
|
4326
4513
|
}
|
|
4327
4514
|
for (const child of children4(node)) {
|
|
@@ -4913,7 +5100,8 @@ async function scanBody(source, ctx, options) {
|
|
|
4913
5100
|
);
|
|
4914
5101
|
if (outcome.kind === "stopped") {
|
|
4915
5102
|
const stopKey = `${outcome.reason}:${callee}`;
|
|
4916
|
-
|
|
5103
|
+
const claimed = options.storage !== void 0 && storageClaims(call, source.file, options.storage);
|
|
5104
|
+
if (!seen.has(stopKey) && !claimed && worthRecording(outcome.reason)) {
|
|
4917
5105
|
seen.add(stopKey);
|
|
4918
5106
|
stops.push({ callee, reason: outcome.reason });
|
|
4919
5107
|
}
|
|
@@ -4944,7 +5132,7 @@ async function scanBody(source, ctx, options) {
|
|
|
4944
5132
|
}
|
|
4945
5133
|
function libraryUnit(target, options) {
|
|
4946
5134
|
const { file, node, name, exportPath } = target;
|
|
4947
|
-
const body = bodyOfMethod(node, options);
|
|
5135
|
+
const body = bodyOfMethod(node, file, options);
|
|
4948
5136
|
const range = rangeOf(node);
|
|
4949
5137
|
return {
|
|
4950
5138
|
identity: {
|
|
@@ -5037,7 +5225,8 @@ function inheritedMethodsIn(packs) {
|
|
|
5037
5225
|
return found;
|
|
5038
5226
|
}
|
|
5039
5227
|
function alreadyDiscovered(seen, raw) {
|
|
5040
|
-
const
|
|
5228
|
+
const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
|
|
5229
|
+
const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
|
|
5041
5230
|
if (seen.has(key)) {
|
|
5042
5231
|
return true;
|
|
5043
5232
|
}
|
|
@@ -5101,6 +5290,8 @@ async function extractRubyProject(options) {
|
|
|
5101
5290
|
bindEvaluator(db, { files: parsed, definitions });
|
|
5102
5291
|
});
|
|
5103
5292
|
const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
|
|
5293
|
+
const loaderPatterns = options.packs.flatMap((pack) => pack.loaders ?? []);
|
|
5294
|
+
const storage = storagePatterns.length > 0 ? { facts: db, patterns: storagePatterns, loaders: loaderPatterns } : void 0;
|
|
5104
5295
|
const inheritedMethods = inheritedMethodsIn(options.packs);
|
|
5105
5296
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file) : file;
|
|
5106
5297
|
const seeds = [];
|
|
@@ -5116,7 +5307,7 @@ async function extractRubyProject(options) {
|
|
|
5116
5307
|
filePath: displayPath,
|
|
5117
5308
|
absoluteFile: file,
|
|
5118
5309
|
cache,
|
|
5119
|
-
...
|
|
5310
|
+
...storage === void 0 ? {} : { storage },
|
|
5120
5311
|
inheritedMethods,
|
|
5121
5312
|
displayPathOf,
|
|
5122
5313
|
facts: db,
|
|
@@ -5195,7 +5386,7 @@ async function extractRubyProject(options) {
|
|
|
5195
5386
|
() => reachedFunctions(seeds, {
|
|
5196
5387
|
files: parsed,
|
|
5197
5388
|
displayPathOf,
|
|
5198
|
-
...
|
|
5389
|
+
...storage === void 0 ? {} : { storage },
|
|
5199
5390
|
inheritedMethods
|
|
5200
5391
|
})
|
|
5201
5392
|
);
|
|
@@ -5334,8 +5525,8 @@ function displayNameOf(node) {
|
|
|
5334
5525
|
if (node.type === "class" || node.type === "module") {
|
|
5335
5526
|
return field(node, "name")?.text ?? node.text;
|
|
5336
5527
|
}
|
|
5337
|
-
const
|
|
5338
|
-
return
|
|
5528
|
+
const text2 = node.text.split("\n")[0].trim();
|
|
5529
|
+
return text2.length > 40 ? `${text2.slice(0, 40)}...` : text2;
|
|
5339
5530
|
}
|
|
5340
5531
|
function width(node) {
|
|
5341
5532
|
return node.endIndex - node.startIndex;
|
|
@@ -5362,14 +5553,14 @@ var RubyWhySession = class {
|
|
|
5362
5553
|
* The smallest expression on `line` of `file` whose text is exactly
|
|
5363
5554
|
* `text`, or null.
|
|
5364
5555
|
*/
|
|
5365
|
-
findExpression(file, line,
|
|
5556
|
+
findExpression(file, line, text2) {
|
|
5366
5557
|
const root = this.rootOf(file);
|
|
5367
5558
|
if (root === null) {
|
|
5368
5559
|
return null;
|
|
5369
5560
|
}
|
|
5370
5561
|
let found = null;
|
|
5371
5562
|
const visit = (node) => {
|
|
5372
|
-
if (node.startPosition.row + 1 === line && node.text ===
|
|
5563
|
+
if (node.startPosition.row + 1 === line && node.text === text2) {
|
|
5373
5564
|
if (found === null || width(node) <= width(found)) {
|
|
5374
5565
|
found = node;
|
|
5375
5566
|
}
|
|
@@ -5539,6 +5730,7 @@ export {
|
|
|
5539
5730
|
inheritedStatements,
|
|
5540
5731
|
methodDefinitionsIn,
|
|
5541
5732
|
methodInAncestry,
|
|
5733
|
+
nestedStatements,
|
|
5542
5734
|
nodeId,
|
|
5543
5735
|
parseRuby,
|
|
5544
5736
|
parseRubySync,
|