@suss/adapter-ruby 0.22.4 → 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 -248
- package/dist/index.cjs +828 -167
- package/dist/index.d.cts +91 -4
- package/dist/index.d.ts +91 -4
- package/dist/index.js +806 -144
- 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
|
|
@@ -519,110 +531,13 @@ function inheritedStatements(ancestry, blockConfigures) {
|
|
|
519
531
|
import {
|
|
520
532
|
dispatchByType,
|
|
521
533
|
graphqlResolverBinding,
|
|
522
|
-
restBinding
|
|
534
|
+
restBinding as restBinding2
|
|
523
535
|
} from "@suss/behavioral-ir";
|
|
524
536
|
import { absentReading as absentReading2, unreadableReading as unreadableReading2 } from "@suss/extractor";
|
|
525
537
|
|
|
526
|
-
// src/
|
|
527
|
-
import {
|
|
528
|
-
import {
|
|
529
|
-
var RUBY_ENV_RECOGNITION = "ruby-env";
|
|
530
|
-
var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method", "lambda"]);
|
|
531
|
-
function envReadEffects(root) {
|
|
532
|
-
const effects = [];
|
|
533
|
-
walkDescendants2(root, null, {
|
|
534
|
-
at: (node) => {
|
|
535
|
-
const read = envReadAt(node);
|
|
536
|
-
if (read !== null) {
|
|
537
|
-
effects.push(configReadEffect(read));
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
into: (node) => DEFERRED_BODY_TYPES.has(node.type) ? SKIP_CHILDREN2 : null
|
|
541
|
-
});
|
|
542
|
-
return effects;
|
|
543
|
-
}
|
|
544
|
-
function configReadEffect(read) {
|
|
545
|
-
return {
|
|
546
|
-
type: "interaction",
|
|
547
|
-
binding: runtimeConfigBinding({
|
|
548
|
-
recognition: RUBY_ENV_RECOGNITION,
|
|
549
|
-
deploymentTarget: "lambda",
|
|
550
|
-
instanceName: "<unknown>"
|
|
551
|
-
}),
|
|
552
|
-
callee: `ENV["${read.name}"]`,
|
|
553
|
-
interaction: {
|
|
554
|
-
class: "config-read",
|
|
555
|
-
name: read.name,
|
|
556
|
-
defaulted: read.defaulted
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
function envReadAt(node) {
|
|
561
|
-
if (node.type === "element_reference") {
|
|
562
|
-
return elementRead(node);
|
|
563
|
-
}
|
|
564
|
-
if (node.type === "call") {
|
|
565
|
-
return fetchRead(node);
|
|
566
|
-
}
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
function elementRead(node) {
|
|
570
|
-
const object = field(node, "object");
|
|
571
|
-
if (object === null || !isEnv(object) || isAssignedTo(node)) {
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
const index = node.namedChildren.find(
|
|
575
|
-
(child) => child !== null && child.id !== object.id
|
|
576
|
-
);
|
|
577
|
-
const name = index === void 0 ? null : stringLiteralValue(index);
|
|
578
|
-
if (name === null) {
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
return { name, defaulted: isDefaultedAt(node) };
|
|
582
|
-
}
|
|
583
|
-
function fetchRead(node) {
|
|
584
|
-
const receiver = field(node, "receiver");
|
|
585
|
-
if (receiver === null || !isEnv(receiver) || field(node, "method")?.text !== "fetch") {
|
|
586
|
-
return null;
|
|
587
|
-
}
|
|
588
|
-
const { positional } = readCallArgs(field(node, "arguments"));
|
|
589
|
-
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
590
|
-
if (name === null) {
|
|
591
|
-
return null;
|
|
592
|
-
}
|
|
593
|
-
const hasDefault = positional.length > 1 || field(node, "block") !== null;
|
|
594
|
-
return { name, defaulted: hasDefault || isDefaultedAt(node) };
|
|
595
|
-
}
|
|
596
|
-
function isEnv(node) {
|
|
597
|
-
if (node.type === "constant") {
|
|
598
|
-
return node.text === "ENV";
|
|
599
|
-
}
|
|
600
|
-
return node.type === "scope_resolution" && field(node, "scope") === null && field(node, "name")?.text === "ENV";
|
|
601
|
-
}
|
|
602
|
-
function isAssignedTo(node) {
|
|
603
|
-
const parent = node.parent;
|
|
604
|
-
return parent !== null && (parent.type === "assignment" || parent.type === "operator_assignment") && field(parent, "left")?.id === node.id;
|
|
605
|
-
}
|
|
606
|
-
function isDefaultedAt(node) {
|
|
607
|
-
let child = node;
|
|
608
|
-
let parent = node.parent;
|
|
609
|
-
while (parent !== null) {
|
|
610
|
-
if (parent.type === "parenthesized_statements") {
|
|
611
|
-
child = parent;
|
|
612
|
-
parent = parent.parent;
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
if (parent.type !== "binary" || field(parent, "operator")?.text !== "||") {
|
|
616
|
-
return false;
|
|
617
|
-
}
|
|
618
|
-
if (field(parent, "left")?.id === child.id) {
|
|
619
|
-
return true;
|
|
620
|
-
}
|
|
621
|
-
child = parent;
|
|
622
|
-
parent = parent.parent;
|
|
623
|
-
}
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
538
|
+
// src/clientCalls.ts
|
|
539
|
+
import { namesNothing, restBinding } from "@suss/behavioral-ir";
|
|
540
|
+
import { pathOf } from "@suss/values";
|
|
626
541
|
|
|
627
542
|
// src/paths/effects.ts
|
|
628
543
|
import { enumerateOrDegrade, sharedGatingConditions } from "@suss/extractor";
|
|
@@ -1174,11 +1089,122 @@ import {
|
|
|
1174
1089
|
unreadableReading,
|
|
1175
1090
|
writtenReading
|
|
1176
1091
|
} from "@suss/extractor";
|
|
1177
|
-
import { constantOf, literalOf as
|
|
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
|
+
}
|
|
1178
1204
|
|
|
1179
1205
|
// src/values/evaluator.ts
|
|
1180
1206
|
import { nodeOfKey } from "@suss/resolution";
|
|
1181
|
-
import { Evaluator, force, literalOf } from "@suss/values";
|
|
1207
|
+
import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
|
|
1182
1208
|
|
|
1183
1209
|
// src/facts/resolve.ts
|
|
1184
1210
|
import {
|
|
@@ -2523,6 +2549,18 @@ var calleeRows = [
|
|
|
2523
2549
|
kind: "callee",
|
|
2524
2550
|
origin: { module: "Kernel", name: "String" },
|
|
2525
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])]) })
|
|
2526
2564
|
}
|
|
2527
2565
|
];
|
|
2528
2566
|
var rubyRows = [
|
|
@@ -2548,7 +2586,7 @@ function evaluatedValue(node, db) {
|
|
|
2548
2586
|
return force(evaluatorFor(node, db).evaluate(node));
|
|
2549
2587
|
}
|
|
2550
2588
|
function stringValueOf(node, db) {
|
|
2551
|
-
return
|
|
2589
|
+
return literalOf2(evaluatedValue(node, db));
|
|
2552
2590
|
}
|
|
2553
2591
|
function methodDefinitionsIn(file, root) {
|
|
2554
2592
|
const found = /* @__PURE__ */ new Map();
|
|
@@ -2641,7 +2679,7 @@ function statusNumberOf(node, names) {
|
|
|
2641
2679
|
if (typeof constant2 === "number") {
|
|
2642
2680
|
return constant2;
|
|
2643
2681
|
}
|
|
2644
|
-
const name =
|
|
2682
|
+
const name = literalOf3(value);
|
|
2645
2683
|
return name === null ? null : names[name] ?? null;
|
|
2646
2684
|
}
|
|
2647
2685
|
function declarationsByName(declarations) {
|
|
@@ -2710,12 +2748,15 @@ function effectsReaching(effects, conditions) {
|
|
|
2710
2748
|
function conditionOf(condition) {
|
|
2711
2749
|
return {
|
|
2712
2750
|
sourceText: condition.sourceText,
|
|
2713
|
-
structured: null,
|
|
2751
|
+
structured: condition.expression === null ? null : predicateOf(condition.expression),
|
|
2714
2752
|
polarity: condition.polarity,
|
|
2715
2753
|
source: condition.source
|
|
2716
2754
|
};
|
|
2717
2755
|
}
|
|
2718
|
-
function branchOf(outcome, pattern, effects, extraEffects) {
|
|
2756
|
+
function branchOf(outcome, pattern, effects, extraEffects, options) {
|
|
2757
|
+
if (outcome.fellThrough === true && options.fallthrough === "handOn") {
|
|
2758
|
+
return handsOnBranch(outcome, effects, extraEffects);
|
|
2759
|
+
}
|
|
2719
2760
|
return {
|
|
2720
2761
|
conditions: outcome.conditions,
|
|
2721
2762
|
terminal: {
|
|
@@ -2740,7 +2781,72 @@ function branchOf(outcome, pattern, effects, extraEffects) {
|
|
|
2740
2781
|
isDefault: outcome.conditions.length === 0
|
|
2741
2782
|
};
|
|
2742
2783
|
}
|
|
2743
|
-
function
|
|
2784
|
+
function handsOnBranch(outcome, effects, extraEffects) {
|
|
2785
|
+
return {
|
|
2786
|
+
conditions: outcome.conditions,
|
|
2787
|
+
terminal: {
|
|
2788
|
+
kind: "delegate",
|
|
2789
|
+
statusCode: null,
|
|
2790
|
+
body: null,
|
|
2791
|
+
exceptionType: null,
|
|
2792
|
+
message: null,
|
|
2793
|
+
component: null,
|
|
2794
|
+
renderTree: null,
|
|
2795
|
+
delegateTarget: null,
|
|
2796
|
+
emitEvent: null,
|
|
2797
|
+
location: outcome.location
|
|
2798
|
+
},
|
|
2799
|
+
effects: effectsReaching(effects, outcome.conditions),
|
|
2800
|
+
...extraEffects === void 0 ? {} : { extraEffects },
|
|
2801
|
+
location: outcome.location,
|
|
2802
|
+
isDefault: outcome.conditions.length === 0
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
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 = {}) {
|
|
2744
2850
|
const declarations = pattern.responseStatusCalls ?? [];
|
|
2745
2851
|
const body = field(method, "body");
|
|
2746
2852
|
if (declarations.length === 0 || body === null) {
|
|
@@ -2779,43 +2885,544 @@ function responseBranches(method, pattern, effects, extraEffects) {
|
|
|
2779
2885
|
outcomes.push({
|
|
2780
2886
|
conditions: path7.map(conditionOf),
|
|
2781
2887
|
reading: absentReading,
|
|
2782
|
-
location: rangeOf(method)
|
|
2888
|
+
location: rangeOf(method),
|
|
2889
|
+
fellThrough: true
|
|
2783
2890
|
});
|
|
2784
2891
|
}
|
|
2785
2892
|
if (outcomes.length === 0) {
|
|
2786
2893
|
return null;
|
|
2787
2894
|
}
|
|
2788
2895
|
return outcomes.map(
|
|
2789
|
-
(outcome) => branchOf(outcome, pattern, effects, extraEffects)
|
|
2896
|
+
(outcome) => branchOf(outcome, pattern, effects, extraEffects, options)
|
|
2790
2897
|
);
|
|
2791
2898
|
}
|
|
2792
2899
|
|
|
2793
|
-
// src/
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
}
|
|
2801
|
-
|
|
2802
|
-
|
|
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;
|
|
2803
2917
|
}
|
|
2804
|
-
function
|
|
2805
|
-
|
|
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;
|
|
2806
2929
|
}
|
|
2807
|
-
function
|
|
2808
|
-
|
|
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;
|
|
2809
2941
|
}
|
|
2810
|
-
function
|
|
2811
|
-
const receiver =
|
|
2812
|
-
|
|
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) {
|
|
2813
2946
|
return null;
|
|
2814
2947
|
}
|
|
2815
|
-
|
|
2816
|
-
|
|
2948
|
+
const prefix = receiverPrefix(receiver, method, pattern, options);
|
|
2949
|
+
if (prefix === null) {
|
|
2950
|
+
return null;
|
|
2817
2951
|
}
|
|
2818
|
-
|
|
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)) {
|
|
3160
|
+
return null;
|
|
3161
|
+
}
|
|
3162
|
+
const index = node.namedChildren.find(
|
|
3163
|
+
(child) => child !== null && child.id !== object.id
|
|
3164
|
+
);
|
|
3165
|
+
const name = index === void 0 ? null : stringLiteralValue(index);
|
|
3166
|
+
if (name === null) {
|
|
3167
|
+
return null;
|
|
3168
|
+
}
|
|
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;
|
|
3175
|
+
}
|
|
3176
|
+
const { positional } = readCallArgs(field(node, "arguments"));
|
|
3177
|
+
const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
|
|
3178
|
+
if (name === null) {
|
|
3179
|
+
return null;
|
|
3180
|
+
}
|
|
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;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
// src/filters.ts
|
|
3216
|
+
function controllerFilters(pattern, ancestry) {
|
|
3217
|
+
const forms = pattern.filters ?? [];
|
|
3218
|
+
if (forms.length === 0) {
|
|
3219
|
+
return [];
|
|
3220
|
+
}
|
|
3221
|
+
const byName = new Map(forms.map((form) => [form.name, form]));
|
|
3222
|
+
const skipNames = /* @__PURE__ */ new Map();
|
|
3223
|
+
for (const form of forms) {
|
|
3224
|
+
if (form.skippedBy !== void 0) {
|
|
3225
|
+
skipNames.set(form.skippedBy, form.name);
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
const declared = [];
|
|
3229
|
+
const skips = [];
|
|
3230
|
+
for (const { statement } of inheritedStatements(ancestry)) {
|
|
3231
|
+
const called = calledName(statement);
|
|
3232
|
+
if (called === null) {
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
const form = byName.get(called);
|
|
3236
|
+
if (form !== void 0) {
|
|
3237
|
+
declared.push(...declarationsOf(statement, form));
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
const skipped = skipNames.get(called);
|
|
3241
|
+
if (skipped !== void 0) {
|
|
3242
|
+
skips.push(...skipsOf(statement, skipped));
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
const resolved = [];
|
|
3246
|
+
for (const declaration of applySkips(declared, skips)) {
|
|
3247
|
+
const found = methodInAncestry(ancestry, declaration.methodName);
|
|
3248
|
+
if (found.type !== "found") {
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
resolved.push({
|
|
3252
|
+
filter: declaration.filter,
|
|
3253
|
+
methodName: declaration.methodName,
|
|
3254
|
+
method: found.method,
|
|
3255
|
+
file: found.block.file,
|
|
3256
|
+
enclosingQualifiedName: found.block.info.qualifiedName,
|
|
3257
|
+
only: declaration.only,
|
|
3258
|
+
except: declaration.except
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
return [
|
|
3262
|
+
...resolved.filter((one) => one.filter.onThrow !== true),
|
|
3263
|
+
...resolved.filter((one) => one.filter.onThrow === true)
|
|
3264
|
+
];
|
|
3265
|
+
}
|
|
3266
|
+
function filterCoversAction(filter, actionName) {
|
|
3267
|
+
if (filter.except.has(actionName)) {
|
|
3268
|
+
return false;
|
|
3269
|
+
}
|
|
3270
|
+
return filter.only === null || filter.only.has(actionName);
|
|
3271
|
+
}
|
|
3272
|
+
function filterReference(filter, displayPath) {
|
|
3273
|
+
return {
|
|
3274
|
+
file: displayPath,
|
|
3275
|
+
name: filter.methodName,
|
|
3276
|
+
...filter.filter.onThrow === true ? { onThrow: true } : {}
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function filterUnit(filter, pattern, displayPath, body) {
|
|
3280
|
+
const range = rangeOf(filter.method);
|
|
3281
|
+
const branches = responseBranches(
|
|
3282
|
+
filter.method,
|
|
3283
|
+
pattern,
|
|
3284
|
+
body.effects ?? [],
|
|
3285
|
+
body.extraEffects,
|
|
3286
|
+
{ fallthrough: "handOn" }
|
|
3287
|
+
);
|
|
3288
|
+
return {
|
|
3289
|
+
identity: {
|
|
3290
|
+
name: filter.methodName,
|
|
3291
|
+
nameKind: "binding",
|
|
3292
|
+
kind: "middleware",
|
|
3293
|
+
file: displayPath,
|
|
3294
|
+
range,
|
|
3295
|
+
span: spanOf(filter.method),
|
|
3296
|
+
exportName: filter.methodName,
|
|
3297
|
+
exportPath: [filter.enclosingQualifiedName, filter.methodName]
|
|
3298
|
+
},
|
|
3299
|
+
boundaryBinding: null,
|
|
3300
|
+
parameters: [],
|
|
3301
|
+
branches: branches ?? [handsOn(range, body)],
|
|
3302
|
+
bodyContent: body.bodyContent ?? "absent",
|
|
3303
|
+
dependencyCalls: [],
|
|
3304
|
+
declaredContract: null
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
function handsOn(range, body) {
|
|
3308
|
+
return {
|
|
3309
|
+
conditions: [],
|
|
3310
|
+
terminal: {
|
|
3311
|
+
kind: "delegate",
|
|
3312
|
+
statusCode: null,
|
|
3313
|
+
body: null,
|
|
3314
|
+
exceptionType: null,
|
|
3315
|
+
message: null,
|
|
3316
|
+
component: null,
|
|
3317
|
+
renderTree: null,
|
|
3318
|
+
delegateTarget: null,
|
|
3319
|
+
emitEvent: null,
|
|
3320
|
+
location: range
|
|
3321
|
+
},
|
|
3322
|
+
effects: body.effects ?? [],
|
|
3323
|
+
...body.extraEffects === void 0 ? {} : { extraEffects: body.extraEffects },
|
|
3324
|
+
location: range,
|
|
3325
|
+
isDefault: true
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
function calledName(statement) {
|
|
3329
|
+
if (statement.type !== "call" || field(statement, "receiver") !== null) {
|
|
3330
|
+
return null;
|
|
3331
|
+
}
|
|
3332
|
+
return field(statement, "method")?.text ?? null;
|
|
3333
|
+
}
|
|
3334
|
+
function declarationsOf(statement, filter) {
|
|
3335
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
3336
|
+
const keywords = filter.actionKeywords;
|
|
3337
|
+
const only = keywords === void 0 ? null : namesUnder(args.keyword[keywords.include]);
|
|
3338
|
+
const except = keywords === void 0 ? null : namesUnder(args.keyword[keywords.exclude]);
|
|
3339
|
+
return methodNamesOf(args, filter).map((methodName) => ({
|
|
3340
|
+
filter,
|
|
3341
|
+
methodName,
|
|
3342
|
+
only,
|
|
3343
|
+
except: except ?? /* @__PURE__ */ new Set()
|
|
3344
|
+
}));
|
|
3345
|
+
}
|
|
3346
|
+
function methodNamesOf(args, filter) {
|
|
3347
|
+
if (filter.methodFrom === "withKeyword") {
|
|
3348
|
+
const named2 = args.keyword.with;
|
|
3349
|
+
const value = named2 === void 0 ? null : symbolValue(named2);
|
|
3350
|
+
return value === null ? [] : [value];
|
|
3351
|
+
}
|
|
3352
|
+
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
|
|
3353
|
+
}
|
|
3354
|
+
function skipsOf(statement, filterName) {
|
|
3355
|
+
const args = readCallArgs(field(statement, "arguments"));
|
|
3356
|
+
const actions = namesUnder(args.keyword.only);
|
|
3357
|
+
return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
|
|
3358
|
+
}
|
|
3359
|
+
function applySkips(declared, skips) {
|
|
3360
|
+
const left = [];
|
|
3361
|
+
for (const declaration of declared) {
|
|
3362
|
+
const reaching = skips.filter(
|
|
3363
|
+
(skip) => skip.filterName === declaration.filter.name && skip.methodName === declaration.methodName
|
|
3364
|
+
);
|
|
3365
|
+
if (reaching.some((skip) => skip.actions === null)) {
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
const except = new Set(declaration.except);
|
|
3369
|
+
for (const skip of reaching) {
|
|
3370
|
+
for (const action of skip.actions ?? []) {
|
|
3371
|
+
except.add(action);
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
left.push({ ...declaration, except });
|
|
3375
|
+
}
|
|
3376
|
+
return left;
|
|
3377
|
+
}
|
|
3378
|
+
function namesUnder(node) {
|
|
3379
|
+
if (node === void 0) {
|
|
3380
|
+
return null;
|
|
3381
|
+
}
|
|
3382
|
+
const single = symbolValue(node);
|
|
3383
|
+
if (single !== null) {
|
|
3384
|
+
return /* @__PURE__ */ new Set([single]);
|
|
3385
|
+
}
|
|
3386
|
+
if (node.type !== "array" && node.type !== "symbol_array") {
|
|
3387
|
+
return null;
|
|
3388
|
+
}
|
|
3389
|
+
const names = /* @__PURE__ */ new Set();
|
|
3390
|
+
for (const child of bodyStatements(node)) {
|
|
3391
|
+
const bare = child.type === "bare_symbol" ? child.text : null;
|
|
3392
|
+
const name = symbolValue(child) ?? bare;
|
|
3393
|
+
if (name !== null) {
|
|
3394
|
+
names.add(name);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
return names;
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/storage.ts
|
|
3401
|
+
import { deriveOnDemand as deriveOnDemand2, evaluate as evaluate2 } from "@suss/datalog";
|
|
3402
|
+
import { storageBinding } from "@suss/ir-core";
|
|
3403
|
+
import {
|
|
3404
|
+
ANSWER_RELATIONS as ANSWER_RELATIONS2,
|
|
3405
|
+
RESOLUTION_QUESTIONS as RESOLUTION_QUESTIONS2,
|
|
3406
|
+
RESOLUTION_RULES as RESOLUTION_RULES2
|
|
3407
|
+
} from "@suss/resolution";
|
|
3408
|
+
function children3(node) {
|
|
3409
|
+
return node.namedChildren.filter((child) => child !== null);
|
|
3410
|
+
}
|
|
3411
|
+
function receiverOf(node) {
|
|
3412
|
+
return node.type === "call" ? field(node, "receiver") : null;
|
|
3413
|
+
}
|
|
3414
|
+
function methodOf(node) {
|
|
3415
|
+
return field(node, "method")?.text ?? "";
|
|
3416
|
+
}
|
|
3417
|
+
function rootConstant(node) {
|
|
3418
|
+
const receiver = receiverOf(node);
|
|
3419
|
+
if (receiver === null) {
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
if (receiver.type === "constant" || receiver.type === "scope_resolution") {
|
|
3423
|
+
return receiver;
|
|
3424
|
+
}
|
|
3425
|
+
return rootConstant(receiver);
|
|
2819
3426
|
}
|
|
2820
3427
|
var ANCESTRY_PROGRAM = deriveOnDemand2(
|
|
2821
3428
|
[...RESOLUTION_RULES2, ...RESOLUTION_QUESTIONS2],
|
|
@@ -3012,6 +3619,16 @@ async function discoverUnits(root, options) {
|
|
|
3012
3619
|
file: options.absoluteFile ?? options.filePath
|
|
3013
3620
|
}));
|
|
3014
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
|
+
}
|
|
3015
3632
|
for (const info of classes) {
|
|
3016
3633
|
const ownBlocks = fileBlocks.filter(
|
|
3017
3634
|
(block) => block.info.qualifiedName === info.qualifiedName
|
|
@@ -3093,7 +3710,23 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3093
3710
|
if (!reachesConfiguredBase(ancestry, info.qualifiedName, pattern.baseClassNames)) {
|
|
3094
3711
|
return [];
|
|
3095
3712
|
}
|
|
3713
|
+
const filters = controllerFilters(pattern, ancestry);
|
|
3096
3714
|
const units = [];
|
|
3715
|
+
for (const filter of filters) {
|
|
3716
|
+
const displayPath = options.displayPathOf?.(filter.file) ?? filter.file;
|
|
3717
|
+
const raw = filterUnit(
|
|
3718
|
+
filter,
|
|
3719
|
+
pattern,
|
|
3720
|
+
displayPath,
|
|
3721
|
+
bodyOfMethod(filter.method, options)
|
|
3722
|
+
);
|
|
3723
|
+
units.push(raw);
|
|
3724
|
+
options.onReachSeed?.(raw, {
|
|
3725
|
+
file: filter.file,
|
|
3726
|
+
node: filter.method,
|
|
3727
|
+
enclosingQualifiedName: filter.enclosingQualifiedName
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3097
3730
|
for (const block of ownBlocks) {
|
|
3098
3731
|
if (block.info.bodyNode === null) {
|
|
3099
3732
|
continue;
|
|
@@ -3105,6 +3738,12 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3105
3738
|
if ((visibility.get(actionName) ?? "public") !== "public") {
|
|
3106
3739
|
continue;
|
|
3107
3740
|
}
|
|
3741
|
+
const around = filters.filter((filter) => filterCoversAction(filter, actionName)).map(
|
|
3742
|
+
(filter) => filterReference(
|
|
3743
|
+
filter,
|
|
3744
|
+
options.displayPathOf?.(filter.file) ?? filter.file
|
|
3745
|
+
)
|
|
3746
|
+
);
|
|
3108
3747
|
const raw = buildControllerActionUnit(
|
|
3109
3748
|
pack,
|
|
3110
3749
|
pattern,
|
|
@@ -3112,7 +3751,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3112
3751
|
actionName,
|
|
3113
3752
|
method,
|
|
3114
3753
|
options.filePath,
|
|
3115
|
-
options
|
|
3754
|
+
options,
|
|
3755
|
+
around
|
|
3116
3756
|
);
|
|
3117
3757
|
units.push(raw);
|
|
3118
3758
|
options.onReachSeed?.(raw, {
|
|
@@ -3124,7 +3764,7 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
|
|
|
3124
3764
|
}
|
|
3125
3765
|
return units;
|
|
3126
3766
|
}
|
|
3127
|
-
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, filePath, bodyRead) {
|
|
3767
|
+
function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actionName, method, filePath, bodyRead, wrappers = []) {
|
|
3128
3768
|
const range = rangeOf(method);
|
|
3129
3769
|
const route = pattern.routeFor(controllerQualifiedName, actionName);
|
|
3130
3770
|
const body = bodyOfMethod(method, bodyRead);
|
|
@@ -3145,7 +3785,7 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3145
3785
|
exportName: actionName,
|
|
3146
3786
|
exportPath: [controllerQualifiedName, actionName]
|
|
3147
3787
|
},
|
|
3148
|
-
boundaryBinding: route === null ? null :
|
|
3788
|
+
boundaryBinding: route === null ? null : restBinding2({
|
|
3149
3789
|
transport: pack.protocol,
|
|
3150
3790
|
method: route.method,
|
|
3151
3791
|
path: route.path,
|
|
@@ -3179,7 +3819,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
|
|
|
3179
3819
|
],
|
|
3180
3820
|
bodyContent: body.bodyContent ?? "absent",
|
|
3181
3821
|
dependencyCalls: [],
|
|
3182
|
-
declaredContract: null
|
|
3822
|
+
declaredContract: null,
|
|
3823
|
+
...wrappers.length > 0 ? { wrappers: [...wrappers] } : {}
|
|
3183
3824
|
};
|
|
3184
3825
|
}
|
|
3185
3826
|
function routingGapUnit(pattern, gaps) {
|
|
@@ -3262,7 +3903,7 @@ function bodyOfMethod(method, bodyRead = {}) {
|
|
|
3262
3903
|
const storage = bodyRead.storage;
|
|
3263
3904
|
const extra = [
|
|
3264
3905
|
...envReadEffects(method),
|
|
3265
|
-
...storage === void 0 ? [] : storageEffects(
|
|
3906
|
+
...storage === void 0 ? [] : storageEffects(callsUnder2(method), storage)
|
|
3266
3907
|
];
|
|
3267
3908
|
return {
|
|
3268
3909
|
bodyContent: methodHasStatements(method) ? "statements" : "empty",
|
|
@@ -3271,7 +3912,7 @@ function bodyOfMethod(method, bodyRead = {}) {
|
|
|
3271
3912
|
...extra.length > 0 ? { extraEffects: extra } : {}
|
|
3272
3913
|
};
|
|
3273
3914
|
}
|
|
3274
|
-
function
|
|
3915
|
+
function callsUnder2(node, found = []) {
|
|
3275
3916
|
for (const child of node.namedChildren) {
|
|
3276
3917
|
if (child === null) {
|
|
3277
3918
|
continue;
|
|
@@ -3279,7 +3920,7 @@ function callsUnder(node, found = []) {
|
|
|
3279
3920
|
if (child.type === "call") {
|
|
3280
3921
|
found.push(child);
|
|
3281
3922
|
}
|
|
3282
|
-
|
|
3923
|
+
callsUnder2(child, found);
|
|
3283
3924
|
}
|
|
3284
3925
|
return found;
|
|
3285
3926
|
}
|
|
@@ -3786,6 +4427,7 @@ import {
|
|
|
3786
4427
|
import { Database as Database2 } from "@suss/datalog";
|
|
3787
4428
|
import {
|
|
3788
4429
|
assembleSummary as assembleSummary2,
|
|
4430
|
+
composeWrappers,
|
|
3789
4431
|
createCacheLayer,
|
|
3790
4432
|
createTimer,
|
|
3791
4433
|
moduleInitStructure,
|
|
@@ -4375,6 +5017,14 @@ function inheritedMethodsIn(packs) {
|
|
|
4375
5017
|
}
|
|
4376
5018
|
return found;
|
|
4377
5019
|
}
|
|
5020
|
+
function alreadyDiscovered(seen, raw) {
|
|
5021
|
+
const key = `${raw.identity.file}::${raw.identity.name}::${raw.identity.range.start}`;
|
|
5022
|
+
if (seen.has(key)) {
|
|
5023
|
+
return true;
|
|
5024
|
+
}
|
|
5025
|
+
seen.add(key);
|
|
5026
|
+
return false;
|
|
5027
|
+
}
|
|
4378
5028
|
async function extractRubyProject(options) {
|
|
4379
5029
|
const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
|
|
4380
5030
|
const cacheDir = adapterStamp.declineWhenRunFromSource(
|
|
@@ -4436,6 +5086,7 @@ async function extractRubyProject(options) {
|
|
|
4436
5086
|
const displayPathOf = (file) => options.workspaceRoot !== void 0 ? path4.relative(options.workspaceRoot, file) : file;
|
|
4437
5087
|
const seeds = [];
|
|
4438
5088
|
const summariesBySeed = /* @__PURE__ */ new Map();
|
|
5089
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
4439
5090
|
for (const { file, root } of parsed) {
|
|
4440
5091
|
const displayPath = displayPathOf(file);
|
|
4441
5092
|
const seedByRaw = /* @__PURE__ */ new Map();
|
|
@@ -4448,10 +5099,15 @@ async function extractRubyProject(options) {
|
|
|
4448
5099
|
cache,
|
|
4449
5100
|
...storagePatterns.length > 0 ? { storage: { facts: db, patterns: storagePatterns } } : {},
|
|
4450
5101
|
inheritedMethods,
|
|
5102
|
+
displayPathOf,
|
|
5103
|
+
facts: db,
|
|
4451
5104
|
onReachSeed: (raw, seed) => seedByRaw.set(raw, seed)
|
|
4452
5105
|
})
|
|
4453
5106
|
);
|
|
4454
5107
|
for (const raw of rawUnits) {
|
|
5108
|
+
if (alreadyDiscovered(discovered, raw)) {
|
|
5109
|
+
continue;
|
|
5110
|
+
}
|
|
4455
5111
|
const summary = timer.time(
|
|
4456
5112
|
"summarize",
|
|
4457
5113
|
() => assembleSummary2(raw, { gapHandling: "permissive" })
|
|
@@ -4554,12 +5210,18 @@ async function extractRubyProject(options) {
|
|
|
4554
5210
|
}
|
|
4555
5211
|
disambiguateSummaryIds(summaries);
|
|
4556
5212
|
linkCallsToSummaries(summaries);
|
|
5213
|
+
const composed = timer.time(
|
|
5214
|
+
"summarize",
|
|
5215
|
+
() => composeWrappers(summaries, {
|
|
5216
|
+
gapHandling: options.gapHandling ?? "permissive"
|
|
5217
|
+
})
|
|
5218
|
+
);
|
|
4557
5219
|
await timer.timeAsync("cache.write", async () => {
|
|
4558
|
-
if (cacheDir === null ||
|
|
5220
|
+
if (cacheDir === null || composed.length === 0) {
|
|
4559
5221
|
return;
|
|
4560
5222
|
}
|
|
4561
5223
|
try {
|
|
4562
|
-
await extractionCache.write(cacheInput,
|
|
5224
|
+
await extractionCache.write(cacheInput, composed);
|
|
4563
5225
|
} catch {
|
|
4564
5226
|
}
|
|
4565
5227
|
});
|
|
@@ -4568,11 +5230,11 @@ async function extractRubyProject(options) {
|
|
|
4568
5230
|
packs: options.packs,
|
|
4569
5231
|
tallies,
|
|
4570
5232
|
filesWalked: options.files.length,
|
|
4571
|
-
summaries
|
|
5233
|
+
summaries: composed
|
|
4572
5234
|
})
|
|
4573
5235
|
);
|
|
4574
5236
|
options.onTiming?.(timer.report());
|
|
4575
|
-
return { summaries, facts: db };
|
|
5237
|
+
return { summaries: composed, facts: db };
|
|
4576
5238
|
}
|
|
4577
5239
|
function fileDependenciesOf(db, displayPathOf) {
|
|
4578
5240
|
const byFile = /* @__PURE__ */ new Map();
|