@suss/adapter-ruby 0.23.0 → 0.24.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 +25 -264
- package/dist/index.cjs +764 -366
- package/dist/index.d.cts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +746 -348
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -531,120 +531,16 @@ function inheritedStatements(ancestry, blockConfigures) {
|
|
|
531
531
|
import {
|
|
532
532
|
dispatchByType,
|
|
533
533
|
graphqlResolverBinding,
|
|
534
|
-
restBinding
|
|
534
|
+
restBinding as restBinding2
|
|
535
535
|
} from "@suss/behavioral-ir";
|
|
536
536
|
import { absentReading as absentReading2, unreadableReading as unreadableReading2 } from "@suss/extractor";
|
|
537
537
|
|
|
538
|
-
// src/
|
|
539
|
-
import {
|
|
540
|
-
import {
|
|
541
|
-
var RUBY_ENV_RECOGNITION = "ruby-env";
|
|
542
|
-
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
|
|
543
|
-
function envReadEffects(root) {
|
|
544
|
-
const effects = [];
|
|
545
|
-
walkDescendants2(root, null, {
|
|
546
|
-
at: (node) => {
|
|
547
|
-
const read = envReadAt(node);
|
|
548
|
-
if (read !== null) {
|
|
549
|
-
effects.push(configReadEffect(read));
|
|
550
|
-
}
|
|
551
|
-
},
|
|
552
|
-
into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? SKIP_CHILDREN2 : null
|
|
553
|
-
});
|
|
554
|
-
return effects;
|
|
555
|
-
}
|
|
556
|
-
function configReadEffect(read) {
|
|
557
|
-
return {
|
|
558
|
-
type: "interaction",
|
|
559
|
-
binding: runtimeConfigBinding({
|
|
560
|
-
recognition: RUBY_ENV_RECOGNITION,
|
|
561
|
-
deploymentTarget: "lambda",
|
|
562
|
-
instanceName: "<unknown>"
|
|
563
|
-
}),
|
|
564
|
-
callee: `ENV["${read.name}"]`,
|
|
565
|
-
interaction: {
|
|
566
|
-
class: "config-read",
|
|
567
|
-
name: read.name,
|
|
568
|
-
defaulted: read.defaulted
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function envReadAt(node) {
|
|
573
|
-
if (node.type === "element_reference") {
|
|
574
|
-
return elementRead(node);
|
|
575
|
-
}
|
|
576
|
-
if (node.type === "call") {
|
|
577
|
-
return fetchRead(node);
|
|
578
|
-
}
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
function elementRead(node) {
|
|
582
|
-
const object = field(node, "object");
|
|
583
|
-
if (object === null || !isEnv(object) || isAssignedTo(node)) {
|
|
584
|
-
return null;
|
|
585
|
-
}
|
|
586
|
-
const index = node.namedChildren.find(
|
|
587
|
-
(child) => child !== null && child.id !== object.id
|
|
588
|
-
);
|
|
589
|
-
const name = index === void 0 ? null : stringLiteralValue(index);
|
|
590
|
-
if (name === null) {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
return { name, defaulted: isDefaultedAt(node) };
|
|
594
|
-
}
|
|
595
|
-
function fetchRead(node) {
|
|
596
|
-
const receiver = field(node, "receiver");
|
|
597
|
-
if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
const { positional } = readCallArgs(field(node, "arguments"));
|
|
601
|
-
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
602
|
-
if (name === null) {
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
const hasDefault = positional.length > 1 || field(node, "block") !== null;
|
|
606
|
-
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
607
|
-
}
|
|
608
|
-
function isEnv(node) {
|
|
609
|
-
if (node.type === "constant") {
|
|
610
|
-
return node.text === "ENV";
|
|
611
|
-
}
|
|
612
|
-
return node.type === "scope_resolution" && field(node, "scope") === null && field(node, "name")?.text === "ENV";
|
|
613
|
-
}
|
|
614
|
-
function isAssignedTo(node) {
|
|
615
|
-
const parent = node.parent;
|
|
616
|
-
return parent !== null && (parent.type === "assignment" || parent.type === "operator_assignment") && field(parent, "left")?.id === node.id;
|
|
617
|
-
}
|
|
618
|
-
function isDefaultedAt(node) {
|
|
619
|
-
let child = node;
|
|
620
|
-
let parent = node.parent;
|
|
621
|
-
while (parent !== null) {
|
|
622
|
-
if (parent.type === "parenthesized_statements") {
|
|
623
|
-
child = parent;
|
|
624
|
-
parent = parent.parent;
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
if (parent.type !== "binary" || field(parent, "operator")?.text !== "||") {
|
|
628
|
-
return false;
|
|
629
|
-
}
|
|
630
|
-
if (field(parent, "left")?.id === child.id) {
|
|
631
|
-
return true;
|
|
632
|
-
}
|
|
633
|
-
child = parent;
|
|
634
|
-
parent = parent.parent;
|
|
635
|
-
}
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
538
|
+
// src/clientCalls.ts
|
|
539
|
+
import { namesNothing, restBinding } from "@suss/behavioral-ir";
|
|
540
|
+
import { pathOf } from "@suss/values";
|
|
638
541
|
|
|
639
|
-
// src/
|
|
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";
|
|
542
|
+
// src/paths/effects.ts
|
|
543
|
+
import { enumerateOrDegrade, sharedGatingConditions } from "@suss/extractor";
|
|
648
544
|
|
|
649
545
|
// src/paths/bareCalls.ts
|
|
650
546
|
var PARAMETER_LIST_TYPES = [
|
|
@@ -1020,9 +916,295 @@ function lowerRubyBody(body, terminals, responseCalls = []) {
|
|
|
1020
916
|
};
|
|
1021
917
|
}
|
|
1022
918
|
|
|
919
|
+
// src/paths/effects.ts
|
|
920
|
+
var RAISE_NAMES2 = /* @__PURE__ */ new Set(["raise", "fail"]);
|
|
921
|
+
var BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
922
|
+
"body_statement",
|
|
923
|
+
"then",
|
|
924
|
+
"else",
|
|
925
|
+
"do_block",
|
|
926
|
+
"block"
|
|
927
|
+
]);
|
|
928
|
+
var LITERAL_ARGS = {
|
|
929
|
+
string: (node) => ({
|
|
930
|
+
kind: "string",
|
|
931
|
+
value: node.text.replace(/^["']|["']$/g, "")
|
|
932
|
+
}),
|
|
933
|
+
integer: (node) => {
|
|
934
|
+
const value = Number.parseInt(node.text, 10);
|
|
935
|
+
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
936
|
+
},
|
|
937
|
+
float: (node) => {
|
|
938
|
+
const value = Number.parseFloat(node.text);
|
|
939
|
+
return Number.isNaN(value) ? null : { kind: "number", value };
|
|
940
|
+
},
|
|
941
|
+
true: () => ({ kind: "boolean", value: true }),
|
|
942
|
+
false: () => ({ kind: "boolean", value: false }),
|
|
943
|
+
simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
|
|
944
|
+
};
|
|
945
|
+
function children(node) {
|
|
946
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
947
|
+
}
|
|
948
|
+
function withoutChainLinks(calls) {
|
|
949
|
+
const isLink = /* @__PURE__ */ new Set();
|
|
950
|
+
for (const call of calls) {
|
|
951
|
+
const receiver = field(call, "receiver");
|
|
952
|
+
if (receiver !== null && receiver.type === "call") {
|
|
953
|
+
isLink.add(receiver.id);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return calls.filter((call) => !isLink.has(call.id));
|
|
957
|
+
}
|
|
958
|
+
function calleeMethodName(call) {
|
|
959
|
+
if (call.type === "identifier") {
|
|
960
|
+
return call.text;
|
|
961
|
+
}
|
|
962
|
+
const method = field(call, "method");
|
|
963
|
+
if (method !== null) {
|
|
964
|
+
return method.text;
|
|
965
|
+
}
|
|
966
|
+
return field(call, "receiver") !== null ? "call" : children(call)[0]?.text;
|
|
967
|
+
}
|
|
968
|
+
function isPropertyRead(node) {
|
|
969
|
+
if (field(node, "receiver") === null || field(node, "arguments") !== null) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
return calleeMethodName(node) !== "call";
|
|
973
|
+
}
|
|
974
|
+
function isRaise2(node) {
|
|
975
|
+
const method = field(node, "method") ?? children(node)[0] ?? null;
|
|
976
|
+
return field(node, "receiver") === null && method !== null && RAISE_NAMES2.has(method.text);
|
|
977
|
+
}
|
|
978
|
+
var NO_INHERITED_METHODS = /* @__PURE__ */ new Set();
|
|
979
|
+
function isInherited(node, inherited) {
|
|
980
|
+
const method = field(node, "method");
|
|
981
|
+
return field(node, "receiver") === null && method !== null && inherited.has(method.text);
|
|
982
|
+
}
|
|
983
|
+
function isCall(node, locals, inherited) {
|
|
984
|
+
if (node.type === "call") {
|
|
985
|
+
return !isPropertyRead(node) && !isRaise2(node) && !isInherited(node, inherited);
|
|
986
|
+
}
|
|
987
|
+
return isBareMethodCall(node, locals) && !RAISE_NAMES2.has(node.text) && !inherited.has(node.text);
|
|
988
|
+
}
|
|
989
|
+
function collectCalls(node, locals, inherited, found) {
|
|
990
|
+
for (const child of children(node)) {
|
|
991
|
+
if (OWN_BODY_TYPES.has(child.type)) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (isCall(child, locals, inherited)) {
|
|
995
|
+
found.push(child);
|
|
996
|
+
}
|
|
997
|
+
collectCalls(child, locals, inherited, found);
|
|
998
|
+
}
|
|
999
|
+
return found;
|
|
1000
|
+
}
|
|
1001
|
+
function bodyCalls(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
1002
|
+
const body = field(definitionNode, "body");
|
|
1003
|
+
if (body === null) {
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
return collectCalls(body, localNamesIn(definitionNode), inherited, []);
|
|
1007
|
+
}
|
|
1008
|
+
function calleeText(call) {
|
|
1009
|
+
const receiver = field(call, "receiver");
|
|
1010
|
+
const method = calleeMethodName(call);
|
|
1011
|
+
if (method === void 0) {
|
|
1012
|
+
return call.text;
|
|
1013
|
+
}
|
|
1014
|
+
return receiver === null ? method : `${receiver.text}.${method}`;
|
|
1015
|
+
}
|
|
1016
|
+
function argOf(node) {
|
|
1017
|
+
const literal = LITERAL_ARGS[node.type]?.(node);
|
|
1018
|
+
if (literal !== null && literal !== void 0) {
|
|
1019
|
+
return literal;
|
|
1020
|
+
}
|
|
1021
|
+
if (node.type === "call" && !isPropertyRead(node)) {
|
|
1022
|
+
return { kind: "call", callee: calleeText(node), args: argsOf(node) };
|
|
1023
|
+
}
|
|
1024
|
+
return { kind: "identifier", name: node.text };
|
|
1025
|
+
}
|
|
1026
|
+
function argsOf(call) {
|
|
1027
|
+
const args = field(call, "arguments");
|
|
1028
|
+
if (args === null) {
|
|
1029
|
+
return [];
|
|
1030
|
+
}
|
|
1031
|
+
return children(args).map(
|
|
1032
|
+
(child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
function enclosingStatement(call, body) {
|
|
1036
|
+
let current = call;
|
|
1037
|
+
while (current.parent !== null) {
|
|
1038
|
+
if (BLOCK_TYPES.has(current.parent.type) || current.parent.id === body.id) {
|
|
1039
|
+
return current;
|
|
1040
|
+
}
|
|
1041
|
+
current = current.parent;
|
|
1042
|
+
}
|
|
1043
|
+
return current;
|
|
1044
|
+
}
|
|
1045
|
+
function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS) {
|
|
1046
|
+
const body = field(definitionNode, "body");
|
|
1047
|
+
if (body === null) {
|
|
1048
|
+
return [];
|
|
1049
|
+
}
|
|
1050
|
+
const calls = withoutChainLinks(bodyCalls(definitionNode, inherited));
|
|
1051
|
+
if (calls.length === 0) {
|
|
1052
|
+
return [];
|
|
1053
|
+
}
|
|
1054
|
+
const statementOf2 = /* @__PURE__ */ new Map();
|
|
1055
|
+
const statements = [];
|
|
1056
|
+
for (const call of calls) {
|
|
1057
|
+
const statement = enclosingStatement(call, body);
|
|
1058
|
+
statementOf2.set(call.id, statement);
|
|
1059
|
+
statements.push(statement);
|
|
1060
|
+
}
|
|
1061
|
+
const lowered = lowerRubyBody(body, statements);
|
|
1062
|
+
const enumerated = enumerateOrDegrade(
|
|
1063
|
+
{
|
|
1064
|
+
statements: lowered.statements,
|
|
1065
|
+
terminalsByStmt: lowered.terminalsByStmt
|
|
1066
|
+
},
|
|
1067
|
+
statements
|
|
1068
|
+
);
|
|
1069
|
+
return calls.map((call) => {
|
|
1070
|
+
const statement = statementOf2.get(call.id);
|
|
1071
|
+
const conditions = sharedGatingConditions(
|
|
1072
|
+
statement === void 0 ? void 0 : enumerated.byTerminal.get(statement)
|
|
1073
|
+
);
|
|
1074
|
+
return {
|
|
1075
|
+
type: "invocation",
|
|
1076
|
+
callee: calleeText(call),
|
|
1077
|
+
args: argsOf(call),
|
|
1078
|
+
async: false,
|
|
1079
|
+
...conditions.length > 0 ? { preconditions: conditions } : {}
|
|
1080
|
+
};
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/responseStatus.ts
|
|
1085
|
+
import {
|
|
1086
|
+
absentReading,
|
|
1087
|
+
enumerateOrDegrade as enumerateOrDegrade2,
|
|
1088
|
+
guardsHoldOn,
|
|
1089
|
+
unreadableReading,
|
|
1090
|
+
writtenReading
|
|
1091
|
+
} from "@suss/extractor";
|
|
1092
|
+
import { constantOf, literalOf as literalOf3 } from "@suss/values";
|
|
1093
|
+
|
|
1094
|
+
// src/paths/predicates.ts
|
|
1095
|
+
var OPERATORS = {
|
|
1096
|
+
"==": "eq",
|
|
1097
|
+
"!=": "neq",
|
|
1098
|
+
">": "gt",
|
|
1099
|
+
">=": "gte",
|
|
1100
|
+
"<": "lt",
|
|
1101
|
+
"<=": "lte"
|
|
1102
|
+
};
|
|
1103
|
+
var CONVERSIONS = /* @__PURE__ */ new Set(["to_i", "to_s", "to_sym"]);
|
|
1104
|
+
var opaqueOf = (node) => ({
|
|
1105
|
+
type: "opaque",
|
|
1106
|
+
sourceText: node.text,
|
|
1107
|
+
reason: "complexExpression"
|
|
1108
|
+
});
|
|
1109
|
+
function predicateOf(node) {
|
|
1110
|
+
if (node.type === "parenthesized_statements") {
|
|
1111
|
+
const inner = node.namedChildren[0];
|
|
1112
|
+
return inner == null ? opaqueOf(node) : predicateOf(inner);
|
|
1113
|
+
}
|
|
1114
|
+
if (node.type === "unary" && field(node, "operator")?.text === "!") {
|
|
1115
|
+
const operand2 = field(node, "operand") ?? node.namedChildren[0];
|
|
1116
|
+
return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2) };
|
|
1117
|
+
}
|
|
1118
|
+
if (node.type === "binary") {
|
|
1119
|
+
return binaryOf(node);
|
|
1120
|
+
}
|
|
1121
|
+
const asked = calledMethod(node);
|
|
1122
|
+
if (asked === "nil?") {
|
|
1123
|
+
const receiver = field(node, "receiver");
|
|
1124
|
+
return receiver === null ? opaqueOf(node) : { type: "nullCheck", subject: valueRefOf(receiver), negated: false };
|
|
1125
|
+
}
|
|
1126
|
+
if (node.type === "identifier" || memberChain(node) !== null) {
|
|
1127
|
+
return {
|
|
1128
|
+
type: "truthinessCheck",
|
|
1129
|
+
subject: valueRefOf(node),
|
|
1130
|
+
negated: false
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return opaqueOf(node);
|
|
1134
|
+
}
|
|
1135
|
+
function binaryOf(node) {
|
|
1136
|
+
const operator = field(node, "operator")?.text ?? "";
|
|
1137
|
+
const left = field(node, "left");
|
|
1138
|
+
const right = field(node, "right");
|
|
1139
|
+
if (left === null || right === null) {
|
|
1140
|
+
return opaqueOf(node);
|
|
1141
|
+
}
|
|
1142
|
+
const op = OPERATORS[operator];
|
|
1143
|
+
if (op !== void 0) {
|
|
1144
|
+
return {
|
|
1145
|
+
type: "comparison",
|
|
1146
|
+
left: valueRefOf(left),
|
|
1147
|
+
op,
|
|
1148
|
+
right: valueRefOf(right)
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
if (operator === "&&" || operator === "||" || operator === "and" || operator === "or") {
|
|
1152
|
+
return {
|
|
1153
|
+
type: "compound",
|
|
1154
|
+
op: operator === "&&" || operator === "and" ? "and" : "or",
|
|
1155
|
+
operands: [predicateOf(left), predicateOf(right)]
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
return opaqueOf(node);
|
|
1159
|
+
}
|
|
1160
|
+
function valueRefOf(node) {
|
|
1161
|
+
const literal = literalOf(node);
|
|
1162
|
+
if (literal !== null) {
|
|
1163
|
+
return literal;
|
|
1164
|
+
}
|
|
1165
|
+
const chain = memberChain(node);
|
|
1166
|
+
if (chain !== null) {
|
|
1167
|
+
const [name, ...accessChain] = chain;
|
|
1168
|
+
if (name !== void 0 && accessChain.length > 0) {
|
|
1169
|
+
return { type: "dependency", name, accessChain };
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return { type: "unresolved", sourceText: node.text };
|
|
1173
|
+
}
|
|
1174
|
+
function literalOf(node) {
|
|
1175
|
+
if (node.type === "integer") {
|
|
1176
|
+
const value = Number.parseInt(node.text, 10);
|
|
1177
|
+
return Number.isNaN(value) ? null : { type: "literal", value };
|
|
1178
|
+
}
|
|
1179
|
+
const string = stringLiteralValue(node);
|
|
1180
|
+
if (string !== null) {
|
|
1181
|
+
return { type: "literal", value: string };
|
|
1182
|
+
}
|
|
1183
|
+
const boolean = booleanLiteralValue(node);
|
|
1184
|
+
return boolean === null ? null : { type: "literal", value: boolean };
|
|
1185
|
+
}
|
|
1186
|
+
function memberChain(node, tail = []) {
|
|
1187
|
+
if (node.type === "identifier") {
|
|
1188
|
+
return [node.text, ...tail];
|
|
1189
|
+
}
|
|
1190
|
+
const called = calledMethod(node);
|
|
1191
|
+
const receiver = field(node, "receiver");
|
|
1192
|
+
if (called === null || receiver === null || field(node, "arguments") !== null) {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
const rest = CONVERSIONS.has(called) && tail.length === 0 ? tail : [called, ...tail];
|
|
1196
|
+
return memberChain(receiver, rest);
|
|
1197
|
+
}
|
|
1198
|
+
function calledMethod(node) {
|
|
1199
|
+
if (node.type !== "call" || field(node, "block") !== null) {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
return field(node, "method")?.text ?? null;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1023
1205
|
// src/values/evaluator.ts
|
|
1024
1206
|
import { nodeOfKey } from "@suss/resolution";
|
|
1025
|
-
import { Evaluator, force, literalOf } from "@suss/values";
|
|
1207
|
+
import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
|
|
1026
1208
|
|
|
1027
1209
|
// src/facts/resolve.ts
|
|
1028
1210
|
import {
|
|
@@ -1088,7 +1270,7 @@ function paramNameOf(param) {
|
|
|
1088
1270
|
function declaredParamNames(method) {
|
|
1089
1271
|
const params = field(method, "parameters");
|
|
1090
1272
|
const declared = /* @__PURE__ */ new Set();
|
|
1091
|
-
for (const param of params === null ? [] :
|
|
1273
|
+
for (const param of params === null ? [] : children2(params)) {
|
|
1092
1274
|
const name = paramNameOf(param);
|
|
1093
1275
|
if (name !== null) {
|
|
1094
1276
|
declared.add(name.text);
|
|
@@ -1102,7 +1284,7 @@ function readKey(filePath, node, enclosing) {
|
|
|
1102
1284
|
}
|
|
1103
1285
|
const parent = node.parent;
|
|
1104
1286
|
if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
|
|
1105
|
-
return
|
|
1287
|
+
return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
|
|
1106
1288
|
}
|
|
1107
1289
|
if (enclosing !== null && declaredParamNames(enclosing).has(node.text)) {
|
|
1108
1290
|
return `${nodeId(filePath, enclosing)}#${node.text}`;
|
|
@@ -1130,7 +1312,7 @@ var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
|
1130
1312
|
"parenthesized_statements"
|
|
1131
1313
|
]);
|
|
1132
1314
|
var ARRAY_TYPES = /* @__PURE__ */ new Set(["array", "string_array", "symbol_array"]);
|
|
1133
|
-
function
|
|
1315
|
+
function children2(node) {
|
|
1134
1316
|
return node.namedChildren.filter((child) => child !== null);
|
|
1135
1317
|
}
|
|
1136
1318
|
function add(emitter, relation, ...tuple) {
|
|
@@ -1158,8 +1340,8 @@ function pairKeyText(key) {
|
|
|
1158
1340
|
}
|
|
1159
1341
|
return null;
|
|
1160
1342
|
}
|
|
1161
|
-
function
|
|
1162
|
-
return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null &&
|
|
1343
|
+
function isPropertyRead2(node) {
|
|
1344
|
+
return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null && children2(node).every(
|
|
1163
1345
|
(child) => child.type !== "do_block" && child.type !== "block"
|
|
1164
1346
|
);
|
|
1165
1347
|
}
|
|
@@ -1198,7 +1380,7 @@ function emitCall(emitter, call) {
|
|
|
1198
1380
|
}
|
|
1199
1381
|
const args = field(call, "arguments");
|
|
1200
1382
|
let position = 0;
|
|
1201
|
-
for (const argument of args === null ? [] :
|
|
1383
|
+
for (const argument of args === null ? [] : children2(args)) {
|
|
1202
1384
|
if (argument.type === "pair") {
|
|
1203
1385
|
const key = field(argument, "key");
|
|
1204
1386
|
const value = field(argument, "value");
|
|
@@ -1228,7 +1410,7 @@ function emitArray(emitter, array) {
|
|
|
1228
1410
|
const objectKey = nodeId(emitter.filePath, array);
|
|
1229
1411
|
add(emitter, "objectValue", objectKey);
|
|
1230
1412
|
let position = 0;
|
|
1231
|
-
for (const element of
|
|
1413
|
+
for (const element of children2(array)) {
|
|
1232
1414
|
add(
|
|
1233
1415
|
emitter,
|
|
1234
1416
|
"holdsProperty",
|
|
@@ -1242,7 +1424,7 @@ function emitArray(emitter, array) {
|
|
|
1242
1424
|
function emitHash(emitter, hash) {
|
|
1243
1425
|
const objectKey = nodeId(emitter.filePath, hash);
|
|
1244
1426
|
add(emitter, "objectValue", objectKey);
|
|
1245
|
-
for (const pair of
|
|
1427
|
+
for (const pair of children2(hash)) {
|
|
1246
1428
|
if (pair.type !== "pair") {
|
|
1247
1429
|
continue;
|
|
1248
1430
|
}
|
|
@@ -1256,7 +1438,7 @@ function emitHash(emitter, hash) {
|
|
|
1256
1438
|
}
|
|
1257
1439
|
}
|
|
1258
1440
|
function walkExpressions(node, visit) {
|
|
1259
|
-
for (const child of
|
|
1441
|
+
for (const child of children2(node)) {
|
|
1260
1442
|
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1261
1443
|
continue;
|
|
1262
1444
|
}
|
|
@@ -1266,7 +1448,7 @@ function walkExpressions(node, visit) {
|
|
|
1266
1448
|
}
|
|
1267
1449
|
function emitExpressionFacts(emitter, node) {
|
|
1268
1450
|
walkExpressions(node, (child) => {
|
|
1269
|
-
if (
|
|
1451
|
+
if (isPropertyRead2(child)) {
|
|
1270
1452
|
emitPropertyRead(emitter, child);
|
|
1271
1453
|
} else if (child.type === "call") {
|
|
1272
1454
|
emitCall(emitter, child);
|
|
@@ -1286,7 +1468,7 @@ function emitExpressionFacts(emitter, node) {
|
|
|
1286
1468
|
});
|
|
1287
1469
|
}
|
|
1288
1470
|
function implicitReturn(body) {
|
|
1289
|
-
const statements =
|
|
1471
|
+
const statements = children2(body).filter(
|
|
1290
1472
|
(child) => child.type !== "rescue" && child.type !== "ensure"
|
|
1291
1473
|
);
|
|
1292
1474
|
const last = statements[statements.length - 1];
|
|
@@ -1298,7 +1480,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1298
1480
|
const params = field(method, "parameters");
|
|
1299
1481
|
const declared = /* @__PURE__ */ new Set();
|
|
1300
1482
|
let position = 0;
|
|
1301
|
-
for (const param of params === null ? [] :
|
|
1483
|
+
for (const param of params === null ? [] : children2(params)) {
|
|
1302
1484
|
const paramName = paramNameOf(param);
|
|
1303
1485
|
if (paramName !== null) {
|
|
1304
1486
|
declared.add(paramName.text);
|
|
@@ -1317,7 +1499,7 @@ function emitMethodFacts(emitter, method) {
|
|
|
1317
1499
|
return funcKey;
|
|
1318
1500
|
}
|
|
1319
1501
|
const recordNested = (node) => {
|
|
1320
|
-
for (const child of
|
|
1502
|
+
for (const child of children2(node)) {
|
|
1321
1503
|
if (OWN_BODY_TYPES.has(child.type)) {
|
|
1322
1504
|
add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
|
|
1323
1505
|
continue;
|
|
@@ -1328,8 +1510,8 @@ function emitMethodFacts(emitter, method) {
|
|
|
1328
1510
|
recordNested(body);
|
|
1329
1511
|
walkExpressions(body, (child) => {
|
|
1330
1512
|
if (child.type === "return") {
|
|
1331
|
-
const first =
|
|
1332
|
-
const returned = first?.type === "argument_list" ?
|
|
1513
|
+
const first = children2(child)[0];
|
|
1514
|
+
const returned = first?.type === "argument_list" ? children2(first)[0] : first;
|
|
1333
1515
|
if (returned !== void 0) {
|
|
1334
1516
|
add(inside, "returnsValue", funcKey, valueKey(inside, returned));
|
|
1335
1517
|
}
|
|
@@ -1373,13 +1555,13 @@ function emitClassFacts(emitter, cls) {
|
|
|
1373
1555
|
const classKey = nodeId(emitter.filePath, cls);
|
|
1374
1556
|
add(emitter, "objectValue", classKey);
|
|
1375
1557
|
const superclass = field(cls, "superclass");
|
|
1376
|
-
const base = superclass === null ? null :
|
|
1558
|
+
const base = superclass === null ? null : children2(superclass)[0] ?? null;
|
|
1377
1559
|
if (base !== null) {
|
|
1378
1560
|
add(emitter, "extends", classKey, valueKey(emitter, base));
|
|
1379
1561
|
add(emitter, "extendsNamed", classKey, base.text);
|
|
1380
1562
|
}
|
|
1381
1563
|
const body = field(cls, "body");
|
|
1382
|
-
for (const statement of body === null ? [] :
|
|
1564
|
+
for (const statement of body === null ? [] : children2(body)) {
|
|
1383
1565
|
if (statement.type === "assignment") {
|
|
1384
1566
|
const left = field(statement, "left");
|
|
1385
1567
|
const right = field(statement, "right");
|
|
@@ -1421,7 +1603,7 @@ function emitValueFacts(db, filePath, root) {
|
|
|
1421
1603
|
add(emitter, "exportsAs", filePath, name.text, key);
|
|
1422
1604
|
};
|
|
1423
1605
|
const walk = (node) => {
|
|
1424
|
-
for (const child of
|
|
1606
|
+
for (const child of children2(node)) {
|
|
1425
1607
|
if (NESTING_TYPES.has(child.type)) {
|
|
1426
1608
|
declaresName(child, emitClassFacts(emitter, child));
|
|
1427
1609
|
continue;
|
|
@@ -1452,7 +1634,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
|
|
|
1452
1634
|
"module",
|
|
1453
1635
|
"singleton_class"
|
|
1454
1636
|
]);
|
|
1455
|
-
var
|
|
1637
|
+
var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
|
|
1456
1638
|
var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
|
|
1457
1639
|
var NAME_TYPES = /* @__PURE__ */ new Set([
|
|
1458
1640
|
"identifier",
|
|
@@ -1958,7 +2140,7 @@ function isTailExpression(node) {
|
|
|
1958
2140
|
if (owner.type === "block" && owner.parent?.type === "lambda") {
|
|
1959
2141
|
return true;
|
|
1960
2142
|
}
|
|
1961
|
-
if (METHOD_TYPES2.has(owner.type) ||
|
|
2143
|
+
if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES2.has(owner.type)) {
|
|
1962
2144
|
return true;
|
|
1963
2145
|
}
|
|
1964
2146
|
if (owner.type === "when") {
|
|
@@ -1977,7 +2159,7 @@ function isRoot(node) {
|
|
|
1977
2159
|
if (OWN_ROOT_TYPES.has(node.type)) {
|
|
1978
2160
|
return true;
|
|
1979
2161
|
}
|
|
1980
|
-
return
|
|
2162
|
+
return BLOCK_TYPES2.has(node.type) && node.parent?.type === "call";
|
|
1981
2163
|
}
|
|
1982
2164
|
function isPathStatement(node, parent) {
|
|
1983
2165
|
if (node.type === "elsif") {
|
|
@@ -2031,7 +2213,7 @@ function functionOf(node) {
|
|
|
2031
2213
|
body: bodyOf(block)
|
|
2032
2214
|
};
|
|
2033
2215
|
}
|
|
2034
|
-
if (
|
|
2216
|
+
if (BLOCK_TYPES2.has(node.type)) {
|
|
2035
2217
|
return {
|
|
2036
2218
|
parameters: parametersOf(field(node, "parameters")),
|
|
2037
2219
|
body: bodyOf(node)
|
|
@@ -2367,6 +2549,18 @@ var calleeRows = [
|
|
|
2367
2549
|
kind: "callee",
|
|
2368
2550
|
origin: { module: "Kernel", name: "String" },
|
|
2369
2551
|
apply: ({ args }) => ({ result: concat([operand(args[0])]) })
|
|
2552
|
+
},
|
|
2553
|
+
// A URI is the string it was parsed from, as far as a reader of paths
|
|
2554
|
+
// is concerned, and both spellings are the standard library's own.
|
|
2555
|
+
{
|
|
2556
|
+
kind: "callee",
|
|
2557
|
+
origin: { module: "Kernel", name: "URI" },
|
|
2558
|
+
apply: ({ args }) => ({ result: concat([operand(args[0])]) })
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
kind: "callee",
|
|
2562
|
+
origin: { module: "URI", name: "parse" },
|
|
2563
|
+
apply: ({ args }) => ({ result: concat([operand(args[0])]) })
|
|
2370
2564
|
}
|
|
2371
2565
|
];
|
|
2372
2566
|
var rubyRows = [
|
|
@@ -2392,7 +2586,7 @@ function evaluatedValue(node, db) {
|
|
|
2392
2586
|
return force(evaluatorFor(node, db).evaluate(node));
|
|
2393
2587
|
}
|
|
2394
2588
|
function stringValueOf(node, db) {
|
|
2395
|
-
return
|
|
2589
|
+
return literalOf2(evaluatedValue(node, db));
|
|
2396
2590
|
}
|
|
2397
2591
|
function methodDefinitionsIn(file, root) {
|
|
2398
2592
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -2485,7 +2679,7 @@ function statusNumberOf(node, names) {
|
|
|
2485
2679
|
if (typeof constant2 === "number") {
|
|
2486
2680
|
return constant2;
|
|
2487
2681
|
}
|
|
2488
|
-
const name =
|
|
2682
|
+
const name = literalOf3(value);
|
|
2489
2683
|
return name === null ? null : names[name] ?? null;
|
|
2490
2684
|
}
|
|
2491
2685
|
function declarationsByName(declarations) {
|
|
@@ -2554,7 +2748,7 @@ function effectsReaching(effects, conditions) {
|
|
|
2554
2748
|
function conditionOf(condition) {
|
|
2555
2749
|
return {
|
|
2556
2750
|
sourceText: condition.sourceText,
|
|
2557
|
-
structured: null,
|
|
2751
|
+
structured: condition.expression === null ? null : predicateOf(condition.expression),
|
|
2558
2752
|
polarity: condition.polarity,
|
|
2559
2753
|
source: condition.source
|
|
2560
2754
|
};
|
|
@@ -2608,55 +2802,414 @@ function handsOnBranch(outcome, effects, extraEffects) {
|
|
|
2608
2802
|
isDefault: outcome.conditions.length === 0
|
|
2609
2803
|
};
|
|
2610
2804
|
}
|
|
2611
|
-
function
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
|
|
2805
|
+
function returnPathBranches(method, effects) {
|
|
2806
|
+
const body = field(method, "body");
|
|
2807
|
+
if (body === null) {
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
const returns = collectReturns(body, []);
|
|
2811
|
+
const lowered = lowerRubyBody(body, returns, []);
|
|
2812
|
+
const enumerated = enumerateOrDegrade2(
|
|
2813
|
+
{
|
|
2814
|
+
statements: lowered.statements,
|
|
2815
|
+
terminalsByStmt: lowered.terminalsByStmt
|
|
2816
|
+
},
|
|
2817
|
+
returns
|
|
2818
|
+
);
|
|
2819
|
+
const branches = [];
|
|
2820
|
+
const push = (paths, at) => {
|
|
2821
|
+
for (const path7 of paths) {
|
|
2822
|
+
const conditions = path7.map(conditionOf);
|
|
2823
|
+
branches.push({
|
|
2824
|
+
conditions,
|
|
2825
|
+
terminal: {
|
|
2826
|
+
kind: "return",
|
|
2827
|
+
statusCode: null,
|
|
2828
|
+
body: null,
|
|
2829
|
+
exceptionType: null,
|
|
2830
|
+
message: null,
|
|
2831
|
+
component: null,
|
|
2832
|
+
renderTree: null,
|
|
2833
|
+
delegateTarget: null,
|
|
2834
|
+
emitEvent: null,
|
|
2835
|
+
location: at
|
|
2836
|
+
},
|
|
2837
|
+
effects: effectsReaching(effects, conditions),
|
|
2838
|
+
location: at,
|
|
2839
|
+
isDefault: conditions.length === 0
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
};
|
|
2843
|
+
for (const statement of returns) {
|
|
2844
|
+
push(enumerated.byTerminal.get(statement) ?? [], rangeOf(statement));
|
|
2845
|
+
}
|
|
2846
|
+
push(enumerated.fallthrough, rangeOf(method));
|
|
2847
|
+
return branches.length === 0 ? null : branches;
|
|
2848
|
+
}
|
|
2849
|
+
function responseBranches(method, pattern, effects, extraEffects, options = {}) {
|
|
2850
|
+
const declarations = pattern.responseStatusCalls ?? [];
|
|
2851
|
+
const body = field(method, "body");
|
|
2852
|
+
if (declarations.length === 0 || body === null) {
|
|
2853
|
+
return null;
|
|
2854
|
+
}
|
|
2855
|
+
const byName = declarationsByName(declarations);
|
|
2856
|
+
const locals = localNamesIn(method);
|
|
2857
|
+
const responses = collectResponseCalls(body, byName, locals, []);
|
|
2858
|
+
const returns = collectReturns(body, []);
|
|
2859
|
+
const lowered = lowerRubyBody(body, returns, responses);
|
|
2860
|
+
const bareReturns = returns.filter(
|
|
2861
|
+
(node) => lowered.terminalHome.has(node) && collectResponseCalls(node, byName, locals, []).length === 0
|
|
2862
|
+
);
|
|
2863
|
+
const terminals = [...responses, ...bareReturns];
|
|
2864
|
+
const enumerated = enumerateOrDegrade2(
|
|
2865
|
+
{
|
|
2866
|
+
statements: lowered.statements,
|
|
2867
|
+
terminalsByStmt: lowered.terminalsByStmt
|
|
2868
|
+
},
|
|
2869
|
+
terminals
|
|
2870
|
+
);
|
|
2871
|
+
const statusNames = pattern.statusCodeNames ?? {};
|
|
2872
|
+
const outcomes = [];
|
|
2873
|
+
for (const terminal of terminals) {
|
|
2874
|
+
const declaration = declarationOf(terminal, byName, locals);
|
|
2875
|
+
const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames);
|
|
2876
|
+
for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
|
|
2877
|
+
outcomes.push({
|
|
2878
|
+
conditions: path7.map(conditionOf),
|
|
2879
|
+
reading,
|
|
2880
|
+
location: rangeOf(terminal)
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
for (const path7 of enumerated.fallthrough) {
|
|
2885
|
+
outcomes.push({
|
|
2886
|
+
conditions: path7.map(conditionOf),
|
|
2887
|
+
reading: absentReading,
|
|
2888
|
+
location: rangeOf(method),
|
|
2889
|
+
fellThrough: true
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
if (outcomes.length === 0) {
|
|
2893
|
+
return null;
|
|
2894
|
+
}
|
|
2895
|
+
return outcomes.map(
|
|
2896
|
+
(outcome) => branchOf(outcome, pattern, effects, extraEffects, options)
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
// src/clientCalls.ts
|
|
2901
|
+
function clientCallUnits(root, pack, pattern, options) {
|
|
2902
|
+
const units = [];
|
|
2903
|
+
for (const method of methodDefinitions(root)) {
|
|
2904
|
+
const name = field(method, "name")?.text;
|
|
2905
|
+
if (name === void 0) {
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
for (const call of callsUnder(method)) {
|
|
2909
|
+
const request = requestCall(call, method, pattern, options);
|
|
2910
|
+
if (request === null) {
|
|
2911
|
+
continue;
|
|
2912
|
+
}
|
|
2913
|
+
units.push(clientUnit(method, name, request, pattern, pack, options));
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
return units;
|
|
2917
|
+
}
|
|
2918
|
+
function methodDefinitions(node, found = []) {
|
|
2919
|
+
for (const child of node.namedChildren) {
|
|
2920
|
+
if (child === null) {
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
if (child.type === "method" || child.type === "singleton_method") {
|
|
2924
|
+
found.push(child);
|
|
2925
|
+
}
|
|
2926
|
+
methodDefinitions(child, found);
|
|
2927
|
+
}
|
|
2928
|
+
return found;
|
|
2929
|
+
}
|
|
2930
|
+
function callsUnder(node, found = []) {
|
|
2931
|
+
for (const child of node.namedChildren) {
|
|
2932
|
+
if (child === null) {
|
|
2933
|
+
continue;
|
|
2934
|
+
}
|
|
2935
|
+
if (child.type === "call") {
|
|
2936
|
+
found.push(child);
|
|
2937
|
+
}
|
|
2938
|
+
callsUnder(child, found);
|
|
2939
|
+
}
|
|
2940
|
+
return found;
|
|
2941
|
+
}
|
|
2942
|
+
function requestCall(call, method, pattern, options) {
|
|
2943
|
+
const receiver = field(call, "receiver");
|
|
2944
|
+
const called = field(call, "method")?.text;
|
|
2945
|
+
if (receiver === null || called === void 0) {
|
|
2946
|
+
return null;
|
|
2947
|
+
}
|
|
2948
|
+
const prefix = receiverPrefix(receiver, method, pattern, options);
|
|
2949
|
+
if (prefix === null) {
|
|
2950
|
+
return null;
|
|
2951
|
+
}
|
|
2952
|
+
const args = readCallArgs(field(call, "arguments"));
|
|
2953
|
+
const verb = pattern.verbMethodNames[called];
|
|
2954
|
+
if (verb !== void 0) {
|
|
2955
|
+
const path8 = urlIn(args, pattern, options);
|
|
2956
|
+
return path8 === null ? null : { method: verb, path: prefix + path8 };
|
|
2957
|
+
}
|
|
2958
|
+
const sent = pattern.requestObject;
|
|
2959
|
+
if (sent === void 0 || sent.attribute !== called) {
|
|
2960
|
+
return null;
|
|
2961
|
+
}
|
|
2962
|
+
const built = requestBuilt(args.positional[0], method, sent);
|
|
2963
|
+
if (built === null) {
|
|
2964
|
+
return null;
|
|
2965
|
+
}
|
|
2966
|
+
const path7 = pathAt(built.url, options);
|
|
2967
|
+
return path7 === null ? null : { method: built.method, path: prefix + path7 };
|
|
2968
|
+
}
|
|
2969
|
+
function requestBuilt(argument, method, sent) {
|
|
2970
|
+
const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
|
|
2971
|
+
if (written === null || written.type !== "call") {
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
const requestClass = field(written, "receiver")?.text;
|
|
2975
|
+
const verb = requestClass === void 0 ? void 0 : sent.constructors[requestClass];
|
|
2976
|
+
if (verb === void 0) {
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
const args = readCallArgs(field(written, "arguments"));
|
|
2980
|
+
return { method: verb, url: args.positional[sent.urlPosition] };
|
|
2981
|
+
}
|
|
2982
|
+
function assignedCall(node, method) {
|
|
2983
|
+
if (node.type !== "identifier") {
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
const value = assignedValue(node.text, method);
|
|
2987
|
+
return value !== null && value.type === "call" ? value : null;
|
|
2988
|
+
}
|
|
2989
|
+
function receiverPrefix(receiver, method, pattern, options) {
|
|
2990
|
+
if (namesConstant(receiver, pattern.constantName)) {
|
|
2991
|
+
return "";
|
|
2992
|
+
}
|
|
2993
|
+
if (receiver.type !== "identifier") {
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
const built = builtBy(receiver.text, method, pattern);
|
|
2997
|
+
if (built === null) {
|
|
2998
|
+
return null;
|
|
2999
|
+
}
|
|
3000
|
+
const keyword = pattern.builderUrlKeyword;
|
|
3001
|
+
if (keyword === void 0) {
|
|
3002
|
+
return "";
|
|
3003
|
+
}
|
|
3004
|
+
const base = readCallArgs(field(built, "arguments")).keyword[keyword];
|
|
3005
|
+
const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts));
|
|
3006
|
+
return path7 === void 0 || path7 === null ? "" : trimmed(path7);
|
|
3007
|
+
}
|
|
3008
|
+
function trimmed(path7) {
|
|
3009
|
+
return path7 === "/" ? "" : path7.replace(/\/+$/, "");
|
|
3010
|
+
}
|
|
3011
|
+
function namesConstant(receiver, constantName) {
|
|
3012
|
+
return (receiver.type === "constant" || receiver.type === "scope_resolution") && receiver.text === constantName;
|
|
3013
|
+
}
|
|
3014
|
+
function builtBy(name, method, pattern) {
|
|
3015
|
+
const builders = pattern.receiverBuilders ?? [];
|
|
3016
|
+
const value = builders.length === 0 ? null : assignedValue(name, method);
|
|
3017
|
+
if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
|
|
3018
|
+
return null;
|
|
3019
|
+
}
|
|
3020
|
+
const receiver = field(value, "receiver");
|
|
3021
|
+
return receiver !== null && namesConstant(receiver, pattern.constantName) ? value : null;
|
|
3022
|
+
}
|
|
3023
|
+
function assignedValue(name, method) {
|
|
3024
|
+
const body = field(method, "body");
|
|
3025
|
+
if (body === null) {
|
|
3026
|
+
return null;
|
|
3027
|
+
}
|
|
3028
|
+
for (const statement of runStatements(body)) {
|
|
3029
|
+
if (statement.type !== "assignment") {
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
if (field(statement, "left")?.text === name) {
|
|
3033
|
+
return field(statement, "right");
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
function urlIn(args, pattern, options) {
|
|
3039
|
+
const keyword = pattern.url.keyword;
|
|
3040
|
+
const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
|
|
3041
|
+
return pathAt(written, options);
|
|
3042
|
+
}
|
|
3043
|
+
function pathAt(written, options) {
|
|
3044
|
+
if (written === void 0) {
|
|
3045
|
+
return null;
|
|
3046
|
+
}
|
|
3047
|
+
const path7 = pathOf(evaluatedValue(written, options.facts));
|
|
3048
|
+
return path7 === void 0 || namesNothing(path7) ? null : path7;
|
|
3049
|
+
}
|
|
3050
|
+
function clientUnit(method, name, request, pattern, pack, options) {
|
|
3051
|
+
const range = rangeOf(method);
|
|
3052
|
+
return {
|
|
3053
|
+
identity: {
|
|
3054
|
+
name,
|
|
3055
|
+
nameKind: "binding",
|
|
3056
|
+
kind: "client",
|
|
3057
|
+
file: options.filePath,
|
|
3058
|
+
range,
|
|
3059
|
+
span: spanOf(method),
|
|
3060
|
+
exportName: name,
|
|
3061
|
+
exportPath: [name]
|
|
3062
|
+
},
|
|
3063
|
+
boundaryBinding: restBinding({
|
|
3064
|
+
transport: pack.protocol,
|
|
3065
|
+
method: request.method,
|
|
3066
|
+
path: request.path,
|
|
3067
|
+
recognition: pack.name
|
|
3068
|
+
}),
|
|
3069
|
+
parameters: [],
|
|
3070
|
+
branches: callerBranches(method, range),
|
|
3071
|
+
...responseAccessors(pattern),
|
|
3072
|
+
bodyContent: "statements",
|
|
3073
|
+
dependencyCalls: [],
|
|
3074
|
+
declaredContract: null
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
function callerBranches(method, range) {
|
|
3078
|
+
const effects = invocationEffects(method);
|
|
3079
|
+
return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
|
|
3080
|
+
}
|
|
3081
|
+
function responseAccessors(pattern) {
|
|
3082
|
+
const response = pattern.response;
|
|
3083
|
+
if (response === void 0) {
|
|
3084
|
+
return {};
|
|
3085
|
+
}
|
|
3086
|
+
return {
|
|
3087
|
+
...response.body === void 0 ? {} : { bodyAccessors: response.body },
|
|
3088
|
+
...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
|
|
3089
|
+
...response.success === void 0 ? {} : { successAccessors: response.success },
|
|
3090
|
+
...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
function returnBranch(range, effects) {
|
|
3094
|
+
return {
|
|
3095
|
+
conditions: [],
|
|
3096
|
+
terminal: {
|
|
3097
|
+
kind: "return",
|
|
3098
|
+
statusCode: null,
|
|
3099
|
+
body: null,
|
|
3100
|
+
exceptionType: null,
|
|
3101
|
+
message: null,
|
|
3102
|
+
component: null,
|
|
3103
|
+
renderTree: null,
|
|
3104
|
+
delegateTarget: null,
|
|
3105
|
+
emitEvent: null,
|
|
3106
|
+
location: range
|
|
3107
|
+
},
|
|
3108
|
+
effects,
|
|
3109
|
+
location: range,
|
|
3110
|
+
isDefault: true
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// src/envReads.ts
|
|
3115
|
+
import { runtimeConfigBinding } from "@suss/behavioral-ir";
|
|
3116
|
+
import { SKIP_CHILDREN as SKIP_CHILDREN2, walkDescendants as walkDescendants2 } from "@suss/extractor";
|
|
3117
|
+
var RUBY_ENV_RECOGNITION = "ruby-env";
|
|
3118
|
+
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
|
|
3119
|
+
function envReadEffects(root) {
|
|
3120
|
+
const effects = [];
|
|
3121
|
+
walkDescendants2(root, null, {
|
|
3122
|
+
at: (node) => {
|
|
3123
|
+
const read = envReadAt(node);
|
|
3124
|
+
if (read !== null) {
|
|
3125
|
+
effects.push(configReadEffect(read));
|
|
3126
|
+
}
|
|
3127
|
+
},
|
|
3128
|
+
into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? SKIP_CHILDREN2 : null
|
|
3129
|
+
});
|
|
3130
|
+
return effects;
|
|
3131
|
+
}
|
|
3132
|
+
function configReadEffect(read) {
|
|
3133
|
+
return {
|
|
3134
|
+
type: "interaction",
|
|
3135
|
+
binding: runtimeConfigBinding({
|
|
3136
|
+
recognition: RUBY_ENV_RECOGNITION,
|
|
3137
|
+
deploymentTarget: "lambda",
|
|
3138
|
+
instanceName: "<unknown>"
|
|
3139
|
+
}),
|
|
3140
|
+
callee: `ENV["${read.name}"]`,
|
|
3141
|
+
interaction: {
|
|
3142
|
+
class: "config-read",
|
|
3143
|
+
name: read.name,
|
|
3144
|
+
defaulted: read.defaulted
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
function envReadAt(node) {
|
|
3149
|
+
if (node.type === "element_reference") {
|
|
3150
|
+
return elementRead(node);
|
|
3151
|
+
}
|
|
3152
|
+
if (node.type === "call") {
|
|
3153
|
+
return fetchRead(node);
|
|
3154
|
+
}
|
|
3155
|
+
return null;
|
|
3156
|
+
}
|
|
3157
|
+
function elementRead(node) {
|
|
3158
|
+
const object = field(node, "object");
|
|
3159
|
+
if (object === null || !isEnv(object) || isAssignedTo(node)) {
|
|
2615
3160
|
return null;
|
|
2616
3161
|
}
|
|
2617
|
-
const
|
|
2618
|
-
|
|
2619
|
-
const responses = collectResponseCalls(body, byName, locals, []);
|
|
2620
|
-
const returns = collectReturns(body, []);
|
|
2621
|
-
const lowered = lowerRubyBody(body, returns, responses);
|
|
2622
|
-
const bareReturns = returns.filter(
|
|
2623
|
-
(node) => lowered.terminalHome.has(node) && collectResponseCalls(node, byName, locals, []).length === 0
|
|
2624
|
-
);
|
|
2625
|
-
const terminals = [...responses, ...bareReturns];
|
|
2626
|
-
const enumerated = enumerateOrDegrade(
|
|
2627
|
-
{
|
|
2628
|
-
statements: lowered.statements,
|
|
2629
|
-
terminalsByStmt: lowered.terminalsByStmt
|
|
2630
|
-
},
|
|
2631
|
-
terminals
|
|
3162
|
+
const index = node.namedChildren.find(
|
|
3163
|
+
(child) => child !== null && child.id !== object.id
|
|
2632
3164
|
);
|
|
2633
|
-
const
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
const declaration = declarationOf(terminal, byName, locals);
|
|
2637
|
-
const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames);
|
|
2638
|
-
for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
|
|
2639
|
-
outcomes.push({
|
|
2640
|
-
conditions: path7.map(conditionOf),
|
|
2641
|
-
reading,
|
|
2642
|
-
location: rangeOf(terminal)
|
|
2643
|
-
});
|
|
2644
|
-
}
|
|
3165
|
+
const name = index === void 0 ? null : stringLiteralValue(index);
|
|
3166
|
+
if (name === null) {
|
|
3167
|
+
return null;
|
|
2645
3168
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
});
|
|
3169
|
+
return { name, defaulted: isDefaultedAt(node) };
|
|
3170
|
+
}
|
|
3171
|
+
function fetchRead(node) {
|
|
3172
|
+
const receiver = field(node, "receiver");
|
|
3173
|
+
if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
|
|
3174
|
+
return null;
|
|
2653
3175
|
}
|
|
2654
|
-
|
|
3176
|
+
const { positional } = readCallArgs(field(node, "arguments"));
|
|
3177
|
+
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
3178
|
+
if (name === null) {
|
|
2655
3179
|
return null;
|
|
2656
3180
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3181
|
+
const hasDefault = positional.length > 1 || field(node, "block") !== null;
|
|
3182
|
+
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
3183
|
+
}
|
|
3184
|
+
function isEnv(node) {
|
|
3185
|
+
if (node.type === "constant") {
|
|
3186
|
+
return node.text === "ENV";
|
|
3187
|
+
}
|
|
3188
|
+
return node.type === "scope_resolution" && field(node, "scope") === null && field(node, "name")?.text === "ENV";
|
|
3189
|
+
}
|
|
3190
|
+
function isAssignedTo(node) {
|
|
3191
|
+
const parent = node.parent;
|
|
3192
|
+
return parent !== null && (parent.type === "assignment" || parent.type === "operator_assignment") && field(parent, "left")?.id === node.id;
|
|
3193
|
+
}
|
|
3194
|
+
function isDefaultedAt(node) {
|
|
3195
|
+
let child = node;
|
|
3196
|
+
let parent = node.parent;
|
|
3197
|
+
while (parent !== null) {
|
|
3198
|
+
if (parent.type === "parenthesized_statements") {
|
|
3199
|
+
child = parent;
|
|
3200
|
+
parent = parent.parent;
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
if (parent.type !== "binary" || field(parent, "operator")?.text !== "||") {
|
|
3204
|
+
return false;
|
|
3205
|
+
}
|
|
3206
|
+
if (field(parent, "left")?.id === child.id) {
|
|
3207
|
+
return true;
|
|
3208
|
+
}
|
|
3209
|
+
child = parent;
|
|
3210
|
+
parent = parent.parent;
|
|
3211
|
+
}
|
|
3212
|
+
return false;
|
|
2660
3213
|
}
|
|
2661
3214
|
|
|
2662
3215
|
// src/filters.ts
|
|
@@ -2844,172 +3397,6 @@ function namesUnder(node) {
|
|
|
2844
3397
|
return names;
|
|
2845
3398
|
}
|
|
2846
3399
|
|
|
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
|
-
|
|
3013
3400
|
// src/storage.ts
|
|
3014
3401
|
import { deriveOnDemand as deriveOnDemand2, evaluate as evaluate2 } from "@suss/datalog";
|
|
3015
3402
|
import { storageBinding } from "@suss/ir-core";
|
|
@@ -3232,6 +3619,16 @@ async function discoverUnits(root, options) {
|
|
|
3232
3619
|
file: options.absoluteFile ?? options.filePath
|
|
3233
3620
|
}));
|
|
3234
3621
|
const units = [];
|
|
3622
|
+
for (const pack of options.packs) {
|
|
3623
|
+
for (const pattern of pack.clients ?? []) {
|
|
3624
|
+
units.push(
|
|
3625
|
+
...clientCallUnits(root, pack, pattern, {
|
|
3626
|
+
filePath: options.filePath,
|
|
3627
|
+
...options.facts === void 0 ? {} : { facts: options.facts }
|
|
3628
|
+
})
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3235
3632
|
for (const info of classes) {
|
|
3236
3633
|
const ownBlocks = fileBlocks.filter(
|
|
3237
3634
|
(block) => block.info.qualifiedName === info.qualifiedName
|
|
@@ -3388,7 +3785,7 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3388
3785
|
exportName: actionName,
|
|
3389
3786
|
exportPath: [controllerQualifiedName, actionName]
|
|
3390
3787
|
},
|
|
3391
|
-
boundaryBinding: route === null ? null :
|
|
3788
|
+
boundaryBinding: route === null ? null : restBinding2({
|
|
3392
3789
|
transport: pack.protocol,
|
|
3393
3790
|
method: route.method,
|
|
3394
3791
|
path: route.path,
|
|
@@ -3506,7 +3903,7 @@ function bodyOfMethod(method, bodyRead = {}) {
|
|
|
3506
3903
|
const storage = bodyRead.storage;
|
|
3507
3904
|
const extra = [
|
|
3508
3905
|
...envReadEffects(method),
|
|
3509
|
-
...storage === void 0 ? [] : storageEffects(
|
|
3906
|
+
...storage === void 0 ? [] : storageEffects(callsUnder2(method), storage)
|
|
3510
3907
|
];
|
|
3511
3908
|
return {
|
|
3512
3909
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
@@ -3515,7 +3912,7 @@ function bodyOfMethod(method, bodyRead = {}) {
|
|
|
3515
3912
|
...extra.length > 0 ? { extraEffects: extra } : {}
|
|
3516
3913
|
};
|
|
3517
3914
|
}
|
|
3518
|
-
function
|
|
3915
|
+
function callsUnder2(node, found = []) {
|
|
3519
3916
|
for (const child of node.namedChildren) {
|
|
3520
3917
|
if (child === null) {
|
|
3521
3918
|
continue;
|
|
@@ -3523,7 +3920,7 @@ function callsUnder(node, found = []) {
|
|
|
3523
3920
|
if (child.type === "call") {
|
|
3524
3921
|
found.push(child);
|
|
3525
3922
|
}
|
|
3526
|
-
|
|
3923
|
+
callsUnder2(child, found);
|
|
3527
3924
|
}
|
|
3528
3925
|
return found;
|
|
3529
3926
|
}
|
|
@@ -4703,6 +5100,7 @@ async function extractRubyProject(options) {
|
|
|
4703
5100
|
...storagePatterns.length > 0 ? { storage: { facts: db, patterns: storagePatterns } } : {},
|
|
4704
5101
|
inheritedMethods,
|
|
4705
5102
|
displayPathOf,
|
|
5103
|
+
facts: db,
|
|
4706
5104
|
onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
|
|
4707
5105
|
})
|
|
4708
5106
|
);
|