@suss/adapter-ruby 0.22.4 → 0.23.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 +16 -0
- package/dist/index.cjs +476 -213
- package/dist/index.d.cts +41 -4
- package/dist/index.d.ts +41 -4
- package/dist/index.js +478 -214
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -209,9 +209,21 @@ var PATH_CONVENTIONS = {
|
|
|
209
209
|
railsUnderscore: underscoreConstantPath
|
|
210
210
|
};
|
|
211
211
|
function resolveConstantFile(root, qualifiedName, convention) {
|
|
212
|
-
const relative = PATH_CONVENTIONS[convention](qualifiedName)
|
|
213
|
-
const
|
|
214
|
-
|
|
212
|
+
const relative = `${PATH_CONVENTIONS[convention](qualifiedName)}.rb`;
|
|
213
|
+
for (const candidate of [root, ...autoloadDirectories(root)]) {
|
|
214
|
+
const file = path.join(candidate, relative);
|
|
215
|
+
if (fs.existsSync(file)) {
|
|
216
|
+
return file;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function autoloadDirectories(root) {
|
|
222
|
+
try {
|
|
223
|
+
return fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => path.join(root, entry.name)).sort();
|
|
224
|
+
} catch {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
215
227
|
}
|
|
216
228
|
|
|
217
229
|
// src/scope.ts
|
|
@@ -624,8 +636,15 @@ function isDefaultedAt(node) {
|
|
|
624
636
|
return false;
|
|
625
637
|
}
|
|
626
638
|
|
|
627
|
-
// src/
|
|
628
|
-
import {
|
|
639
|
+
// src/responseStatus.ts
|
|
640
|
+
import {
|
|
641
|
+
absentReading,
|
|
642
|
+
enumerateOrDegrade,
|
|
643
|
+
guardsHoldOn,
|
|
644
|
+
unreadableReading,
|
|
645
|
+
writtenReading
|
|
646
|
+
} from "@suss/extractor";
|
|
647
|
+
import { constantOf, literalOf as literalOf2 } from "@suss/values";
|
|
629
648
|
|
|
630
649
|
// src/paths/bareCalls.ts
|
|
631
650
|
var PARAMETER_LIST_TYPES = [
|
|
@@ -1001,181 +1020,6 @@ function lowerRubyBody(body, terminals, responseCalls = []) {
|
|
|
1001
1020
|
};
|
|
1002
1021
|
}
|
|
1003
1022
|
|
|
1004
|
-
// src/paths/effects.ts
|
|
1005
|
-
var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
|
|
1006
|
-
var BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
1007
|
-
"body_statement",
|
|
1008
|
-
"then",
|
|
1009
|
-
"else",
|
|
1010
|
-
"do_block",
|
|
1011
|
-
"block"
|
|
1012
|
-
]);
|
|
1013
|
-
var LITERAL_ARGS = {
|
|
1014
|
-
string: (node) => ({
|
|
1015
|
-
kind: "string",
|
|
1016
|
-
value: node.text.replace(/^["']|["']$/g, "")
|
|
1017
|
-
}),
|
|
1018
|
-
integer: (node) => {
|
|
1019
|
-
const value = Number.parseInt(node.text, 10);
|
|
1020
|
-
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
1021
|
-
},
|
|
1022
|
-
float: (node) => {
|
|
1023
|
-
const value = Number.parseFloat(node.text);
|
|
1024
|
-
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
1025
|
-
},
|
|
1026
|
-
true: () => ({ kind: "boolean", value: true }),
|
|
1027
|
-
false: () => ({ kind: "boolean", value: false }),
|
|
1028
|
-
simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
|
|
1029
|
-
};
|
|
1030
|
-
function children(node) {
|
|
1031
|
-
return node.namedChildren.filter((child) => child !== null);
|
|
1032
|
-
}
|
|
1033
|
-
function withoutChainLinks(calls) {
|
|
1034
|
-
const isLink = /* @__PURE__ */ new Set();
|
|
1035
|
-
for (const call of calls) {
|
|
1036
|
-
const receiver = field(call, "receiver");
|
|
1037
|
-
if (receiver !== null && receiver.type === "call") {
|
|
1038
|
-
isLink.add(receiver.id);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
return calls.filter((call) => !isLink.has(call.id));
|
|
1042
|
-
}
|
|
1043
|
-
function calleeMethodName(call) {
|
|
1044
|
-
if (call.type === "identifier") {
|
|
1045
|
-
return call.text;
|
|
1046
|
-
}
|
|
1047
|
-
const method = field(call, "method");
|
|
1048
|
-
if (method !== null) {
|
|
1049
|
-
return method.text;
|
|
1050
|
-
}
|
|
1051
|
-
return field(call, "receiver") !== null ? "call" : children(call)[0]?.text;
|
|
1052
|
-
}
|
|
1053
|
-
function isPropertyRead(node) {
|
|
1054
|
-
if (field(node, "receiver") === null || field(node, "arguments") !== null) {
|
|
1055
|
-
return false;
|
|
1056
|
-
}
|
|
1057
|
-
return calleeMethodName(node) !== "call";
|
|
1058
|
-
}
|
|
1059
|
-
function isRaise2(node) {
|
|
1060
|
-
const method = field(node, "method") ?? children(node)[0] ?? null;
|
|
1061
|
-
return field(node, "receiver") === null && method !== null && RAISE_NAMES2.has(method.text);
|
|
1062
|
-
}
|
|
1063
|
-
var NO_INHERITED_METHODS = /* @__PURE__ */ new Set();
|
|
1064
|
-
function isInherited(node, inherited) {
|
|
1065
|
-
const method = field(node, "method");
|
|
1066
|
-
return field(node, "receiver") === null && method !== null && inherited.has(method.text);
|
|
1067
|
-
}
|
|
1068
|
-
function isCall(node, locals, inherited) {
|
|
1069
|
-
if (node.type === "call") {
|
|
1070
|
-
return !isPropertyRead(node) && !isRaise2(node) && !isInherited(node, inherited);
|
|
1071
|
-
}
|
|
1072
|
-
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
1073
|
-
}
|
|
1074
|
-
function collectCalls(node, locals, inherited, found) {
|
|
1075
|
-
for (const child of children(node)) {
|
|
1076
|
-
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
if (isCall(child, locals, inherited)) {
|
|
1080
|
-
found.push(child);
|
|
1081
|
-
}
|
|
1082
|
-
collectCalls(child, locals, inherited, found);
|
|
1083
|
-
}
|
|
1084
|
-
return found;
|
|
1085
|
-
}
|
|
1086
|
-
function bodyCalls(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
1087
|
-
const body = field(definitionNode, "body");
|
|
1088
|
-
if (body === null) {
|
|
1089
|
-
return [];
|
|
1090
|
-
}
|
|
1091
|
-
return collectCalls(body, localNamesIn(definitionNode), inherited, []);
|
|
1092
|
-
}
|
|
1093
|
-
function calleeText(call) {
|
|
1094
|
-
const receiver = field(call, "receiver");
|
|
1095
|
-
const method = calleeMethodName(call);
|
|
1096
|
-
if (method === void 0) {
|
|
1097
|
-
return call.text;
|
|
1098
|
-
}
|
|
1099
|
-
return receiver === null ? method : `${receiver.text}.${method}`;
|
|
1100
|
-
}
|
|
1101
|
-
function argOf(node) {
|
|
1102
|
-
const literal = LITERAL_ARGS[node.type]?.(node);
|
|
1103
|
-
if (literal !== null && literal !== void 0) {
|
|
1104
|
-
return literal;
|
|
1105
|
-
}
|
|
1106
|
-
if (node.type === "call" && !isPropertyRead(node)) {
|
|
1107
|
-
return { kind: "call", callee: calleeText(node), args: argsOf(node) };
|
|
1108
|
-
}
|
|
1109
|
-
return { kind: "identifier", name: node.text };
|
|
1110
|
-
}
|
|
1111
|
-
function argsOf(call) {
|
|
1112
|
-
const args = field(call, "arguments");
|
|
1113
|
-
if (args === null) {
|
|
1114
|
-
return [];
|
|
1115
|
-
}
|
|
1116
|
-
return children(args).map(
|
|
1117
|
-
(child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1120
|
-
function enclosingStatement(call, body) {
|
|
1121
|
-
let current = call;
|
|
1122
|
-
while (current.parent !== null) {
|
|
1123
|
-
if (BLOCK_TYPES.has(current.parent.type) || current.parent.id === body.id) {
|
|
1124
|
-
return current;
|
|
1125
|
-
}
|
|
1126
|
-
current = current.parent;
|
|
1127
|
-
}
|
|
1128
|
-
return current;
|
|
1129
|
-
}
|
|
1130
|
-
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
1131
|
-
const body = field(definitionNode, "body");
|
|
1132
|
-
if (body === null) {
|
|
1133
|
-
return [];
|
|
1134
|
-
}
|
|
1135
|
-
const calls = withoutChainLinks(bodyCalls(definitionNode, inherited));
|
|
1136
|
-
if (calls.length === 0) {
|
|
1137
|
-
return [];
|
|
1138
|
-
}
|
|
1139
|
-
const statementOf2 = /* @__PURE__ */ new Map();
|
|
1140
|
-
const statements = [];
|
|
1141
|
-
for (const call of calls) {
|
|
1142
|
-
const statement = enclosingStatement(call, body);
|
|
1143
|
-
statementOf2.set(call.id, statement);
|
|
1144
|
-
statements.push(statement);
|
|
1145
|
-
}
|
|
1146
|
-
const lowered = lowerRubyBody(body, statements);
|
|
1147
|
-
const enumerated = enumerateOrDegrade(
|
|
1148
|
-
{
|
|
1149
|
-
statements: lowered.statements,
|
|
1150
|
-
terminalsByStmt: lowered.terminalsByStmt
|
|
1151
|
-
},
|
|
1152
|
-
statements
|
|
1153
|
-
);
|
|
1154
|
-
return calls.map((call) => {
|
|
1155
|
-
const statement = statementOf2.get(call.id);
|
|
1156
|
-
const conditions = sharedGatingConditions(
|
|
1157
|
-
statement === void 0 ? void 0 : enumerated.byTerminal.get(statement)
|
|
1158
|
-
);
|
|
1159
|
-
return {
|
|
1160
|
-
type: "invocation",
|
|
1161
|
-
callee: calleeText(call),
|
|
1162
|
-
args: argsOf(call),
|
|
1163
|
-
async: false,
|
|
1164
|
-
...conditions.length > 0 ? { preconditions: conditions } : {}
|
|
1165
|
-
};
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// src/responseStatus.ts
|
|
1170
|
-
import {
|
|
1171
|
-
absentReading,
|
|
1172
|
-
enumerateOrDegrade as enumerateOrDegrade2,
|
|
1173
|
-
guardsHoldOn,
|
|
1174
|
-
unreadableReading,
|
|
1175
|
-
writtenReading
|
|
1176
|
-
} from "@suss/extractor";
|
|
1177
|
-
import { constantOf, literalOf as literalOf2 } from "@suss/values";
|
|
1178
|
-
|
|
1179
1023
|
// src/values/evaluator.ts
|
|
1180
1024
|
import { nodeOfKey } from "@suss/resolution";
|
|
1181
1025
|
import { Evaluator, force, literalOf } from "@suss/values";
|
|
@@ -1244,7 +1088,7 @@ function paramNameOf(param) {
|
|
|
1244
1088
|
function declaredParamNames(method) {
|
|
1245
1089
|
const params = field(method, "parameters");
|
|
1246
1090
|
const declared = /* @__PURE__ */ new Set();
|
|
1247
|
-
for (const param of params === null ? [] :
|
|
1091
|
+
for (const param of params === null ? [] : children(params)) {
|
|
1248
1092
|
const name = paramNameOf(param);
|
|
1249
1093
|
if (name !== null) {
|
|
1250
1094
|
declared.add(name.text);
|
|
@@ -1258,7 +1102,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1258
1102
|
}
|
|
1259
1103
|
const parent = node.parent;
|
|
1260
1104
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1261
|
-
return
|
|
1105
|
+
return isPropertyRead(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1262
1106
|
}
|
|
1263
1107
|
if (enclosing !== null && declaredParamNames(enclosing).has(node.text)) {
|
|
1264
1108
|
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
@@ -1286,7 +1130,7 @@ var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1286
1130
|
"parenthesized_statements"
|
|
1287
1131
|
]);
|
|
1288
1132
|
var ARRAY_TYPES = /* @__PURE__ */ new Set(["array", "string_array", "symbol_array"]);
|
|
1289
|
-
function
|
|
1133
|
+
function children(node) {
|
|
1290
1134
|
return node.namedChildren.filter((child) => child !== null);
|
|
1291
1135
|
}
|
|
1292
1136
|
function add(emitter, relation, ...tuple) {
|
|
@@ -1314,8 +1158,8 @@ function pairKeyText(key) {
|
|
|
1314
1158
|
}
|
|
1315
1159
|
return null;
|
|
1316
1160
|
}
|
|
1317
|
-
function
|
|
1318
|
-
return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null &&
|
|
1161
|
+
function isPropertyRead(node) {
|
|
1162
|
+
return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null && children(node).every(
|
|
1319
1163
|
(child) => child.type !== "do_block" && child.type !== "block"
|
|
1320
1164
|
);
|
|
1321
1165
|
}
|
|
@@ -1354,7 +1198,7 @@ function emitCall(emitter, call) {
|
|
|
1354
1198
|
}
|
|
1355
1199
|
const args = field(call, "arguments");
|
|
1356
1200
|
let position = 0;
|
|
1357
|
-
for (const argument of args === null ? [] :
|
|
1201
|
+
for (const argument of args === null ? [] : children(args)) {
|
|
1358
1202
|
if (argument.type === "pair") {
|
|
1359
1203
|
const key = field(argument, "key");
|
|
1360
1204
|
const value = field(argument, "value");
|
|
@@ -1384,7 +1228,7 @@ function emitArray(emitter, array) {
|
|
|
1384
1228
|
const objectKey = nodeId(emitter.filePath, array);
|
|
1385
1229
|
add(emitter, "objectValue", objectKey);
|
|
1386
1230
|
let position = 0;
|
|
1387
|
-
for (const element of
|
|
1231
|
+
for (const element of children(array)) {
|
|
1388
1232
|
add(
|
|
1389
1233
|
emitter,
|
|
1390
1234
|
"holdsProperty",
|
|
@@ -1398,7 +1242,7 @@ function emitArray(emitter, array) {
|
|
|
1398
1242
|
function emitHash(emitter, hash) {
|
|
1399
1243
|
const objectKey = nodeId(emitter.filePath, hash);
|
|
1400
1244
|
add(emitter, "objectValue", objectKey);
|
|
1401
|
-
for (const pair of
|
|
1245
|
+
for (const pair of children(hash)) {
|
|
1402
1246
|
if (pair.type !== "pair") {
|
|
1403
1247
|
continue;
|
|
1404
1248
|
}
|
|
@@ -1412,7 +1256,7 @@ function emitHash(emitter, hash) {
|
|
|
1412
1256
|
}
|
|
1413
1257
|
}
|
|
1414
1258
|
function walkExpressions(node, visit) {
|
|
1415
|
-
for (const child of
|
|
1259
|
+
for (const child of children(node)) {
|
|
1416
1260
|
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1417
1261
|
continue;
|
|
1418
1262
|
}
|
|
@@ -1422,7 +1266,7 @@ function walkExpressions(node, visit) {
|
|
|
1422
1266
|
}
|
|
1423
1267
|
function emitExpressionFacts(emitter, node) {
|
|
1424
1268
|
walkExpressions(node, (child) => {
|
|
1425
|
-
if (
|
|
1269
|
+
if (isPropertyRead(child)) {
|
|
1426
1270
|
emitPropertyRead(emitter, child);
|
|
1427
1271
|
} else if (child.type === "call") {
|
|
1428
1272
|
emitCall(emitter, child);
|
|
@@ -1442,7 +1286,7 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1442
1286
|
});
|
|
1443
1287
|
}
|
|
1444
1288
|
function implicitReturn(body) {
|
|
1445
|
-
const statements =
|
|
1289
|
+
const statements = children(body).filter(
|
|
1446
1290
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
1447
1291
|
);
|
|
1448
1292
|
const last = statements[statements.length - 1];
|
|
@@ -1454,7 +1298,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1454
1298
|
const params = field(method, "parameters");
|
|
1455
1299
|
const declared = /* @__PURE__ */ new Set();
|
|
1456
1300
|
let position = 0;
|
|
1457
|
-
for (const param of params === null ? [] :
|
|
1301
|
+
for (const param of params === null ? [] : children(params)) {
|
|
1458
1302
|
const paramName = paramNameOf(param);
|
|
1459
1303
|
if (paramName !== null) {
|
|
1460
1304
|
declared.add(paramName.text);
|
|
@@ -1473,7 +1317,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1473
1317
|
return funcKey;
|
|
1474
1318
|
}
|
|
1475
1319
|
const recordNested = (node) => {
|
|
1476
|
-
for (const child of
|
|
1320
|
+
for (const child of children(node)) {
|
|
1477
1321
|
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1478
1322
|
add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
|
|
1479
1323
|
continue;
|
|
@@ -1484,8 +1328,8 @@ function emitMethodFacts(emitter, method) {
|
|
|
1484
1328
|
recordNested(body);
|
|
1485
1329
|
walkExpressions(body, (child) => {
|
|
1486
1330
|
if (child.type === "return") {
|
|
1487
|
-
const first =
|
|
1488
|
-
const returned = first?.type === "argument_list" ?
|
|
1331
|
+
const first = children(child)[0];
|
|
1332
|
+
const returned = first?.type === "argument_list" ? children(first)[0] : first;
|
|
1489
1333
|
if (returned !== void 0) {
|
|
1490
1334
|
add(inside, "returnsValue", funcKey, valueKey(inside, returned));
|
|
1491
1335
|
}
|
|
@@ -1529,13 +1373,13 @@ function emitClassFacts(emitter, cls) {
|
|
|
1529
1373
|
const classKey = nodeId(emitter.filePath, cls);
|
|
1530
1374
|
add(emitter, "objectValue", classKey);
|
|
1531
1375
|
const superclass = field(cls, "superclass");
|
|
1532
|
-
const base = superclass === null ? null :
|
|
1376
|
+
const base = superclass === null ? null : children(superclass)[0] ?? null;
|
|
1533
1377
|
if (base !== null) {
|
|
1534
1378
|
add(emitter, "extends", classKey, valueKey(emitter, base));
|
|
1535
1379
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1536
1380
|
}
|
|
1537
1381
|
const body = field(cls, "body");
|
|
1538
|
-
for (const statement of body === null ? [] :
|
|
1382
|
+
for (const statement of body === null ? [] : children(body)) {
|
|
1539
1383
|
if (statement.type === "assignment") {
|
|
1540
1384
|
const left = field(statement, "left");
|
|
1541
1385
|
const right = field(statement, "right");
|
|
@@ -1577,7 +1421,7 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1577
1421
|
add(emitter, "exportsAs", filePath, name.text, key);
|
|
1578
1422
|
};
|
|
1579
1423
|
const walk = (node) => {
|
|
1580
|
-
for (const child of
|
|
1424
|
+
for (const child of children(node)) {
|
|
1581
1425
|
if (NESTING_TYPES.has(child.type)) {
|
|
1582
1426
|
declaresName(child, emitClassFacts(emitter, child));
|
|
1583
1427
|
continue;
|
|
@@ -1608,7 +1452,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1608
1452
|
"module",
|
|
1609
1453
|
"singleton_class"
|
|
1610
1454
|
]);
|
|
1611
|
-
var
|
|
1455
|
+
var BLOCK_TYPES = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1612
1456
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1613
1457
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1614
1458
|
"identifier",
|
|
@@ -2114,7 +1958,7 @@ function isTailExpression(node) {
|
|
|
2114
1958
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
2115
1959
|
return true;
|
|
2116
1960
|
}
|
|
2117
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
1961
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES.has(owner.type)) {
|
|
2118
1962
|
return true;
|
|
2119
1963
|
}
|
|
2120
1964
|
if (owner.type === "when") {
|
|
@@ -2133,7 +1977,7 @@ function isRoot(node) {
|
|
|
2133
1977
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
2134
1978
|
return true;
|
|
2135
1979
|
}
|
|
2136
|
-
return
|
|
1980
|
+
return BLOCK_TYPES.has(node.type) && node.parent?.type === "call";
|
|
2137
1981
|
}
|
|
2138
1982
|
function isPathStatement(node, parent) {
|
|
2139
1983
|
if (node.type === "elsif") {
|
|
@@ -2187,7 +2031,7 @@ function functionOf(node) {
|
|
|
2187
2031
|
body: bodyOf(block)
|
|
2188
2032
|
};
|
|
2189
2033
|
}
|
|
2190
|
-
if (
|
|
2034
|
+
if (BLOCK_TYPES.has(node.type)) {
|
|
2191
2035
|
return {
|
|
2192
2036
|
parameters: parametersOf(field(node, "parameters")),
|
|
2193
2037
|
body: bodyOf(node)
|
|
@@ -2715,7 +2559,10 @@ function conditionOf(condition) {
|
|
|
2715
2559
|
source: condition.source
|
|
2716
2560
|
};
|
|
2717
2561
|
}
|
|
2718
|
-
function branchOf(outcome, pattern, effects, extraEffects) {
|
|
2562
|
+
function branchOf(outcome, pattern, effects, extraEffects, options) {
|
|
2563
|
+
if (outcome.fellThrough === true && options.fallthrough === "handOn") {
|
|
2564
|
+
return handsOnBranch(outcome, effects, extraEffects);
|
|
2565
|
+
}
|
|
2719
2566
|
return {
|
|
2720
2567
|
conditions: outcome.conditions,
|
|
2721
2568
|
terminal: {
|
|
@@ -2740,7 +2587,28 @@ function branchOf(outcome, pattern, effects, extraEffects) {
|
|
|
2740
2587
|
isDefault: outcome.conditions.length === 0
|
|
2741
2588
|
};
|
|
2742
2589
|
}
|
|
2743
|
-
function
|
|
2590
|
+
function handsOnBranch(outcome, effects, extraEffects) {
|
|
2591
|
+
return {
|
|
2592
|
+
conditions: outcome.conditions,
|
|
2593
|
+
terminal: {
|
|
2594
|
+
kind: "delegate",
|
|
2595
|
+
statusCode: null,
|
|
2596
|
+
body: null,
|
|
2597
|
+
exceptionType: null,
|
|
2598
|
+
message: null,
|
|
2599
|
+
component: null,
|
|
2600
|
+
renderTree: null,
|
|
2601
|
+
delegateTarget: null,
|
|
2602
|
+
emitEvent: null,
|
|
2603
|
+
location: outcome.location
|
|
2604
|
+
},
|
|
2605
|
+
effects: effectsReaching(effects, outcome.conditions),
|
|
2606
|
+
...extraEffects === void 0 ? {} : { extraEffects },
|
|
2607
|
+
location: outcome.location,
|
|
2608
|
+
isDefault: outcome.conditions.length === 0
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
function responseBranches(method, pattern, effects, extraEffects, options = {}) {
|
|
2744
2612
|
const declarations = pattern.responseStatusCalls ?? [];
|
|
2745
2613
|
const body = field(method, "body");
|
|
2746
2614
|
if (declarations.length === 0 || body === null) {
|
|
@@ -2755,7 +2623,7 @@ function responseBranches(method, pattern, effects, extraEffects) {
|
|
|
2755
2623
|
(node) => lowered.terminalHome.has(node) && collectResponseCalls(node, byName, locals, []).length === 0
|
|
2756
2624
|
);
|
|
2757
2625
|
const terminals = [...responses, ...bareReturns];
|
|
2758
|
-
const enumerated =
|
|
2626
|
+
const enumerated = enumerateOrDegrade(
|
|
2759
2627
|
{
|
|
2760
2628
|
statements: lowered.statements,
|
|
2761
2629
|
terminalsByStmt: lowered.terminalsByStmt
|
|
@@ -2779,17 +2647,369 @@ function responseBranches(method, pattern, effects, extraEffects) {
|
|
|
2779
2647
|
outcomes.push({
|
|
2780
2648
|
conditions: path7.map(conditionOf),
|
|
2781
2649
|
reading: absentReading,
|
|
2782
|
-
location: rangeOf(method)
|
|
2650
|
+
location: rangeOf(method),
|
|
2651
|
+
fellThrough: true
|
|
2783
2652
|
});
|
|
2784
2653
|
}
|
|
2785
2654
|
if (outcomes.length === 0) {
|
|
2786
2655
|
return null;
|
|
2787
2656
|
}
|
|
2788
2657
|
return outcomes.map(
|
|
2789
|
-
(outcome) => branchOf(outcome, pattern, effects, extraEffects)
|
|
2658
|
+
(outcome) => branchOf(outcome, pattern, effects, extraEffects, options)
|
|
2790
2659
|
);
|
|
2791
2660
|
}
|
|
2792
2661
|
|
|
2662
|
+
// src/filters.ts
|
|
2663
|
+
function controllerFilters(pattern, ancestry) {
|
|
2664
|
+
const forms = pattern.filters ?? [];
|
|
2665
|
+
if (forms.length === 0) {
|
|
2666
|
+
return [];
|
|
2667
|
+
}
|
|
2668
|
+
const byName = new Map(forms.map((form) => [form.name, form]));
|
|
2669
|
+
const skipNames = /* @__PURE__ */ new Map();
|
|
2670
|
+
for (const form of forms) {
|
|
2671
|
+
if (form.skippedBy !== void 0) {
|
|
2672
|
+
skipNames.set(form.skippedBy, form.name);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
const declared = [];
|
|
2676
|
+
const skips = [];
|
|
2677
|
+
for (const { statement } of inheritedStatements(ancestry)) {
|
|
2678
|
+
const called = calledName(statement);
|
|
2679
|
+
if (called === null) {
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
const form = byName.get(called);
|
|
2683
|
+
if (form !== void 0) {
|
|
2684
|
+
declared.push(...declarationsOf(statement, form));
|
|
2685
|
+
continue;
|
|
2686
|
+
}
|
|
2687
|
+
const skipped = skipNames.get(called);
|
|
2688
|
+
if (skipped !== void 0) {
|
|
2689
|
+
skips.push(...skipsOf(statement, skipped));
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
const resolved = [];
|
|
2693
|
+
for (const declaration of applySkips(declared, skips)) {
|
|
2694
|
+
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
2695
|
+
if (found.type !== "found") {
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
resolved.push({
|
|
2699
|
+
filter: declaration.filter,
|
|
2700
|
+
methodName: declaration.methodName,
|
|
2701
|
+
method: found.method,
|
|
2702
|
+
file: found.block.file,
|
|
2703
|
+
enclosingQualifiedName: found.block.info.qualifiedName,
|
|
2704
|
+
only: declaration.only,
|
|
2705
|
+
except: declaration.except
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2708
|
+
return [
|
|
2709
|
+
...resolved.filter((one) => one.filter.onThrow !== true),
|
|
2710
|
+
...resolved.filter((one) => one.filter.onThrow === true)
|
|
2711
|
+
];
|
|
2712
|
+
}
|
|
2713
|
+
function filterCoversAction(filter, actionName) {
|
|
2714
|
+
if (filter.except.has(actionName)) {
|
|
2715
|
+
return false;
|
|
2716
|
+
}
|
|
2717
|
+
return filter.only === null || filter.only.has(actionName);
|
|
2718
|
+
}
|
|
2719
|
+
function filterReference(filter, displayPath) {
|
|
2720
|
+
return {
|
|
2721
|
+
file: displayPath,
|
|
2722
|
+
name: filter.methodName,
|
|
2723
|
+
...filter.filter.onThrow === true ? { onThrow: true } : {}
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
function filterUnit(filter, pattern, displayPath, body) {
|
|
2727
|
+
const range = rangeOf(filter.method);
|
|
2728
|
+
const branches = responseBranches(
|
|
2729
|
+
filter.method,
|
|
2730
|
+
pattern,
|
|
2731
|
+
body.effects ?? [],
|
|
2732
|
+
body.extraEffects,
|
|
2733
|
+
{ fallthrough: "handOn" }
|
|
2734
|
+
);
|
|
2735
|
+
return {
|
|
2736
|
+
identity: {
|
|
2737
|
+
name: filter.methodName,
|
|
2738
|
+
nameKind: "binding",
|
|
2739
|
+
kind: "middleware",
|
|
2740
|
+
file: displayPath,
|
|
2741
|
+
range,
|
|
2742
|
+
span: spanOf(filter.method),
|
|
2743
|
+
exportName: filter.methodName,
|
|
2744
|
+
exportPath: [filter.enclosingQualifiedName, filter.methodName]
|
|
2745
|
+
},
|
|
2746
|
+
boundaryBinding: null,
|
|
2747
|
+
parameters: [],
|
|
2748
|
+
branches: branches ?? [handsOn(range, body)],
|
|
2749
|
+
bodyContent: body.bodyContent ?? "absent",
|
|
2750
|
+
dependencyCalls: [],
|
|
2751
|
+
declaredContract: null
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
function handsOn(range, body) {
|
|
2755
|
+
return {
|
|
2756
|
+
conditions: [],
|
|
2757
|
+
terminal: {
|
|
2758
|
+
kind: "delegate",
|
|
2759
|
+
statusCode: null,
|
|
2760
|
+
body: null,
|
|
2761
|
+
exceptionType: null,
|
|
2762
|
+
message: null,
|
|
2763
|
+
component: null,
|
|
2764
|
+
renderTree: null,
|
|
2765
|
+
delegateTarget: null,
|
|
2766
|
+
emitEvent: null,
|
|
2767
|
+
location: range
|
|
2768
|
+
},
|
|
2769
|
+
effects: body.effects ?? [],
|
|
2770
|
+
...body.extraEffects === void 0 ? {} : { extraEffects: body.extraEffects },
|
|
2771
|
+
location: range,
|
|
2772
|
+
isDefault: true
|
|
2773
|
+
};
|
|
2774
|
+
}
|
|
2775
|
+
function calledName(statement) {
|
|
2776
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
2777
|
+
return null;
|
|
2778
|
+
}
|
|
2779
|
+
return field(statement, "method")?.text ?? null;
|
|
2780
|
+
}
|
|
2781
|
+
function declarationsOf(statement, filter) {
|
|
2782
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
2783
|
+
const keywords = filter.actionKeywords;
|
|
2784
|
+
const only = keywords === void 0 ? null : namesUnder(args.keyword[keywords.include]);
|
|
2785
|
+
const except = keywords === void 0 ? null : namesUnder(args.keyword[keywords.exclude]);
|
|
2786
|
+
return methodNamesOf(args, filter).map((methodName) => ({
|
|
2787
|
+
filter,
|
|
2788
|
+
methodName,
|
|
2789
|
+
only,
|
|
2790
|
+
except: except ?? /* @__PURE__ */ new Set()
|
|
2791
|
+
}));
|
|
2792
|
+
}
|
|
2793
|
+
function methodNamesOf(args, filter) {
|
|
2794
|
+
if (filter.methodFrom === "withKeyword") {
|
|
2795
|
+
const named2 = args.keyword.with;
|
|
2796
|
+
const value = named2 === void 0 ? null : symbolValue(named2);
|
|
2797
|
+
return value === null ? [] : [value];
|
|
2798
|
+
}
|
|
2799
|
+
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
|
|
2800
|
+
}
|
|
2801
|
+
function skipsOf(statement, filterName) {
|
|
2802
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
2803
|
+
const actions = namesUnder(args.keyword.only);
|
|
2804
|
+
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
2805
|
+
}
|
|
2806
|
+
function applySkips(declared, skips) {
|
|
2807
|
+
const left = [];
|
|
2808
|
+
for (const declaration of declared) {
|
|
2809
|
+
const reaching = skips.filter(
|
|
2810
|
+
(skip) => skip.filterName === declaration.filter.name && skip.methodName === declaration.methodName
|
|
2811
|
+
);
|
|
2812
|
+
if (reaching.some((skip) => skip.actions === null)) {
|
|
2813
|
+
continue;
|
|
2814
|
+
}
|
|
2815
|
+
const except = new Set(declaration.except);
|
|
2816
|
+
for (const skip of reaching) {
|
|
2817
|
+
for (const action of skip.actions ?? []) {
|
|
2818
|
+
except.add(action);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
left.push({ ...declaration, except });
|
|
2822
|
+
}
|
|
2823
|
+
return left;
|
|
2824
|
+
}
|
|
2825
|
+
function namesUnder(node) {
|
|
2826
|
+
if (node === void 0) {
|
|
2827
|
+
return null;
|
|
2828
|
+
}
|
|
2829
|
+
const single = symbolValue(node);
|
|
2830
|
+
if (single !== null) {
|
|
2831
|
+
return /* @__PURE__ */ new Set([single]);
|
|
2832
|
+
}
|
|
2833
|
+
if (node.type !== "array" && node.type !== "symbol_array") {
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
const names = /* @__PURE__ */ new Set();
|
|
2837
|
+
for (const child of bodyStatements(node)) {
|
|
2838
|
+
const bare = child.type === "bare_symbol" ? child.text : null;
|
|
2839
|
+
const name = symbolValue(child) ?? bare;
|
|
2840
|
+
if (name !== null) {
|
|
2841
|
+
names.add(name);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return names;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/paths/effects.ts
|
|
2848
|
+
import { enumerateOrDegrade as enumerateOrDegrade2, sharedGatingConditions } from "@suss/extractor";
|
|
2849
|
+
var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
|
|
2850
|
+
var BLOCK_TYPES2 = /* @__PURE__ */ new Set([
|
|
2851
|
+
"body_statement",
|
|
2852
|
+
"then",
|
|
2853
|
+
"else",
|
|
2854
|
+
"do_block",
|
|
2855
|
+
"block"
|
|
2856
|
+
]);
|
|
2857
|
+
var LITERAL_ARGS = {
|
|
2858
|
+
string: (node) => ({
|
|
2859
|
+
kind: "string",
|
|
2860
|
+
value: node.text.replace(/^["']|["']$/g, "")
|
|
2861
|
+
}),
|
|
2862
|
+
integer: (node) => {
|
|
2863
|
+
const value = Number.parseInt(node.text, 10);
|
|
2864
|
+
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
2865
|
+
},
|
|
2866
|
+
float: (node) => {
|
|
2867
|
+
const value = Number.parseFloat(node.text);
|
|
2868
|
+
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
2869
|
+
},
|
|
2870
|
+
true: () => ({ kind: "boolean", value: true }),
|
|
2871
|
+
false: () => ({ kind: "boolean", value: false }),
|
|
2872
|
+
simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
|
|
2873
|
+
};
|
|
2874
|
+
function children2(node) {
|
|
2875
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
2876
|
+
}
|
|
2877
|
+
function withoutChainLinks(calls) {
|
|
2878
|
+
const isLink = /* @__PURE__ */ new Set();
|
|
2879
|
+
for (const call of calls) {
|
|
2880
|
+
const receiver = field(call, "receiver");
|
|
2881
|
+
if (receiver !== null && receiver.type === "call") {
|
|
2882
|
+
isLink.add(receiver.id);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
return calls.filter((call) => !isLink.has(call.id));
|
|
2886
|
+
}
|
|
2887
|
+
function calleeMethodName(call) {
|
|
2888
|
+
if (call.type === "identifier") {
|
|
2889
|
+
return call.text;
|
|
2890
|
+
}
|
|
2891
|
+
const method = field(call, "method");
|
|
2892
|
+
if (method !== null) {
|
|
2893
|
+
return method.text;
|
|
2894
|
+
}
|
|
2895
|
+
return field(call, "receiver") !== null ? "call" : children2(call)[0]?.text;
|
|
2896
|
+
}
|
|
2897
|
+
function isPropertyRead2(node) {
|
|
2898
|
+
if (field(node, "receiver") === null || field(node, "arguments") !== null) {
|
|
2899
|
+
return false;
|
|
2900
|
+
}
|
|
2901
|
+
return calleeMethodName(node) !== "call";
|
|
2902
|
+
}
|
|
2903
|
+
function isRaise2(node) {
|
|
2904
|
+
const method = field(node, "method") ?? children2(node)[0] ?? null;
|
|
2905
|
+
return field(node, "receiver") === null && method !== null && RAISE_NAMES2.has(method.text);
|
|
2906
|
+
}
|
|
2907
|
+
var NO_INHERITED_METHODS = /* @__PURE__ */ new Set();
|
|
2908
|
+
function isInherited(node, inherited) {
|
|
2909
|
+
const method = field(node, "method");
|
|
2910
|
+
return field(node, "receiver") === null && method !== null && inherited.has(method.text);
|
|
2911
|
+
}
|
|
2912
|
+
function isCall(node, locals, inherited) {
|
|
2913
|
+
if (node.type === "call") {
|
|
2914
|
+
return !isPropertyRead2(node) && !isRaise2(node) && !isInherited(node, inherited);
|
|
2915
|
+
}
|
|
2916
|
+
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
2917
|
+
}
|
|
2918
|
+
function collectCalls(node, locals, inherited, found) {
|
|
2919
|
+
for (const child of children2(node)) {
|
|
2920
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
if (isCall(child, locals, inherited)) {
|
|
2924
|
+
found.push(child);
|
|
2925
|
+
}
|
|
2926
|
+
collectCalls(child, locals, inherited, found);
|
|
2927
|
+
}
|
|
2928
|
+
return found;
|
|
2929
|
+
}
|
|
2930
|
+
function bodyCalls(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
2931
|
+
const body = field(definitionNode, "body");
|
|
2932
|
+
if (body === null) {
|
|
2933
|
+
return [];
|
|
2934
|
+
}
|
|
2935
|
+
return collectCalls(body, localNamesIn(definitionNode), inherited, []);
|
|
2936
|
+
}
|
|
2937
|
+
function calleeText(call) {
|
|
2938
|
+
const receiver = field(call, "receiver");
|
|
2939
|
+
const method = calleeMethodName(call);
|
|
2940
|
+
if (method === void 0) {
|
|
2941
|
+
return call.text;
|
|
2942
|
+
}
|
|
2943
|
+
return receiver === null ? method : `${receiver.text}.${method}`;
|
|
2944
|
+
}
|
|
2945
|
+
function argOf(node) {
|
|
2946
|
+
const literal = LITERAL_ARGS[node.type]?.(node);
|
|
2947
|
+
if (literal !== null && literal !== void 0) {
|
|
2948
|
+
return literal;
|
|
2949
|
+
}
|
|
2950
|
+
if (node.type === "call" && !isPropertyRead2(node)) {
|
|
2951
|
+
return { kind: "call", callee: calleeText(node), args: argsOf(node) };
|
|
2952
|
+
}
|
|
2953
|
+
return { kind: "identifier", name: node.text };
|
|
2954
|
+
}
|
|
2955
|
+
function argsOf(call) {
|
|
2956
|
+
const args = field(call, "arguments");
|
|
2957
|
+
if (args === null) {
|
|
2958
|
+
return [];
|
|
2959
|
+
}
|
|
2960
|
+
return children2(args).map(
|
|
2961
|
+
(child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
function enclosingStatement(call, body) {
|
|
2965
|
+
let current = call;
|
|
2966
|
+
while (current.parent !== null) {
|
|
2967
|
+
if (BLOCK_TYPES2.has(current.parent.type) || current.parent.id === body.id) {
|
|
2968
|
+
return current;
|
|
2969
|
+
}
|
|
2970
|
+
current = current.parent;
|
|
2971
|
+
}
|
|
2972
|
+
return current;
|
|
2973
|
+
}
|
|
2974
|
+
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
2975
|
+
const body = field(definitionNode, "body");
|
|
2976
|
+
if (body === null) {
|
|
2977
|
+
return [];
|
|
2978
|
+
}
|
|
2979
|
+
const calls = withoutChainLinks(bodyCalls(definitionNode, inherited));
|
|
2980
|
+
if (calls.length === 0) {
|
|
2981
|
+
return [];
|
|
2982
|
+
}
|
|
2983
|
+
const statementOf2 = /* @__PURE__ */ new Map();
|
|
2984
|
+
const statements = [];
|
|
2985
|
+
for (const call of calls) {
|
|
2986
|
+
const statement = enclosingStatement(call, body);
|
|
2987
|
+
statementOf2.set(call.id, statement);
|
|
2988
|
+
statements.push(statement);
|
|
2989
|
+
}
|
|
2990
|
+
const lowered = lowerRubyBody(body, statements);
|
|
2991
|
+
const enumerated = enumerateOrDegrade2(
|
|
2992
|
+
{
|
|
2993
|
+
statements: lowered.statements,
|
|
2994
|
+
terminalsByStmt: lowered.terminalsByStmt
|
|
2995
|
+
},
|
|
2996
|
+
statements
|
|
2997
|
+
);
|
|
2998
|
+
return calls.map((call) => {
|
|
2999
|
+
const statement = statementOf2.get(call.id);
|
|
3000
|
+
const conditions = sharedGatingConditions(
|
|
3001
|
+
statement === void 0 ? void 0 : enumerated.byTerminal.get(statement)
|
|
3002
|
+
);
|
|
3003
|
+
return {
|
|
3004
|
+
type: "invocation",
|
|
3005
|
+
callee: calleeText(call),
|
|
3006
|
+
args: argsOf(call),
|
|
3007
|
+
async: false,
|
|
3008
|
+
...conditions.length > 0 ? { preconditions: conditions } : {}
|
|
3009
|
+
};
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
|
|
2793
3013
|
// src/storage.ts
|
|
2794
3014
|
import { deriveOnDemand as deriveOnDemand2, evaluate as evaluate2 } from "@suss/datalog";
|
|
2795
3015
|
import { storageBinding } from "@suss/ir-core";
|
|
@@ -3093,7 +3313,23 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3093
3313
|
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
3094
3314
|
return [];
|
|
3095
3315
|
}
|
|
3316
|
+
const filters = controllerFilters(pattern, ancestry);
|
|
3096
3317
|
const units = [];
|
|
3318
|
+
for (const filter of filters) {
|
|
3319
|
+
const displayPath = options.displayPathOf?.(filter.file) ?? filter.file;
|
|
3320
|
+
const raw = filterUnit(
|
|
3321
|
+
filter,
|
|
3322
|
+
pattern,
|
|
3323
|
+
displayPath,
|
|
3324
|
+
bodyOfMethod(filter.method, options)
|
|
3325
|
+
);
|
|
3326
|
+
units.push(raw);
|
|
3327
|
+
options.onReachSeed?.(raw, {
|
|
3328
|
+
file: filter.file,
|
|
3329
|
+
node: filter.method,
|
|
3330
|
+
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3331
|
+
});
|
|
3332
|
+
}
|
|
3097
3333
|
for (const block of ownBlocks) {
|
|
3098
3334
|
if (block.info.bodyNode === null) {
|
|
3099
3335
|
continue;
|
|
@@ -3105,6 +3341,12 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3105
3341
|
if ((visibility.get(actionName) ?? "public") !== "public") {
|
|
3106
3342
|
continue;
|
|
3107
3343
|
}
|
|
3344
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
3345
|
+
(filter) => filterReference(
|
|
3346
|
+
filter,
|
|
3347
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
3348
|
+
)
|
|
3349
|
+
);
|
|
3108
3350
|
const raw = buildControllerActionUnit(
|
|
3109
3351
|
pack,
|
|
3110
3352
|
pattern,
|
|
@@ -3112,7 +3354,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3112
3354
|
actionName,
|
|
3113
3355
|
method,
|
|
3114
3356
|
options.filePath,
|
|
3115
|
-
options
|
|
3357
|
+
options,
|
|
3358
|
+
around
|
|
3116
3359
|
);
|
|
3117
3360
|
units.push(raw);
|
|
3118
3361
|
options.onReachSeed?.(raw, {
|
|
@@ -3124,7 +3367,7 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3124
3367
|
}
|
|
3125
3368
|
return units;
|
|
3126
3369
|
}
|
|
3127
|
-
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, filePath, bodyRead) {
|
|
3370
|
+
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, filePath, bodyRead, wrappers = []) {
|
|
3128
3371
|
const range = rangeOf(method);
|
|
3129
3372
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
3130
3373
|
const body = bodyOfMethod(method, bodyRead);
|
|
@@ -3179,7 +3422,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3179
3422
|
],
|
|
3180
3423
|
bodyContent: body.bodyContent ?? "absent",
|
|
3181
3424
|
dependencyCalls: [],
|
|
3182
|
-
declaredContract: null
|
|
3425
|
+
declaredContract: null,
|
|
3426
|
+
...wrappers.length > 0 ? { wrappers: [...wrappers] } : {}
|
|
3183
3427
|
};
|
|
3184
3428
|
}
|
|
3185
3429
|
function routingGapUnit(pattern, gaps) {
|
|
@@ -3786,6 +4030,7 @@ import {
|
|
|
3786
4030
|
import { Database as Database2 } from "@suss/datalog";
|
|
3787
4031
|
import {
|
|
3788
4032
|
assembleSummary as assembleSummary2,
|
|
4033
|
+
composeWrappers,
|
|
3789
4034
|
createCacheLayer,
|
|
3790
4035
|
createTimer,
|
|
3791
4036
|
moduleInitStructure,
|
|
@@ -4375,6 +4620,14 @@ function inheritedMethodsIn(packs) {
|
|
|
4375
4620
|
}
|
|
4376
4621
|
return found;
|
|
4377
4622
|
}
|
|
4623
|
+
function alreadyDiscovered(seen, raw) {
|
|
4624
|
+
const key = `${raw.identity.file}::${raw.identity.name}::${raw.identity.range.start}`;
|
|
4625
|
+
if (seen.has(key)) {
|
|
4626
|
+
return true;
|
|
4627
|
+
}
|
|
4628
|
+
seen.add(key);
|
|
4629
|
+
return false;
|
|
4630
|
+
}
|
|
4378
4631
|
async function extractRubyProject(options) {
|
|
4379
4632
|
const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
|
|
4380
4633
|
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
@@ -4436,6 +4689,7 @@ async function extractRubyProject(options) {
|
|
|
4436
4689
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file) : file;
|
|
4437
4690
|
const seeds = [];
|
|
4438
4691
|
const summariesBySeed = /* @__PURE__ */ new Map();
|
|
4692
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
4439
4693
|
for (const { file, root } of parsed) {
|
|
4440
4694
|
const displayPath = displayPathOf(file);
|
|
4441
4695
|
const seedByRaw = /* @__PURE__ */ new Map();
|
|
@@ -4448,10 +4702,14 @@ async function extractRubyProject(options) {
|
|
|
4448
4702
|
cache,
|
|
4449
4703
|
...storagePatterns.length > 0 ? { storage: { facts: db, patterns: storagePatterns } } : {},
|
|
4450
4704
|
inheritedMethods,
|
|
4705
|
+
displayPathOf,
|
|
4451
4706
|
onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
|
|
4452
4707
|
})
|
|
4453
4708
|
);
|
|
4454
4709
|
for (const raw of rawUnits) {
|
|
4710
|
+
if (alreadyDiscovered(discovered, raw)) {
|
|
4711
|
+
continue;
|
|
4712
|
+
}
|
|
4455
4713
|
const summary = timer.time(
|
|
4456
4714
|
"summarize",
|
|
4457
4715
|
() => assembleSummary2(raw, { gapHandling: "permissive" })
|
|
@@ -4554,12 +4812,18 @@ async function extractRubyProject(options) {
|
|
|
4554
4812
|
}
|
|
4555
4813
|
disambiguateSummaryIds(summaries);
|
|
4556
4814
|
linkCallsToSummaries(summaries);
|
|
4815
|
+
const composed = timer.time(
|
|
4816
|
+
"summarize",
|
|
4817
|
+
() => composeWrappers(summaries, {
|
|
4818
|
+
gapHandling: options.gapHandling ?? "permissive"
|
|
4819
|
+
})
|
|
4820
|
+
);
|
|
4557
4821
|
await timer.timeAsync("cache.write", async () => {
|
|
4558
|
-
if (cacheDir === null ||
|
|
4822
|
+
if (cacheDir === null || composed.length === 0) {
|
|
4559
4823
|
return;
|
|
4560
4824
|
}
|
|
4561
4825
|
try {
|
|
4562
|
-
await extractionCache.write(cacheInput,
|
|
4826
|
+
await extractionCache.write(cacheInput, composed);
|
|
4563
4827
|
} catch {
|
|
4564
4828
|
}
|
|
4565
4829
|
});
|
|
@@ -4568,11 +4832,11 @@ async function extractRubyProject(options) {
|
|
|
4568
4832
|
packs: options.packs,
|
|
4569
4833
|
tallies,
|
|
4570
4834
|
filesWalked: options.files.length,
|
|
4571
|
-
summaries
|
|
4835
|
+
summaries: composed
|
|
4572
4836
|
})
|
|
4573
4837
|
);
|
|
4574
4838
|
options.onTiming?.(timer.report());
|
|
4575
|
-
return { summaries, facts: db };
|
|
4839
|
+
return { summaries: composed, facts: db };
|
|
4576
4840
|
}
|
|
4577
4841
|
function fileDependenciesOf(db, displayPathOf) {
|
|
4578
4842
|
const byFile = /* @__PURE__ */ new Map();
|