@suss/extractor 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +697 -790
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +700 -791
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -166,893 +166,802 @@ import {
|
|
|
166
166
|
readHttpMetadata,
|
|
167
167
|
readWrapperMetadata as readWrapperMetadata2,
|
|
168
168
|
withinScope,
|
|
169
|
-
withWrapperMetadata
|
|
169
|
+
withWrapperMetadata,
|
|
170
|
+
wrapperFor,
|
|
171
|
+
wrapperIndex
|
|
170
172
|
} from "@suss/behavioral-ir";
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
var UnmodeledFlow = class extends Error {
|
|
176
|
-
};
|
|
177
|
-
var PathBudgetExceeded = class extends Error {
|
|
178
|
-
};
|
|
179
|
-
function branchCond(branchStmt, condition, polarity, oppositeExit) {
|
|
180
|
-
return {
|
|
181
|
-
info: {
|
|
182
|
-
sourceText: condition.sourceText,
|
|
183
|
-
polarity,
|
|
184
|
-
source: "explicit",
|
|
185
|
-
expression: condition.expression
|
|
186
|
-
},
|
|
187
|
-
branchStmt,
|
|
188
|
-
oppositeExit
|
|
189
|
-
};
|
|
173
|
+
function composeWrappers(summaries, options = {}) {
|
|
174
|
+
const chain = wrapperIndex(summaries);
|
|
175
|
+
const keepGaps = options.gapHandling !== "silent";
|
|
176
|
+
return summaries.map((summary) => composeOne(summary, chain, keepGaps));
|
|
190
177
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
178
|
+
function composeOne(summary, chain, keepGaps) {
|
|
179
|
+
const recorded = readWrapperMetadata2(summary);
|
|
180
|
+
const applied = recorded?.applied ?? [];
|
|
181
|
+
const covering = applied.filter(
|
|
182
|
+
(reference) => coversUnit(reference, summary)
|
|
183
|
+
);
|
|
184
|
+
const narrowed = covering.length === applied.length ? summary : {
|
|
185
|
+
...summary,
|
|
186
|
+
metadata: withWrapperMetadata(summary.metadata, {
|
|
187
|
+
applied: covering
|
|
188
|
+
})
|
|
201
189
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
"positive",
|
|
206
|
-
"explicit"
|
|
207
|
-
);
|
|
208
|
-
var loopCompletedCond = (headerText, exit) => fixedCond(
|
|
209
|
-
{
|
|
210
|
-
sourceText: `loop exited via ${exit ?? "return"}: ${headerText}`,
|
|
211
|
-
expression: null
|
|
212
|
-
},
|
|
213
|
-
"negative",
|
|
214
|
-
exit === "throw" ? "earlyThrow" : "earlyReturn"
|
|
215
|
-
);
|
|
216
|
-
var catchEntryCond = () => fixedCond(
|
|
217
|
-
{ sourceText: CATCH_ENTRY_TEXT, expression: null },
|
|
218
|
-
"positive",
|
|
219
|
-
"catchBlock"
|
|
220
|
-
);
|
|
221
|
-
function classify(path4, home, isAncestorOrSelf) {
|
|
222
|
-
return path4.map((cond) => {
|
|
223
|
-
if (cond.branchStmt === null) {
|
|
224
|
-
return cond.info;
|
|
225
|
-
}
|
|
226
|
-
const encloses = home !== null && isAncestorOrSelf(cond.branchStmt, home);
|
|
227
|
-
if (encloses || cond.info.polarity === "positive" || cond.oppositeExit === null) {
|
|
228
|
-
return { ...cond.info, source: "explicit" };
|
|
229
|
-
}
|
|
230
|
-
const source = cond.oppositeExit === "throw" ? "earlyThrow" : "earlyReturn";
|
|
231
|
-
return { ...cond.info, source };
|
|
190
|
+
const wrappers = covering.flatMap((reference) => {
|
|
191
|
+
const found = wrapperFor(chain, reference);
|
|
192
|
+
return found === void 0 ? [] : [{ reference, summary: found }];
|
|
232
193
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return [...ownBlocksOf(stmt), ...(stmt.callbacks ?? []).flat()];
|
|
236
|
-
}
|
|
237
|
-
function ownBlocksOf(stmt) {
|
|
238
|
-
if (stmt.kind === "if") {
|
|
239
|
-
return stmt.elseBody === null ? stmt.thenBody : [...stmt.thenBody, ...stmt.elseBody];
|
|
194
|
+
if (wrappers.length === 0) {
|
|
195
|
+
return narrowed;
|
|
240
196
|
}
|
|
241
|
-
|
|
242
|
-
|
|
197
|
+
const responses = respondedInstead(wrappers);
|
|
198
|
+
const handled = handledThrows(wrappers);
|
|
199
|
+
const transitions = beside(summary, responses, handled);
|
|
200
|
+
if (!keepGaps) {
|
|
201
|
+
return transitions === summary.transitions ? narrowed : { ...narrowed, transitions };
|
|
243
202
|
}
|
|
244
|
-
|
|
245
|
-
|
|
203
|
+
const gaps = withContractStatusGaps(
|
|
204
|
+
summary,
|
|
205
|
+
[...responses, ...summary.transitions],
|
|
206
|
+
handled
|
|
207
|
+
);
|
|
208
|
+
if (transitions === summary.transitions && gaps === summary.gaps) {
|
|
209
|
+
return narrowed;
|
|
246
210
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
211
|
+
return { ...narrowed, transitions, gaps };
|
|
212
|
+
}
|
|
213
|
+
function beside(summary, responses, handled) {
|
|
214
|
+
const throws = summary.transitions.some((t) => t.output.type === "throw");
|
|
215
|
+
const onThrow = throws ? handled : [];
|
|
216
|
+
if (responses.length === 0 && onThrow.length === 0) {
|
|
217
|
+
return summary.transitions;
|
|
253
218
|
}
|
|
254
|
-
return [];
|
|
219
|
+
return withDistinctIds([...responses, ...summary.transitions, ...onThrow]);
|
|
255
220
|
}
|
|
256
|
-
function
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
221
|
+
function respondedInstead(wrappers) {
|
|
222
|
+
return wrappers.flatMap(
|
|
223
|
+
(wrapper) => wrapper.reference.onThrow === true ? [] : attribute(
|
|
224
|
+
wrapper.summary.transitions.filter(
|
|
225
|
+
(t) => t.output.type !== "delegate"
|
|
226
|
+
),
|
|
227
|
+
wrapper.reference
|
|
228
|
+
)
|
|
229
|
+
);
|
|
266
230
|
}
|
|
267
|
-
function
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return "throw";
|
|
272
|
-
}
|
|
273
|
-
if (stmt.exitKind === "return") {
|
|
274
|
-
sawReturn = true;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return sawReturn ? "return" : null;
|
|
231
|
+
function handledThrows(wrappers) {
|
|
232
|
+
return wrappers.flatMap(
|
|
233
|
+
(wrapper) => wrapper.reference.onThrow === true ? attribute(wrapper.summary.transitions, wrapper.reference) : []
|
|
234
|
+
);
|
|
278
235
|
}
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
);
|
|
236
|
+
function withContractStatusGaps(summary, composed, handled) {
|
|
237
|
+
const contract = readHttpMetadata(summary)?.declaredContract;
|
|
238
|
+
const binding = summary.identity.boundaryBinding;
|
|
239
|
+
if (contract === void 0 || binding !== null && !exchangesHttpResponses(binding)) {
|
|
240
|
+
return summary.gaps;
|
|
285
241
|
}
|
|
242
|
+
const declared = {
|
|
243
|
+
framework: contract.framework ?? "declared",
|
|
244
|
+
responses: contract.responses
|
|
245
|
+
};
|
|
246
|
+
const stale = new Set(
|
|
247
|
+
contractStatusGaps(declared, summary.transitions).map(gapKey)
|
|
248
|
+
);
|
|
249
|
+
const gaps = [
|
|
250
|
+
...summary.gaps.filter((gap) => !stale.has(gapKey(gap))),
|
|
251
|
+
...contractStatusGaps(declared, [...composed, ...handled])
|
|
252
|
+
];
|
|
253
|
+
const unchanged = gaps.length === summary.gaps.length && gaps.every((gap, i) => gapKey(gap) === gapKey(summary.gaps[i]));
|
|
254
|
+
return unchanged ? summary.gaps : gaps;
|
|
286
255
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
const info = classify(path4, home, ctx.isAncestorOrSelf);
|
|
290
|
-
const existing = ctx.state.byTerminal.get(terminal) ?? [];
|
|
291
|
-
existing.push(info);
|
|
292
|
-
ctx.state.byTerminal.set(terminal, existing);
|
|
256
|
+
function gapKey(gap) {
|
|
257
|
+
return `${gap.type}|${gap.description}`;
|
|
293
258
|
}
|
|
294
|
-
function
|
|
295
|
-
if (
|
|
259
|
+
function coversUnit(reference, summary) {
|
|
260
|
+
if (reference.scope === void 0) {
|
|
296
261
|
return true;
|
|
297
262
|
}
|
|
298
|
-
|
|
263
|
+
const binding = summary.identity.boundaryBinding;
|
|
264
|
+
return binding !== null && withinScope(binding, reference.scope);
|
|
299
265
|
}
|
|
300
|
-
function
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
out.push(elsePath);
|
|
266
|
+
function attribute(transitions, reference) {
|
|
267
|
+
return transitions.map((transition) => ({
|
|
268
|
+
...transition,
|
|
269
|
+
metadata: withWrapperMetadata(transition.metadata, { from: reference })
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
function withDistinctIds(transitions) {
|
|
273
|
+
const seen = /* @__PURE__ */ new Map();
|
|
274
|
+
return transitions.map((transition) => {
|
|
275
|
+
const taken = seen.get(transition.id);
|
|
276
|
+
seen.set(transition.id, (taken ?? 0) + 1);
|
|
277
|
+
return taken === void 0 ? transition : { ...transition, id: `${transition.id}#${taken + 1}` };
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/effectGuards.ts
|
|
282
|
+
var keyOf = (condition) => `${condition.polarity}:${condition.sourceText}`;
|
|
283
|
+
var opposite = (condition) => `${condition.polarity === "positive" ? "negative" : "positive"}:${condition.sourceText}`;
|
|
284
|
+
function guardsHoldOn(preconditions, conditions) {
|
|
285
|
+
if (preconditions === void 0 || preconditions.length === 0) {
|
|
286
|
+
return true;
|
|
322
287
|
}
|
|
323
|
-
|
|
288
|
+
const onBranch = new Set(conditions.map(keyOf));
|
|
289
|
+
return preconditions.every(
|
|
290
|
+
(precondition) => !onBranch.has(opposite(precondition))
|
|
291
|
+
);
|
|
324
292
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
out.push(...conts);
|
|
293
|
+
function runsBefore(effectLine, terminalEndLine) {
|
|
294
|
+
return effectLine <= terminalEndLine;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/moduleImports.ts
|
|
298
|
+
function stampModuleImports(summaries, importsOf) {
|
|
299
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
300
|
+
for (const summary of summaries) {
|
|
301
|
+
const file = summary.location.file;
|
|
302
|
+
if (!byFile.has(file)) {
|
|
303
|
+
const found = importsOf(file);
|
|
304
|
+
byFile.set(
|
|
305
|
+
file,
|
|
306
|
+
found === void 0 ? void 0 : [...new Set(found)].sort()
|
|
307
|
+
);
|
|
342
308
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const exit = exitKindOfList(group.body);
|
|
347
|
-
const negationSource = exit === "throw" ? "earlyThrow" : exit === "return" ? "earlyReturn" : "explicit";
|
|
348
|
-
negations.push(fixedCond(group.condition, "negative", negationSource));
|
|
309
|
+
const imports = byFile.get(file);
|
|
310
|
+
if (imports === void 0) {
|
|
311
|
+
continue;
|
|
349
312
|
}
|
|
313
|
+
summary.metadata = {
|
|
314
|
+
...summary.metadata ?? {},
|
|
315
|
+
moduleImports: imports
|
|
316
|
+
};
|
|
350
317
|
}
|
|
351
|
-
if (!defaultRanBody) {
|
|
352
|
-
out.push([...path4, ...negations]);
|
|
353
|
-
}
|
|
354
|
-
return out;
|
|
355
|
-
}
|
|
356
|
-
function stepLoop(stmt, path4, ctx) {
|
|
357
|
-
const bodyExit = exitKindOfList(stmt.body);
|
|
358
|
-
if (containsTerminal(ctx, stmt)) {
|
|
359
|
-
enumerate(ctx, stmt.body, [
|
|
360
|
-
...path4,
|
|
361
|
-
loopIterationCond(stmt.condition.sourceText)
|
|
362
|
-
]);
|
|
363
|
-
}
|
|
364
|
-
if (bodyExit !== null) {
|
|
365
|
-
return [[...path4, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
|
|
366
|
-
}
|
|
367
|
-
return [path4];
|
|
368
318
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
319
|
+
|
|
320
|
+
// src/moduleInit.ts
|
|
321
|
+
function moduleInitStructure(options) {
|
|
322
|
+
const { name, file, range, effects } = options;
|
|
323
|
+
return {
|
|
324
|
+
identity: {
|
|
325
|
+
name,
|
|
326
|
+
kind: "module-init",
|
|
327
|
+
file,
|
|
328
|
+
range,
|
|
329
|
+
exportName: null,
|
|
330
|
+
exportPath: null
|
|
331
|
+
},
|
|
332
|
+
boundaryBinding: null,
|
|
333
|
+
parameters: [],
|
|
334
|
+
branches: [
|
|
335
|
+
{
|
|
336
|
+
conditions: [],
|
|
337
|
+
terminal: {
|
|
338
|
+
kind: "void",
|
|
339
|
+
statusCode: null,
|
|
340
|
+
body: null,
|
|
341
|
+
exceptionType: null,
|
|
342
|
+
message: null,
|
|
343
|
+
component: null,
|
|
344
|
+
renderTree: null,
|
|
345
|
+
delegateTarget: null,
|
|
346
|
+
emitEvent: null,
|
|
347
|
+
location: range
|
|
348
|
+
},
|
|
349
|
+
effects: [],
|
|
350
|
+
extraEffects: effects,
|
|
351
|
+
location: range,
|
|
352
|
+
isDefault: true
|
|
353
|
+
}
|
|
354
|
+
],
|
|
355
|
+
unmatchedReturns: 0,
|
|
356
|
+
dependencyCalls: [],
|
|
357
|
+
declaredContract: null
|
|
358
|
+
};
|
|
385
359
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
360
|
+
|
|
361
|
+
// src/packHelpers.ts
|
|
362
|
+
function httpRouteDiscovery(opts) {
|
|
363
|
+
const kind = opts.kind ?? "handler";
|
|
364
|
+
const calls = opts.importNames.map((importName) => ({
|
|
365
|
+
kind,
|
|
366
|
+
match: {
|
|
367
|
+
type: "registrationCall",
|
|
368
|
+
importModule: opts.importModule,
|
|
369
|
+
importName,
|
|
370
|
+
registrationChain: [...opts.methods]
|
|
371
|
+
},
|
|
372
|
+
bindingExtraction: {
|
|
373
|
+
// `.all` registers every method, so it records "*", which the pairing
|
|
374
|
+
// engine treats as agreeing with any method at all.
|
|
375
|
+
method: {
|
|
376
|
+
type: "fromRegistration",
|
|
377
|
+
position: "methodName",
|
|
378
|
+
nameMap: { all: "*" }
|
|
379
|
+
},
|
|
380
|
+
path: { type: "fromArgument", position: 0 }
|
|
381
|
+
},
|
|
382
|
+
...opts.mount !== void 0 ? { mount: opts.mount } : {},
|
|
383
|
+
requiresImport: [opts.importModule]
|
|
384
|
+
}));
|
|
385
|
+
if (opts.importNames.length === 0) {
|
|
386
|
+
return calls;
|
|
390
387
|
}
|
|
391
|
-
return [
|
|
388
|
+
return [
|
|
389
|
+
...calls,
|
|
390
|
+
// Routes registered in a loop over an array of specs, a shape
|
|
391
|
+
// registration-call discovery cannot see. The receiver comes from
|
|
392
|
+
// the same import declaration as the calls above, so a loop that
|
|
393
|
+
// never touches this library's routable is left alone.
|
|
394
|
+
{
|
|
395
|
+
kind,
|
|
396
|
+
match: {
|
|
397
|
+
type: "registrationLoop",
|
|
398
|
+
elementShape: LOOP_ELEMENT_SHAPE,
|
|
399
|
+
receiver: {
|
|
400
|
+
importModule: opts.importModule,
|
|
401
|
+
importNames: [...opts.importNames]
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
requiresImport: [opts.importModule]
|
|
405
|
+
}
|
|
406
|
+
];
|
|
392
407
|
}
|
|
393
|
-
function
|
|
394
|
-
return
|
|
408
|
+
function wrapperDiscovery(opts) {
|
|
409
|
+
return opts.importNames.flatMap(
|
|
410
|
+
(importName) => opts.wraps.map((wraps) => ({
|
|
411
|
+
kind: opts.kind ?? "middleware",
|
|
412
|
+
match: {
|
|
413
|
+
type: "registrationCall",
|
|
414
|
+
importModule: opts.importModule,
|
|
415
|
+
importName,
|
|
416
|
+
registrationChain: []
|
|
417
|
+
},
|
|
418
|
+
wraps: { ...wraps },
|
|
419
|
+
requiresImport: [opts.importModule]
|
|
420
|
+
}))
|
|
421
|
+
);
|
|
395
422
|
}
|
|
396
|
-
var
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
try: stepTry,
|
|
401
|
-
exit: stepExit,
|
|
402
|
-
opaque: stepOpaque
|
|
423
|
+
var LOOP_ELEMENT_SHAPE = {
|
|
424
|
+
methodKey: "method",
|
|
425
|
+
pathKey: "path",
|
|
426
|
+
handlerKey: "handler"
|
|
403
427
|
};
|
|
404
|
-
function
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const out = [...fellThrough, ...exits];
|
|
414
|
-
return out.length === 0 ? [path4] : out;
|
|
415
|
-
}
|
|
416
|
-
function stepStatement(ctx, stmt, path4) {
|
|
417
|
-
let paths = [path4];
|
|
418
|
-
for (const body of stmt.callbacks ?? []) {
|
|
419
|
-
paths = paths.flatMap((each) => stepCallback(ctx, body, each));
|
|
420
|
-
}
|
|
421
|
-
const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
|
|
422
|
-
for (const each of withRejoined(paths)) {
|
|
423
|
-
for (const terminal of ownTerminals) {
|
|
424
|
-
recordTerminal(ctx, terminal, stmt, each);
|
|
428
|
+
function registrationHelperDiscovery(helpers, kind = "handler") {
|
|
429
|
+
return helpers.map((helper) => ({
|
|
430
|
+
kind,
|
|
431
|
+
match: {
|
|
432
|
+
type: "registrationTemplate",
|
|
433
|
+
helperName: helper.helperName,
|
|
434
|
+
...helper.importModule !== void 0 ? { importModule: helper.importModule } : {},
|
|
435
|
+
...helper.subject !== void 0 ? { subject: helper.subject } : {},
|
|
436
|
+
registrations: helper.registrations.map((one) => ({ ...one }))
|
|
425
437
|
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
ctx
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
440
|
+
function routeHelperIndex(opts) {
|
|
441
|
+
const methods = new Set(
|
|
442
|
+
opts.methods.map(
|
|
443
|
+
(method) => method.startsWith(".") ? method.slice(1) : method
|
|
433
444
|
)
|
|
434
445
|
);
|
|
446
|
+
return {
|
|
447
|
+
find: { by: "subject" },
|
|
448
|
+
declare: (helpers) => ({
|
|
449
|
+
discovery: registrationHelperDiscovery(
|
|
450
|
+
helpers.flatMap((helper) => routesRegisteredBy(helper, methods, opts)),
|
|
451
|
+
opts.kind ?? "handler"
|
|
452
|
+
)
|
|
453
|
+
})
|
|
454
|
+
};
|
|
435
455
|
}
|
|
436
|
-
function
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const one = left[at];
|
|
443
|
-
const other = right[at];
|
|
444
|
-
if (one === other) {
|
|
456
|
+
function routesRegisteredBy(helper, methods, opts) {
|
|
457
|
+
const registrations = [];
|
|
458
|
+
let subjectArgument;
|
|
459
|
+
for (const sink of helper.sinks) {
|
|
460
|
+
const registration = routeRegisteredBy(helper, sink, methods);
|
|
461
|
+
if (registration === null) {
|
|
445
462
|
continue;
|
|
446
463
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (one === void 0 || other === void 0 || one.branchStmt === null || one.branchStmt !== other.branchStmt || one.info.polarity === other.info.polarity) {
|
|
451
|
-
return null;
|
|
464
|
+
registrations.push(registration);
|
|
465
|
+
if (sink.receiver.as === "parameter") {
|
|
466
|
+
subjectArgument = sink.receiver.position;
|
|
452
467
|
}
|
|
453
|
-
found = at;
|
|
454
468
|
}
|
|
455
|
-
|
|
469
|
+
if (registrations.length === 0 || subjectArgument === void 0) {
|
|
470
|
+
return [];
|
|
471
|
+
}
|
|
472
|
+
return [
|
|
473
|
+
{
|
|
474
|
+
helperName: helper.name,
|
|
475
|
+
importModule: helper.file,
|
|
476
|
+
subject: {
|
|
477
|
+
argument: subjectArgument,
|
|
478
|
+
importModule: opts.importModule,
|
|
479
|
+
importNames: [...opts.importNames]
|
|
480
|
+
},
|
|
481
|
+
registrations
|
|
482
|
+
}
|
|
483
|
+
];
|
|
456
484
|
}
|
|
457
|
-
function
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const out = [];
|
|
461
|
-
const taken = /* @__PURE__ */ new Set();
|
|
462
|
-
let mergedAny = false;
|
|
463
|
-
for (let i = 0; i < current.length; i++) {
|
|
464
|
-
if (taken.has(i)) {
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
let combined = current[i];
|
|
468
|
-
for (let j = i + 1; j < current.length; j++) {
|
|
469
|
-
if (taken.has(j)) {
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
const at = soleDisagreement(combined, current[j]);
|
|
473
|
-
if (at === null) {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
combined = [...combined.slice(0, at), ...combined.slice(at + 1)];
|
|
477
|
-
taken.add(j);
|
|
478
|
-
mergedAny = true;
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
out.push(combined);
|
|
482
|
-
}
|
|
483
|
-
current = out;
|
|
484
|
-
if (!mergedAny) {
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
485
|
+
function routeRegisteredBy(helper, sink, methods) {
|
|
486
|
+
if (sink.method === null || !methods.has(sink.method)) {
|
|
487
|
+
return null;
|
|
487
488
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const merged = mergeRejoined(paths);
|
|
492
|
-
return merged.length === paths.length ? paths : [...merged, ...paths];
|
|
493
|
-
}
|
|
494
|
-
function enumerate(ctx, stmts, prefix) {
|
|
495
|
-
let frontiers = [[...prefix]];
|
|
496
|
-
for (const stmt of stmts) {
|
|
497
|
-
const nextFrontiers = [];
|
|
498
|
-
for (const path4 of mergeRejoined(frontiers)) {
|
|
499
|
-
nextFrontiers.push(...stepStatement(ctx, stmt, path4));
|
|
500
|
-
}
|
|
501
|
-
frontiers = nextFrontiers;
|
|
502
|
-
if (frontiers.length > MAX_PATHS) {
|
|
503
|
-
throw new PathBudgetExceeded(
|
|
504
|
-
`path budget exceeded, more than ${MAX_PATHS} paths`
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
if (frontiers.length === 0) {
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
489
|
+
const { receiver } = sink;
|
|
490
|
+
if (receiver.as !== "parameter" || receiver.property !== void 0 || !helper.subjectParameters.includes(receiver.position)) {
|
|
491
|
+
return null;
|
|
510
492
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const visit = (stmt, parent) => {
|
|
516
|
-
parentOf.set(stmt, parent);
|
|
517
|
-
for (const child of childrenOf(stmt)) {
|
|
518
|
-
visit(child, stmt);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
for (const stmt of statements) {
|
|
522
|
-
visit(stmt, null);
|
|
493
|
+
const pathTemplate = sink.arguments[0];
|
|
494
|
+
const handler = sink.arguments[sink.arguments.length - 1];
|
|
495
|
+
if (sink.arguments.length < 2 || pathTemplate === void 0 || pathTemplate.as !== "text" || handler === void 0) {
|
|
496
|
+
return null;
|
|
523
497
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (current === a) {
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
current = parentOf.get(current) ?? null;
|
|
535
|
-
}
|
|
536
|
-
return false;
|
|
537
|
-
};
|
|
538
|
-
const state = {
|
|
539
|
-
byTerminal: /* @__PURE__ */ new Map(),
|
|
540
|
-
fallthrough: [],
|
|
541
|
-
pathCount: 0,
|
|
542
|
-
callbackExits: []
|
|
543
|
-
};
|
|
544
|
-
const ctx = {
|
|
545
|
-
terminalsByStmt: input.terminalsByStmt,
|
|
546
|
-
state,
|
|
547
|
-
isAncestorOrSelf
|
|
498
|
+
const handlerArg = handlerSlot(handler);
|
|
499
|
+
return handlerArg === null ? null : {
|
|
500
|
+
// `.all` registers every method, which the pairing engine
|
|
501
|
+
// spells as a wildcard.
|
|
502
|
+
method: sink.method === "all" ? "*" : sink.method.toUpperCase(),
|
|
503
|
+
pathTemplate: pathTemplate.text,
|
|
504
|
+
handlerArg
|
|
548
505
|
};
|
|
549
|
-
const continuations = withRejoined(enumerate(ctx, input.statements, []));
|
|
550
|
-
for (const path4 of continuations) {
|
|
551
|
-
chargeBudget(state);
|
|
552
|
-
state.fallthrough.push(classify(path4, null, isAncestorOrSelf));
|
|
553
|
-
}
|
|
554
|
-
return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
|
|
555
506
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
return
|
|
559
|
-
} catch (error) {
|
|
560
|
-
const reason = reasonToDegrade(error);
|
|
561
|
-
if (reason === null) {
|
|
562
|
-
throw error;
|
|
563
|
-
}
|
|
564
|
-
const marker = {
|
|
565
|
-
sourceText: `unmodeled control flow (${reason})`,
|
|
566
|
-
polarity: "positive",
|
|
567
|
-
source: "explicit",
|
|
568
|
-
expression: null
|
|
569
|
-
};
|
|
570
|
-
const byTerminal = /* @__PURE__ */ new Map();
|
|
571
|
-
for (const terminal of terminals) {
|
|
572
|
-
byTerminal.set(terminal, [[marker]]);
|
|
573
|
-
}
|
|
574
|
-
return { byTerminal, fallthrough: [[marker]], degraded: reason };
|
|
507
|
+
function handlerSlot(value) {
|
|
508
|
+
if (value.as !== "parameter") {
|
|
509
|
+
return null;
|
|
575
510
|
}
|
|
511
|
+
return value.property === void 0 ? `{${value.position}}` : `{${value.position}}.${value.property}`;
|
|
576
512
|
}
|
|
577
|
-
function
|
|
578
|
-
if (
|
|
579
|
-
return
|
|
513
|
+
function unwrapJsonStringify(body) {
|
|
514
|
+
if (body === null || typeof body !== "object") {
|
|
515
|
+
return body;
|
|
580
516
|
}
|
|
581
|
-
|
|
582
|
-
|
|
517
|
+
const candidate = body;
|
|
518
|
+
if (candidate.kind !== "call" || candidate.callee !== "JSON.stringify") {
|
|
519
|
+
return body;
|
|
583
520
|
}
|
|
584
|
-
|
|
521
|
+
const inner = candidate.args?.[0];
|
|
522
|
+
return inner ?? body;
|
|
585
523
|
}
|
|
586
524
|
|
|
587
|
-
// src/
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
525
|
+
// src/packOptions.ts
|
|
526
|
+
import { z } from "zod";
|
|
527
|
+
var storageSystemOption = z.enum([
|
|
528
|
+
"postgresql",
|
|
529
|
+
"mysql",
|
|
530
|
+
"sqlite"
|
|
531
|
+
]);
|
|
532
|
+
var scopeOption = z.string();
|
|
533
|
+
var configuredCallOption = z.object({
|
|
534
|
+
/** Module that declares the receiver's type. */
|
|
535
|
+
module: z.string(),
|
|
536
|
+
/** Type name of the receiver, as exported from that module. */
|
|
537
|
+
receiver: z.string(),
|
|
538
|
+
/** Method that performs the send. */
|
|
539
|
+
method: z.string(),
|
|
540
|
+
/** Argument index carrying the subject. */
|
|
541
|
+
subjectArg: z.number(),
|
|
542
|
+
/**
|
|
543
|
+
* Which argument the message body is. Left out when the method has no
|
|
544
|
+
* single body argument, as a batch method taking a list of entries
|
|
545
|
+
* does, and then no body is reported.
|
|
546
|
+
*/
|
|
547
|
+
bodyArg: z.number().optional()
|
|
548
|
+
}).strict();
|
|
549
|
+
|
|
550
|
+
// src/paths/enumeratePaths.ts
|
|
551
|
+
import { CATCH_ENTRY_TEXT } from "@suss/behavioral-ir";
|
|
552
|
+
var MAX_PATHS = 256;
|
|
553
|
+
var UnmodeledFlow = class extends Error {
|
|
593
554
|
};
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const key = summaryKey(summary.location.file, summary.identity.name);
|
|
598
|
-
const sharing = byKey.get(key);
|
|
599
|
-
if (sharing === void 0) {
|
|
600
|
-
byKey.set(key, [summary]);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
sharing.push(summary);
|
|
604
|
-
}
|
|
605
|
-
const keepGaps = options.gapHandling !== "silent";
|
|
606
|
-
return summaries.map((summary) => composeOne(summary, byKey, keepGaps));
|
|
607
|
-
}
|
|
608
|
-
function summaryKey(file, name) {
|
|
609
|
-
return `${file}::${name}`;
|
|
610
|
-
}
|
|
611
|
-
function summaryOf(reference, byKey) {
|
|
612
|
-
const sharing = byKey.get(summaryKey(reference.file, reference.name)) ?? [];
|
|
613
|
-
if (reference.line === void 0) {
|
|
614
|
-
return sharing[0];
|
|
615
|
-
}
|
|
616
|
-
return sharing.find((one) => one.location.range.start === reference.line) ?? sharing[0];
|
|
617
|
-
}
|
|
618
|
-
function composeOne(summary, byKey, keepGaps) {
|
|
619
|
-
const recorded = readWrapperMetadata2(summary);
|
|
620
|
-
const applied = recorded?.applied ?? [];
|
|
621
|
-
const covering = applied.filter(
|
|
622
|
-
(reference) => coversUnit(reference, summary)
|
|
623
|
-
);
|
|
624
|
-
const narrowed = covering.length === applied.length ? summary : {
|
|
625
|
-
...summary,
|
|
626
|
-
metadata: withWrapperMetadata(summary.metadata, {
|
|
627
|
-
applied: covering
|
|
628
|
-
})
|
|
629
|
-
};
|
|
630
|
-
const wrappers = covering.flatMap((reference) => {
|
|
631
|
-
const found = summaryOf(reference, byKey);
|
|
632
|
-
return found === void 0 ? [] : [{ reference, summary: found }];
|
|
633
|
-
});
|
|
634
|
-
if (wrappers.length === 0) {
|
|
635
|
-
return narrowed;
|
|
636
|
-
}
|
|
637
|
-
let composition = {
|
|
638
|
-
transitions: summary.transitions,
|
|
639
|
-
degraded: false
|
|
640
|
-
};
|
|
641
|
-
for (const wrapper of [...wrappers].reverse()) {
|
|
642
|
-
if (wrapper.reference.onThrow === true) {
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
composition = merge(composition, applyWrapper(wrapper, composition));
|
|
646
|
-
}
|
|
647
|
-
for (const wrapper of wrappers) {
|
|
648
|
-
if (wrapper.reference.onThrow !== true) {
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
composition = merge(composition, applyThrowWrapper(wrapper, composition));
|
|
652
|
-
}
|
|
653
|
-
const transitions = composition.transitions === summary.transitions ? summary.transitions : withDistinctIds(composition.transitions);
|
|
654
|
-
if (!keepGaps) {
|
|
655
|
-
return { ...narrowed, transitions };
|
|
656
|
-
}
|
|
657
|
-
const gaps = withContractStatusGaps(summary, transitions, wrappers);
|
|
658
|
-
if (transitions === summary.transitions && gaps === summary.gaps) {
|
|
659
|
-
return narrowed;
|
|
660
|
-
}
|
|
555
|
+
var PathBudgetExceeded = class extends Error {
|
|
556
|
+
};
|
|
557
|
+
function branchCond(branchStmt, condition, polarity, oppositeExit) {
|
|
661
558
|
return {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (contract === void 0 || binding !== null && !exchangesHttpResponses(binding)) {
|
|
671
|
-
return summary.gaps;
|
|
672
|
-
}
|
|
673
|
-
const declared = {
|
|
674
|
-
framework: contract.framework ?? "declared",
|
|
675
|
-
responses: contract.responses
|
|
559
|
+
info: {
|
|
560
|
+
sourceText: condition.sourceText,
|
|
561
|
+
polarity,
|
|
562
|
+
source: "explicit",
|
|
563
|
+
expression: condition.expression
|
|
564
|
+
},
|
|
565
|
+
branchStmt,
|
|
566
|
+
oppositeExit
|
|
676
567
|
};
|
|
677
|
-
const stale = new Set(
|
|
678
|
-
contractStatusGaps(declared, summary.transitions).map(gapKey)
|
|
679
|
-
);
|
|
680
|
-
const onThrow = wrappers.filter((wrapper) => wrapper.reference.onThrow === true).flatMap(
|
|
681
|
-
(wrapper) => attribute(wrapper.summary.transitions, wrapper.reference)
|
|
682
|
-
);
|
|
683
|
-
const gaps = [
|
|
684
|
-
...summary.gaps.filter((gap) => !stale.has(gapKey(gap))),
|
|
685
|
-
...contractStatusGaps(declared, [...composed, ...onThrow])
|
|
686
|
-
];
|
|
687
|
-
const unchanged = gaps.length === summary.gaps.length && gaps.every((gap, i) => gapKey(gap) === gapKey(summary.gaps[i]));
|
|
688
|
-
return unchanged ? summary.gaps : gaps;
|
|
689
568
|
}
|
|
690
|
-
function
|
|
691
|
-
return `${gap.type}|${gap.description}`;
|
|
692
|
-
}
|
|
693
|
-
function merge(before, step) {
|
|
569
|
+
function fixedCond(condition, polarity, source) {
|
|
694
570
|
return {
|
|
695
|
-
|
|
696
|
-
|
|
571
|
+
info: {
|
|
572
|
+
sourceText: condition.sourceText,
|
|
573
|
+
polarity,
|
|
574
|
+
source,
|
|
575
|
+
expression: condition.expression
|
|
576
|
+
},
|
|
577
|
+
branchStmt: null,
|
|
578
|
+
oppositeExit: null
|
|
697
579
|
};
|
|
698
580
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
581
|
+
var loopIterationCond = (headerText) => fixedCond(
|
|
582
|
+
{ sourceText: `some iteration of: ${headerText}`, expression: null },
|
|
583
|
+
"positive",
|
|
584
|
+
"explicit"
|
|
585
|
+
);
|
|
586
|
+
var loopCompletedCond = (headerText, exit) => fixedCond(
|
|
587
|
+
{
|
|
588
|
+
sourceText: `loop exited via ${exit ?? "return"}: ${headerText}`,
|
|
589
|
+
expression: null
|
|
590
|
+
},
|
|
591
|
+
"negative",
|
|
592
|
+
exit === "throw" ? "earlyThrow" : "earlyReturn"
|
|
593
|
+
);
|
|
594
|
+
var catchEntryCond = () => fixedCond(
|
|
595
|
+
{ sourceText: CATCH_ENTRY_TEXT, expression: null },
|
|
596
|
+
"positive",
|
|
597
|
+
"catchBlock"
|
|
598
|
+
);
|
|
599
|
+
function classify(path4, home, isAncestorOrSelf) {
|
|
600
|
+
return path4.map((cond) => {
|
|
601
|
+
if (cond.branchStmt === null) {
|
|
602
|
+
return cond.info;
|
|
603
|
+
}
|
|
604
|
+
const encloses = home !== null && isAncestorOrSelf(cond.branchStmt, home);
|
|
605
|
+
if (encloses || cond.info.polarity === "positive" || cond.oppositeExit === null) {
|
|
606
|
+
return { ...cond.info, source: "explicit" };
|
|
607
|
+
}
|
|
608
|
+
const source = cond.oppositeExit === "throw" ? "earlyThrow" : "earlyReturn";
|
|
609
|
+
return { ...cond.info, source };
|
|
610
|
+
});
|
|
724
611
|
}
|
|
725
|
-
function
|
|
726
|
-
return
|
|
727
|
-
(candidate, i) => !passThroughs.some(
|
|
728
|
-
(other, j) => j !== i && covers(other, candidate) && (j < i || !covers(candidate, other))
|
|
729
|
-
)
|
|
730
|
-
);
|
|
612
|
+
function childrenOf(stmt) {
|
|
613
|
+
return [...ownBlocksOf(stmt), ...(stmt.callbacks ?? []).flat()];
|
|
731
614
|
}
|
|
732
|
-
function
|
|
733
|
-
if (
|
|
734
|
-
return
|
|
615
|
+
function ownBlocksOf(stmt) {
|
|
616
|
+
if (stmt.kind === "if") {
|
|
617
|
+
return stmt.elseBody === null ? stmt.thenBody : [...stmt.thenBody, ...stmt.elseBody];
|
|
735
618
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
function applyThrowWrapper(wrapper, inner) {
|
|
740
|
-
const thrown = inner.transitions.filter((t) => t.output.type === "throw");
|
|
741
|
-
if (thrown.length === 0) {
|
|
742
|
-
return inner;
|
|
619
|
+
if (stmt.kind === "switch") {
|
|
620
|
+
return stmt.groups.flatMap((g) => g.body);
|
|
743
621
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if (rest.length + thrown.length * handled.length > MAX_PATHS) {
|
|
747
|
-
return {
|
|
748
|
-
transitions: [
|
|
749
|
-
...inner.transitions,
|
|
750
|
-
...attribute(handled, wrapper.reference)
|
|
751
|
-
],
|
|
752
|
-
degraded: true
|
|
753
|
-
};
|
|
622
|
+
if (stmt.kind === "loop") {
|
|
623
|
+
return stmt.body;
|
|
754
624
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
625
|
+
if (stmt.kind === "try") {
|
|
626
|
+
return [
|
|
627
|
+
...stmt.tryBody,
|
|
628
|
+
...stmt.catchBody ?? [],
|
|
629
|
+
...stmt.finallyBody ?? []
|
|
630
|
+
];
|
|
631
|
+
}
|
|
632
|
+
return [];
|
|
762
633
|
}
|
|
763
|
-
function
|
|
764
|
-
return {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
634
|
+
function hasStrayBreak(nodes) {
|
|
635
|
+
return nodes.some((node) => {
|
|
636
|
+
if (node.kind === "exit" && node.exit === "break") {
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (node.kind === "switch" || node.kind === "loop") {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
return hasStrayBreak(ownBlocksOf(node));
|
|
643
|
+
});
|
|
771
644
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
645
|
+
function exitKindOfList(stmts) {
|
|
646
|
+
let sawReturn = false;
|
|
647
|
+
for (const stmt of stmts) {
|
|
648
|
+
if (stmt.exitKind === "throw") {
|
|
649
|
+
return "throw";
|
|
650
|
+
}
|
|
651
|
+
if (stmt.exitKind === "return") {
|
|
652
|
+
sawReturn = true;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return sawReturn ? "return" : null;
|
|
777
656
|
}
|
|
778
|
-
function
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
657
|
+
function chargeBudget(state) {
|
|
658
|
+
state.pathCount++;
|
|
659
|
+
if (state.pathCount > MAX_PATHS) {
|
|
660
|
+
throw new PathBudgetExceeded(
|
|
661
|
+
`path budget exceeded, more than ${MAX_PATHS} paths`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
785
664
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
665
|
+
function recordTerminal(ctx, terminal, home, path4) {
|
|
666
|
+
chargeBudget(ctx.state);
|
|
667
|
+
const info = classify(path4, home, ctx.isAncestorOrSelf);
|
|
668
|
+
const existing = ctx.state.byTerminal.get(terminal) ?? [];
|
|
669
|
+
existing.push(info);
|
|
670
|
+
ctx.state.byTerminal.set(terminal, existing);
|
|
671
|
+
}
|
|
672
|
+
function containsTerminal(ctx, stmt) {
|
|
673
|
+
if ((ctx.terminalsByStmt.get(stmt)?.length ?? 0) > 0) {
|
|
792
674
|
return true;
|
|
793
675
|
}
|
|
794
|
-
|
|
795
|
-
return preconditions.every(
|
|
796
|
-
(precondition) => !onBranch.has(opposite(precondition))
|
|
797
|
-
);
|
|
676
|
+
return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
|
|
798
677
|
}
|
|
799
|
-
function
|
|
800
|
-
|
|
678
|
+
function stepIf(stmt, path4, ctx) {
|
|
679
|
+
const thenExit = exitKindOfList(stmt.thenBody);
|
|
680
|
+
const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
|
|
681
|
+
const thenPath = [
|
|
682
|
+
...path4,
|
|
683
|
+
branchCond(stmt, stmt.condition, "positive", elseExit)
|
|
684
|
+
];
|
|
685
|
+
const elsePath = [
|
|
686
|
+
...path4,
|
|
687
|
+
branchCond(
|
|
688
|
+
stmt,
|
|
689
|
+
stmt.condition,
|
|
690
|
+
"negative",
|
|
691
|
+
stmt.elseBody === null ? thenExit : null
|
|
692
|
+
)
|
|
693
|
+
];
|
|
694
|
+
const out = [];
|
|
695
|
+
out.push(...enumerate(ctx, stmt.thenBody, thenPath));
|
|
696
|
+
if (stmt.elseBody !== null) {
|
|
697
|
+
out.push(...enumerate(ctx, stmt.elseBody, elsePath));
|
|
698
|
+
} else {
|
|
699
|
+
out.push(elsePath);
|
|
700
|
+
}
|
|
701
|
+
return out;
|
|
801
702
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
for (
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
file,
|
|
812
|
-
found === void 0 ? void 0 : [...new Set(found)].sort()
|
|
813
|
-
);
|
|
703
|
+
function stepSwitch(stmt, path4, ctx) {
|
|
704
|
+
const out = [];
|
|
705
|
+
const negations = [];
|
|
706
|
+
let defaultRanBody = false;
|
|
707
|
+
for (let i = 0; i < stmt.groups.length; i++) {
|
|
708
|
+
const group = stmt.groups[i];
|
|
709
|
+
const fallthroughSafe = i === stmt.groups.length - 1;
|
|
710
|
+
if (hasStrayBreak(group.body)) {
|
|
711
|
+
throw new UnmodeledFlow("non-trailing break in switch clause");
|
|
814
712
|
}
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
713
|
+
const groupPath = group.condition === null ? [...path4, ...negations] : [...path4, fixedCond(group.condition, "positive", "explicit")];
|
|
714
|
+
const conts = enumerate(ctx, group.body, groupPath);
|
|
715
|
+
if (conts.length > 0) {
|
|
716
|
+
if (!group.hasTrailingBreak && !fallthroughSafe) {
|
|
717
|
+
throw new UnmodeledFlow("fallthrough into a non-empty switch clause");
|
|
718
|
+
}
|
|
719
|
+
out.push(...conts);
|
|
720
|
+
}
|
|
721
|
+
if (group.condition === null) {
|
|
722
|
+
defaultRanBody = true;
|
|
723
|
+
} else {
|
|
724
|
+
const exit = exitKindOfList(group.body);
|
|
725
|
+
const negationSource = exit === "throw" ? "earlyThrow" : exit === "return" ? "earlyReturn" : "explicit";
|
|
726
|
+
negations.push(fixedCond(group.condition, "negative", negationSource));
|
|
818
727
|
}
|
|
819
|
-
summary.metadata = {
|
|
820
|
-
...summary.metadata ?? {},
|
|
821
|
-
moduleImports: imports
|
|
822
|
-
};
|
|
823
728
|
}
|
|
729
|
+
if (!defaultRanBody) {
|
|
730
|
+
out.push([...path4, ...negations]);
|
|
731
|
+
}
|
|
732
|
+
return out;
|
|
824
733
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
kind: "module-init",
|
|
833
|
-
file,
|
|
834
|
-
range,
|
|
835
|
-
exportName: null,
|
|
836
|
-
exportPath: null
|
|
837
|
-
},
|
|
838
|
-
boundaryBinding: null,
|
|
839
|
-
parameters: [],
|
|
840
|
-
branches: [
|
|
841
|
-
{
|
|
842
|
-
conditions: [],
|
|
843
|
-
terminal: {
|
|
844
|
-
kind: "void",
|
|
845
|
-
statusCode: null,
|
|
846
|
-
body: null,
|
|
847
|
-
exceptionType: null,
|
|
848
|
-
message: null,
|
|
849
|
-
component: null,
|
|
850
|
-
renderTree: null,
|
|
851
|
-
delegateTarget: null,
|
|
852
|
-
emitEvent: null,
|
|
853
|
-
location: range
|
|
854
|
-
},
|
|
855
|
-
effects: [],
|
|
856
|
-
extraEffects: effects,
|
|
857
|
-
location: range,
|
|
858
|
-
isDefault: true
|
|
859
|
-
}
|
|
860
|
-
],
|
|
861
|
-
unmatchedReturns: 0,
|
|
862
|
-
dependencyCalls: [],
|
|
863
|
-
declaredContract: null
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// src/packHelpers.ts
|
|
868
|
-
function httpRouteDiscovery(opts) {
|
|
869
|
-
const kind = opts.kind ?? "handler";
|
|
870
|
-
const calls = opts.importNames.map((importName) => ({
|
|
871
|
-
kind,
|
|
872
|
-
match: {
|
|
873
|
-
type: "registrationCall",
|
|
874
|
-
importModule: opts.importModule,
|
|
875
|
-
importName,
|
|
876
|
-
registrationChain: [...opts.methods]
|
|
877
|
-
},
|
|
878
|
-
bindingExtraction: {
|
|
879
|
-
// `.all` registers every method, so it records "*", which the pairing
|
|
880
|
-
// engine treats as agreeing with any method at all.
|
|
881
|
-
method: {
|
|
882
|
-
type: "fromRegistration",
|
|
883
|
-
position: "methodName",
|
|
884
|
-
nameMap: { all: "*" }
|
|
885
|
-
},
|
|
886
|
-
path: { type: "fromArgument", position: 0 }
|
|
887
|
-
},
|
|
888
|
-
...opts.mount !== void 0 ? { mount: opts.mount } : {},
|
|
889
|
-
requiresImport: [opts.importModule]
|
|
890
|
-
}));
|
|
891
|
-
if (opts.importNames.length === 0) {
|
|
892
|
-
return calls;
|
|
734
|
+
function stepLoop(stmt, path4, ctx) {
|
|
735
|
+
const bodyExit = exitKindOfList(stmt.body);
|
|
736
|
+
if (containsTerminal(ctx, stmt)) {
|
|
737
|
+
enumerate(ctx, stmt.body, [
|
|
738
|
+
...path4,
|
|
739
|
+
loopIterationCond(stmt.condition.sourceText)
|
|
740
|
+
]);
|
|
893
741
|
}
|
|
894
|
-
|
|
895
|
-
...
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
importModule: opts.importModule,
|
|
907
|
-
importNames: [...opts.importNames]
|
|
908
|
-
}
|
|
909
|
-
},
|
|
910
|
-
requiresImport: [opts.importModule]
|
|
742
|
+
if (bodyExit !== null) {
|
|
743
|
+
return [[...path4, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
|
|
744
|
+
}
|
|
745
|
+
return [path4];
|
|
746
|
+
}
|
|
747
|
+
function stepTry(stmt, path4, ctx) {
|
|
748
|
+
if (stmt.finallyBody !== null) {
|
|
749
|
+
const finallyHasTerminal = stmt.finallyBody.some(
|
|
750
|
+
(s) => containsTerminal(ctx, s)
|
|
751
|
+
);
|
|
752
|
+
if (exitKindOfList(stmt.finallyBody) !== null || finallyHasTerminal) {
|
|
753
|
+
throw new UnmodeledFlow("finally block with exits or terminals");
|
|
911
754
|
}
|
|
912
|
-
|
|
755
|
+
}
|
|
756
|
+
const out = [];
|
|
757
|
+
out.push(...enumerate(ctx, stmt.tryBody, path4));
|
|
758
|
+
if (stmt.catchBody !== null) {
|
|
759
|
+
const catchPath = [...path4, catchEntryCond()];
|
|
760
|
+
out.push(...enumerate(ctx, stmt.catchBody, catchPath));
|
|
761
|
+
}
|
|
762
|
+
return out;
|
|
913
763
|
}
|
|
914
|
-
function
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
importModule: opts.importModule,
|
|
921
|
-
importName,
|
|
922
|
-
registrationChain: []
|
|
923
|
-
},
|
|
924
|
-
wraps: { ...wraps },
|
|
925
|
-
requiresImport: [opts.importModule]
|
|
926
|
-
}))
|
|
927
|
-
);
|
|
764
|
+
function stepExit(stmt, path4, ctx) {
|
|
765
|
+
const collector = ctx.state.callbackExits[ctx.state.callbackExits.length - 1];
|
|
766
|
+
if (collector !== void 0 && (stmt.exit === "return" || stmt.exit === "throw")) {
|
|
767
|
+
collector.push(path4);
|
|
768
|
+
}
|
|
769
|
+
return [];
|
|
928
770
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
771
|
+
function stepOpaque(_stmt, path4, _ctx) {
|
|
772
|
+
return [path4];
|
|
773
|
+
}
|
|
774
|
+
var stepHandlers = {
|
|
775
|
+
if: stepIf,
|
|
776
|
+
switch: stepSwitch,
|
|
777
|
+
loop: stepLoop,
|
|
778
|
+
try: stepTry,
|
|
779
|
+
exit: stepExit,
|
|
780
|
+
opaque: stepOpaque
|
|
781
|
+
};
|
|
782
|
+
function stepCallback(ctx, body, path4) {
|
|
783
|
+
const exits = [];
|
|
784
|
+
ctx.state.callbackExits.push(exits);
|
|
785
|
+
let fellThrough;
|
|
786
|
+
try {
|
|
787
|
+
fellThrough = enumerate(ctx, body, path4);
|
|
788
|
+
} finally {
|
|
789
|
+
ctx.state.callbackExits.pop();
|
|
790
|
+
}
|
|
791
|
+
const out = [...fellThrough, ...exits];
|
|
792
|
+
return out.length === 0 ? [path4] : out;
|
|
945
793
|
}
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
794
|
+
function stepStatement(ctx, stmt, path4) {
|
|
795
|
+
let paths = [path4];
|
|
796
|
+
for (const body of stmt.callbacks ?? []) {
|
|
797
|
+
paths = paths.flatMap((each) => stepCallback(ctx, body, each));
|
|
798
|
+
}
|
|
799
|
+
const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
|
|
800
|
+
for (const each of withRejoined(paths)) {
|
|
801
|
+
for (const terminal of ownTerminals) {
|
|
802
|
+
recordTerminal(ctx, terminal, stmt, each);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const handler = stepHandlers[stmt.kind];
|
|
806
|
+
return paths.flatMap(
|
|
807
|
+
(each) => handler(
|
|
808
|
+
stmt,
|
|
809
|
+
each,
|
|
810
|
+
ctx
|
|
950
811
|
)
|
|
951
812
|
);
|
|
952
|
-
return {
|
|
953
|
-
find: { by: "subject" },
|
|
954
|
-
declare: (helpers) => ({
|
|
955
|
-
discovery: registrationHelperDiscovery(
|
|
956
|
-
helpers.flatMap((helper) => routesRegisteredBy(helper, methods, opts)),
|
|
957
|
-
opts.kind ?? "handler"
|
|
958
|
-
)
|
|
959
|
-
})
|
|
960
|
-
};
|
|
961
813
|
}
|
|
962
|
-
function
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
814
|
+
function soleDisagreement(left, right) {
|
|
815
|
+
if (left.length !== right.length) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
let found = null;
|
|
819
|
+
for (let at = 0; at < left.length; at++) {
|
|
820
|
+
const one = left[at];
|
|
821
|
+
const other = right[at];
|
|
822
|
+
if (one === other) {
|
|
968
823
|
continue;
|
|
969
824
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
subjectArgument = sink.receiver.position;
|
|
825
|
+
if (found !== null) {
|
|
826
|
+
return null;
|
|
973
827
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
return [];
|
|
977
|
-
}
|
|
978
|
-
return [
|
|
979
|
-
{
|
|
980
|
-
helperName: helper.name,
|
|
981
|
-
importModule: helper.file,
|
|
982
|
-
subject: {
|
|
983
|
-
argument: subjectArgument,
|
|
984
|
-
importModule: opts.importModule,
|
|
985
|
-
importNames: [...opts.importNames]
|
|
986
|
-
},
|
|
987
|
-
registrations
|
|
828
|
+
if (one === void 0 || other === void 0 || one.branchStmt === null || one.branchStmt !== other.branchStmt || one.info.polarity === other.info.polarity) {
|
|
829
|
+
return null;
|
|
988
830
|
}
|
|
989
|
-
|
|
831
|
+
found = at;
|
|
832
|
+
}
|
|
833
|
+
return found;
|
|
990
834
|
}
|
|
991
|
-
function
|
|
992
|
-
|
|
993
|
-
|
|
835
|
+
function mergeRejoined(paths) {
|
|
836
|
+
let current = paths;
|
|
837
|
+
for (let round = 0; round < current.length; round++) {
|
|
838
|
+
const out = [];
|
|
839
|
+
const taken = /* @__PURE__ */ new Set();
|
|
840
|
+
let mergedAny = false;
|
|
841
|
+
for (let i = 0; i < current.length; i++) {
|
|
842
|
+
if (taken.has(i)) {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
let combined = current[i];
|
|
846
|
+
for (let j = i + 1; j < current.length; j++) {
|
|
847
|
+
if (taken.has(j)) {
|
|
848
|
+
continue;
|
|
849
|
+
}
|
|
850
|
+
const at = soleDisagreement(combined, current[j]);
|
|
851
|
+
if (at === null) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
combined = [...combined.slice(0, at), ...combined.slice(at + 1)];
|
|
855
|
+
taken.add(j);
|
|
856
|
+
mergedAny = true;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
out.push(combined);
|
|
860
|
+
}
|
|
861
|
+
current = out;
|
|
862
|
+
if (!mergedAny) {
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
994
865
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
866
|
+
return current;
|
|
867
|
+
}
|
|
868
|
+
function withRejoined(paths) {
|
|
869
|
+
const merged = mergeRejoined(paths);
|
|
870
|
+
return merged.length === paths.length ? paths : [...merged, ...paths];
|
|
871
|
+
}
|
|
872
|
+
function enumerate(ctx, stmts, prefix) {
|
|
873
|
+
let frontiers = [[...prefix]];
|
|
874
|
+
for (const stmt of stmts) {
|
|
875
|
+
const nextFrontiers = [];
|
|
876
|
+
for (const path4 of mergeRejoined(frontiers)) {
|
|
877
|
+
nextFrontiers.push(...stepStatement(ctx, stmt, path4));
|
|
878
|
+
}
|
|
879
|
+
frontiers = nextFrontiers;
|
|
880
|
+
if (frontiers.length > MAX_PATHS) {
|
|
881
|
+
throw new PathBudgetExceeded(
|
|
882
|
+
`path budget exceeded, more than ${MAX_PATHS} paths`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
if (frontiers.length === 0) {
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
998
888
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
889
|
+
return frontiers;
|
|
890
|
+
}
|
|
891
|
+
function buildParentMap(statements) {
|
|
892
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
893
|
+
const visit = (stmt, parent) => {
|
|
894
|
+
parentOf.set(stmt, parent);
|
|
895
|
+
for (const child of childrenOf(stmt)) {
|
|
896
|
+
visit(child, stmt);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
for (const stmt of statements) {
|
|
900
|
+
visit(stmt, null);
|
|
1003
901
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
902
|
+
return parentOf;
|
|
903
|
+
}
|
|
904
|
+
function enumerateStructuredPaths(input) {
|
|
905
|
+
const parentOf = buildParentMap(input.statements);
|
|
906
|
+
const isAncestorOrSelf = (a, b) => {
|
|
907
|
+
let current = b;
|
|
908
|
+
while (current !== null) {
|
|
909
|
+
if (current === a) {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
current = parentOf.get(current) ?? null;
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
};
|
|
916
|
+
const state = {
|
|
917
|
+
byTerminal: /* @__PURE__ */ new Map(),
|
|
918
|
+
fallthrough: [],
|
|
919
|
+
pathCount: 0,
|
|
920
|
+
callbackExits: []
|
|
921
|
+
};
|
|
922
|
+
const ctx = {
|
|
923
|
+
terminalsByStmt: input.terminalsByStmt,
|
|
924
|
+
state,
|
|
925
|
+
isAncestorOrSelf
|
|
1011
926
|
};
|
|
927
|
+
const continuations = withRejoined(enumerate(ctx, input.statements, []));
|
|
928
|
+
for (const path4 of continuations) {
|
|
929
|
+
chargeBudget(state);
|
|
930
|
+
state.fallthrough.push(classify(path4, null, isAncestorOrSelf));
|
|
931
|
+
}
|
|
932
|
+
return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
|
|
1012
933
|
}
|
|
1013
|
-
function
|
|
1014
|
-
|
|
1015
|
-
return null;
|
|
934
|
+
function enumerateOrDegrade(input, terminals) {
|
|
935
|
+
try {
|
|
936
|
+
return { ...enumerateStructuredPaths(input), degraded: null };
|
|
937
|
+
} catch (error) {
|
|
938
|
+
const reason = reasonToDegrade(error);
|
|
939
|
+
if (reason === null) {
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
const marker = {
|
|
943
|
+
sourceText: `unmodeled control flow (${reason})`,
|
|
944
|
+
polarity: "positive",
|
|
945
|
+
source: "explicit",
|
|
946
|
+
expression: null
|
|
947
|
+
};
|
|
948
|
+
const byTerminal = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const terminal of terminals) {
|
|
950
|
+
byTerminal.set(terminal, [[marker]]);
|
|
951
|
+
}
|
|
952
|
+
return { byTerminal, fallthrough: [[marker]], degraded: reason };
|
|
1016
953
|
}
|
|
1017
|
-
return value.property === void 0 ? `{${value.position}}` : `{${value.position}}.${value.property}`;
|
|
1018
954
|
}
|
|
1019
|
-
function
|
|
1020
|
-
if (
|
|
1021
|
-
return
|
|
955
|
+
function reasonToDegrade(error) {
|
|
956
|
+
if (error instanceof PathBudgetExceeded) {
|
|
957
|
+
return error.message === "" ? "path budget exceeded" : error.message;
|
|
1022
958
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
return body;
|
|
959
|
+
if (error instanceof UnmodeledFlow) {
|
|
960
|
+
return error.message;
|
|
1026
961
|
}
|
|
1027
|
-
|
|
1028
|
-
return inner ?? body;
|
|
962
|
+
return null;
|
|
1029
963
|
}
|
|
1030
964
|
|
|
1031
|
-
// src/packOptions.ts
|
|
1032
|
-
import { z } from "zod";
|
|
1033
|
-
var storageSystemOption = z.enum([
|
|
1034
|
-
"postgresql",
|
|
1035
|
-
"mysql",
|
|
1036
|
-
"sqlite"
|
|
1037
|
-
]);
|
|
1038
|
-
var scopeOption = z.string();
|
|
1039
|
-
var configuredCallOption = z.object({
|
|
1040
|
-
/** Module that declares the receiver's type. */
|
|
1041
|
-
module: z.string(),
|
|
1042
|
-
/** Type name of the receiver, as exported from that module. */
|
|
1043
|
-
receiver: z.string(),
|
|
1044
|
-
/** Method that performs the send. */
|
|
1045
|
-
method: z.string(),
|
|
1046
|
-
/** Argument index carrying the subject. */
|
|
1047
|
-
subjectArg: z.number(),
|
|
1048
|
-
/**
|
|
1049
|
-
* Which argument the message body is. Left out when the method has no
|
|
1050
|
-
* single body argument, as a batch method taking a list of entries
|
|
1051
|
-
* does, and then no body is reported.
|
|
1052
|
-
*/
|
|
1053
|
-
bodyArg: z.number().optional()
|
|
1054
|
-
}).strict();
|
|
1055
|
-
|
|
1056
965
|
// src/paths/gatingConditions.ts
|
|
1057
966
|
var keyOf2 = (condition) => `${condition.polarity}:${condition.sourceText}`;
|
|
1058
967
|
function sharedGatingConditions(paths, readPredicate) {
|