@suss/extractor 0.3.2 → 0.5.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 +2 -2
- package/dist/index.cjs +630 -17
- package/dist/index.d.cts +962 -571
- package/dist/index.d.ts +962 -571
- package/dist/index.js +618 -16
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,17 +20,33 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
IdMap: () => IdMap,
|
|
24
|
+
IdSet: () => IdSet,
|
|
25
|
+
PathBudgetExceeded: () => PathBudgetExceeded,
|
|
26
|
+
UnmodeledFlow: () => UnmodeledFlow,
|
|
27
|
+
absentReading: () => absentReading,
|
|
28
|
+
ambiguousReading: () => ambiguousReading,
|
|
29
|
+
andThenReading: () => andThenReading,
|
|
23
30
|
assembleSummary: () => assembleSummary,
|
|
24
31
|
assessConfidence: () => assessConfidence,
|
|
25
32
|
detectGaps: () => detectGaps,
|
|
26
33
|
effectToIR: () => effectToIR,
|
|
34
|
+
enumerateOrDegrade: () => enumerateOrDegrade,
|
|
35
|
+
enumerateStructuredPaths: () => enumerateStructuredPaths,
|
|
36
|
+
firstWrittenReading: () => firstWrittenReading,
|
|
27
37
|
httpRouteDiscovery: () => httpRouteDiscovery,
|
|
28
38
|
makeTransitionId: () => makeTransitionId,
|
|
39
|
+
mapReading: () => mapReading,
|
|
29
40
|
paramToInput: () => paramToInput,
|
|
30
|
-
|
|
41
|
+
sharedGatingConditions: () => sharedGatingConditions,
|
|
42
|
+
terminalToOutput: () => terminalToOutput,
|
|
43
|
+
unreadableReading: () => unreadableReading,
|
|
44
|
+
valueToReadFurtherFrom: () => valueToReadFurtherFrom,
|
|
45
|
+
writtenReading: () => writtenReading
|
|
31
46
|
});
|
|
32
47
|
module.exports = __toCommonJS(index_exports);
|
|
33
48
|
var import_node_crypto = require("crypto");
|
|
49
|
+
var import_behavioral_ir = require("@suss/behavioral-ir");
|
|
34
50
|
|
|
35
51
|
// src/inputReads.ts
|
|
36
52
|
function inputReadsOf(args) {
|
|
@@ -124,13 +140,525 @@ function httpRouteDiscovery(opts) {
|
|
|
124
140
|
registrationChain: [...opts.methods]
|
|
125
141
|
},
|
|
126
142
|
bindingExtraction: {
|
|
127
|
-
method
|
|
143
|
+
// `.all` registers every method, so it records "*", which the pairing
|
|
144
|
+
// engine treats as agreeing with any method at all.
|
|
145
|
+
method: {
|
|
146
|
+
type: "fromRegistration",
|
|
147
|
+
position: "methodName",
|
|
148
|
+
nameMap: { all: "*" }
|
|
149
|
+
},
|
|
128
150
|
path: { type: "fromRegistration", position: 0 }
|
|
129
151
|
},
|
|
152
|
+
...opts.mount !== void 0 ? { mount: opts.mount } : {},
|
|
130
153
|
requiresImport: [opts.importModule]
|
|
131
154
|
}));
|
|
132
155
|
}
|
|
133
156
|
|
|
157
|
+
// src/paths/enumeratePaths.ts
|
|
158
|
+
var MAX_PATHS = 256;
|
|
159
|
+
var UnmodeledFlow = class extends Error {
|
|
160
|
+
};
|
|
161
|
+
var PathBudgetExceeded = class extends Error {
|
|
162
|
+
};
|
|
163
|
+
function branchCond(branchStmt, condition, polarity, oppositeExit) {
|
|
164
|
+
return {
|
|
165
|
+
info: {
|
|
166
|
+
sourceText: condition.sourceText,
|
|
167
|
+
polarity,
|
|
168
|
+
source: "explicit",
|
|
169
|
+
expression: condition.expression
|
|
170
|
+
},
|
|
171
|
+
branchStmt,
|
|
172
|
+
oppositeExit
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function fixedCond(condition, polarity, source) {
|
|
176
|
+
return {
|
|
177
|
+
info: {
|
|
178
|
+
sourceText: condition.sourceText,
|
|
179
|
+
polarity,
|
|
180
|
+
source,
|
|
181
|
+
expression: condition.expression
|
|
182
|
+
},
|
|
183
|
+
branchStmt: null,
|
|
184
|
+
oppositeExit: null
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
var loopIterationCond = (headerText) => fixedCond(
|
|
188
|
+
{ sourceText: `some iteration of: ${headerText}`, expression: null },
|
|
189
|
+
"positive",
|
|
190
|
+
"explicit"
|
|
191
|
+
);
|
|
192
|
+
var loopCompletedCond = (headerText, exit) => fixedCond(
|
|
193
|
+
{
|
|
194
|
+
sourceText: `loop exited via ${exit ?? "return"}: ${headerText}`,
|
|
195
|
+
expression: null
|
|
196
|
+
},
|
|
197
|
+
"negative",
|
|
198
|
+
exit === "throw" ? "earlyThrow" : "earlyReturn"
|
|
199
|
+
);
|
|
200
|
+
var catchEntryCond = () => fixedCond(
|
|
201
|
+
{ sourceText: "catch", expression: null },
|
|
202
|
+
"positive",
|
|
203
|
+
"catchBlock"
|
|
204
|
+
);
|
|
205
|
+
function classify(path, home, isAncestorOrSelf) {
|
|
206
|
+
return path.map((cond) => {
|
|
207
|
+
if (cond.branchStmt === null) {
|
|
208
|
+
return cond.info;
|
|
209
|
+
}
|
|
210
|
+
const encloses = home !== null && isAncestorOrSelf(cond.branchStmt, home);
|
|
211
|
+
if (encloses || cond.info.polarity === "positive") {
|
|
212
|
+
return { ...cond.info, source: "explicit" };
|
|
213
|
+
}
|
|
214
|
+
const source = cond.oppositeExit === "throw" ? "earlyThrow" : "earlyReturn";
|
|
215
|
+
return { ...cond.info, source };
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function childrenOf(stmt) {
|
|
219
|
+
if (stmt.kind === "if") {
|
|
220
|
+
return stmt.elseBody === null ? stmt.thenBody : [...stmt.thenBody, ...stmt.elseBody];
|
|
221
|
+
}
|
|
222
|
+
if (stmt.kind === "switch") {
|
|
223
|
+
return stmt.groups.flatMap((g) => g.body);
|
|
224
|
+
}
|
|
225
|
+
if (stmt.kind === "loop") {
|
|
226
|
+
return stmt.body;
|
|
227
|
+
}
|
|
228
|
+
if (stmt.kind === "try") {
|
|
229
|
+
return [
|
|
230
|
+
...stmt.tryBody,
|
|
231
|
+
...stmt.catchBody ?? [],
|
|
232
|
+
...stmt.finallyBody ?? []
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
function hasStrayBreak(nodes) {
|
|
238
|
+
return nodes.some((node) => {
|
|
239
|
+
if (node.kind === "exit" && node.exit === "break") {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (node.kind === "switch" || node.kind === "loop") {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return hasStrayBreak(childrenOf(node));
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function exitKindOfList(stmts) {
|
|
249
|
+
let sawReturn = false;
|
|
250
|
+
for (const stmt of stmts) {
|
|
251
|
+
if (stmt.exitKind === "throw") {
|
|
252
|
+
return "throw";
|
|
253
|
+
}
|
|
254
|
+
if (stmt.exitKind === "return") {
|
|
255
|
+
sawReturn = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return sawReturn ? "return" : null;
|
|
259
|
+
}
|
|
260
|
+
function chargeBudget(state) {
|
|
261
|
+
state.pathCount++;
|
|
262
|
+
if (state.pathCount > MAX_PATHS) {
|
|
263
|
+
throw new PathBudgetExceeded(
|
|
264
|
+
`path budget exceeded, more than ${MAX_PATHS} paths`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function recordTerminal(ctx, terminal, home, path) {
|
|
269
|
+
chargeBudget(ctx.state);
|
|
270
|
+
const info = classify(path, home, ctx.isAncestorOrSelf);
|
|
271
|
+
const existing = ctx.state.byTerminal.get(terminal) ?? [];
|
|
272
|
+
existing.push(info);
|
|
273
|
+
ctx.state.byTerminal.set(terminal, existing);
|
|
274
|
+
}
|
|
275
|
+
function containsTerminal(ctx, stmt) {
|
|
276
|
+
if ((ctx.terminalsByStmt.get(stmt)?.length ?? 0) > 0) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
return childrenOf(stmt).some((child) => containsTerminal(ctx, child));
|
|
280
|
+
}
|
|
281
|
+
function stepIf(stmt, path, ctx) {
|
|
282
|
+
const armsExit = stmt.exitKind !== null;
|
|
283
|
+
const armsHaveTerminals = containsTerminal(ctx, stmt);
|
|
284
|
+
if (!armsExit && !armsHaveTerminals) {
|
|
285
|
+
return [path];
|
|
286
|
+
}
|
|
287
|
+
const thenExit = exitKindOfList(stmt.thenBody);
|
|
288
|
+
const elseExit = stmt.elseBody === null ? null : exitKindOfList(stmt.elseBody);
|
|
289
|
+
const thenPath = [
|
|
290
|
+
...path,
|
|
291
|
+
branchCond(stmt, stmt.condition, "positive", elseExit)
|
|
292
|
+
];
|
|
293
|
+
const elsePath = [
|
|
294
|
+
...path,
|
|
295
|
+
branchCond(stmt, stmt.condition, "negative", thenExit)
|
|
296
|
+
];
|
|
297
|
+
const out = [];
|
|
298
|
+
out.push(...enumerate(ctx, stmt.thenBody, thenPath));
|
|
299
|
+
if (stmt.elseBody !== null) {
|
|
300
|
+
out.push(...enumerate(ctx, stmt.elseBody, elsePath));
|
|
301
|
+
} else {
|
|
302
|
+
out.push(elsePath);
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
function stepSwitch(stmt, path, ctx) {
|
|
307
|
+
const out = [];
|
|
308
|
+
const negations = [];
|
|
309
|
+
let defaultRanBody = false;
|
|
310
|
+
for (let i = 0; i < stmt.groups.length; i++) {
|
|
311
|
+
const group = stmt.groups[i];
|
|
312
|
+
const fallthroughSafe = i === stmt.groups.length - 1;
|
|
313
|
+
if (hasStrayBreak(group.body)) {
|
|
314
|
+
throw new UnmodeledFlow("non-trailing break in switch clause");
|
|
315
|
+
}
|
|
316
|
+
const groupPath = group.condition === null ? [...path, ...negations] : [...path, fixedCond(group.condition, "positive", "explicit")];
|
|
317
|
+
const conts = enumerate(ctx, group.body, groupPath);
|
|
318
|
+
if (conts.length > 0) {
|
|
319
|
+
if (!group.hasTrailingBreak && !fallthroughSafe) {
|
|
320
|
+
throw new UnmodeledFlow("fallthrough into a non-empty switch clause");
|
|
321
|
+
}
|
|
322
|
+
out.push(...conts);
|
|
323
|
+
}
|
|
324
|
+
if (group.condition === null) {
|
|
325
|
+
defaultRanBody = true;
|
|
326
|
+
} else {
|
|
327
|
+
const exit = exitKindOfList(group.body);
|
|
328
|
+
const negationSource = exit === "throw" ? "earlyThrow" : exit === "return" ? "earlyReturn" : "explicit";
|
|
329
|
+
negations.push(fixedCond(group.condition, "negative", negationSource));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (!defaultRanBody) {
|
|
333
|
+
out.push([...path, ...negations]);
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
function stepLoop(stmt, path, ctx) {
|
|
338
|
+
const bodyExit = exitKindOfList(stmt.body);
|
|
339
|
+
if (containsTerminal(ctx, stmt)) {
|
|
340
|
+
enumerate(ctx, stmt.body, [
|
|
341
|
+
...path,
|
|
342
|
+
loopIterationCond(stmt.condition.sourceText)
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
if (bodyExit !== null) {
|
|
346
|
+
return [[...path, loopCompletedCond(stmt.condition.sourceText, bodyExit)]];
|
|
347
|
+
}
|
|
348
|
+
return [path];
|
|
349
|
+
}
|
|
350
|
+
function stepTry(stmt, path, ctx) {
|
|
351
|
+
if (stmt.finallyBody !== null) {
|
|
352
|
+
const finallyHasTerminal = stmt.finallyBody.some(
|
|
353
|
+
(s) => containsTerminal(ctx, s)
|
|
354
|
+
);
|
|
355
|
+
if (exitKindOfList(stmt.finallyBody) !== null || finallyHasTerminal) {
|
|
356
|
+
throw new UnmodeledFlow("finally block with exits or terminals");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const out = [];
|
|
360
|
+
out.push(...enumerate(ctx, stmt.tryBody, path));
|
|
361
|
+
if (stmt.catchBody !== null) {
|
|
362
|
+
const catchPath = [...path, catchEntryCond()];
|
|
363
|
+
out.push(...enumerate(ctx, stmt.catchBody, catchPath));
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
function stepExit(_stmt, _path, _ctx) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
function stepOpaque(_stmt, path, _ctx) {
|
|
371
|
+
return [path];
|
|
372
|
+
}
|
|
373
|
+
var stepHandlers = {
|
|
374
|
+
if: stepIf,
|
|
375
|
+
switch: stepSwitch,
|
|
376
|
+
loop: stepLoop,
|
|
377
|
+
try: stepTry,
|
|
378
|
+
exit: stepExit,
|
|
379
|
+
opaque: stepOpaque
|
|
380
|
+
};
|
|
381
|
+
function stepStatement(ctx, stmt, path) {
|
|
382
|
+
const ownTerminals = ctx.terminalsByStmt.get(stmt) ?? [];
|
|
383
|
+
for (const terminal of ownTerminals) {
|
|
384
|
+
recordTerminal(ctx, terminal, stmt, path);
|
|
385
|
+
}
|
|
386
|
+
const handler = stepHandlers[stmt.kind];
|
|
387
|
+
return handler(
|
|
388
|
+
stmt,
|
|
389
|
+
path,
|
|
390
|
+
ctx
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
function soleDisagreement(left, right) {
|
|
394
|
+
if (left.length !== right.length) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
let found = null;
|
|
398
|
+
for (let at = 0; at < left.length; at++) {
|
|
399
|
+
const one = left[at];
|
|
400
|
+
const other = right[at];
|
|
401
|
+
if (one === other) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (found !== null) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
if (one === void 0 || other === void 0 || one.branchStmt === null || one.branchStmt !== other.branchStmt || one.info.polarity === other.info.polarity) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
found = at;
|
|
411
|
+
}
|
|
412
|
+
return found;
|
|
413
|
+
}
|
|
414
|
+
function mergeRejoined(paths) {
|
|
415
|
+
let current = paths;
|
|
416
|
+
for (let round = 0; round < current.length; round++) {
|
|
417
|
+
const out = [];
|
|
418
|
+
const taken = /* @__PURE__ */ new Set();
|
|
419
|
+
let mergedAny = false;
|
|
420
|
+
for (let i = 0; i < current.length; i++) {
|
|
421
|
+
if (taken.has(i)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
let combined = current[i];
|
|
425
|
+
for (let j = i + 1; j < current.length; j++) {
|
|
426
|
+
if (taken.has(j)) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const at = soleDisagreement(combined, current[j]);
|
|
430
|
+
if (at === null) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
combined = [...combined.slice(0, at), ...combined.slice(at + 1)];
|
|
434
|
+
taken.add(j);
|
|
435
|
+
mergedAny = true;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
out.push(combined);
|
|
439
|
+
}
|
|
440
|
+
current = out;
|
|
441
|
+
if (!mergedAny) {
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return current;
|
|
446
|
+
}
|
|
447
|
+
function enumerate(ctx, stmts, prefix) {
|
|
448
|
+
let frontiers = [[...prefix]];
|
|
449
|
+
for (const stmt of stmts) {
|
|
450
|
+
const nextFrontiers = [];
|
|
451
|
+
for (const path of frontiers) {
|
|
452
|
+
nextFrontiers.push(...stepStatement(ctx, stmt, path));
|
|
453
|
+
}
|
|
454
|
+
frontiers = mergeRejoined(nextFrontiers);
|
|
455
|
+
if (frontiers.length > MAX_PATHS) {
|
|
456
|
+
throw new PathBudgetExceeded(
|
|
457
|
+
`path budget exceeded, more than ${MAX_PATHS} paths`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
if (frontiers.length === 0) {
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return frontiers;
|
|
465
|
+
}
|
|
466
|
+
function buildParentMap(statements) {
|
|
467
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
468
|
+
const visit = (stmt, parent) => {
|
|
469
|
+
parentOf.set(stmt, parent);
|
|
470
|
+
for (const child of childrenOf(stmt)) {
|
|
471
|
+
visit(child, stmt);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
for (const stmt of statements) {
|
|
475
|
+
visit(stmt, null);
|
|
476
|
+
}
|
|
477
|
+
return parentOf;
|
|
478
|
+
}
|
|
479
|
+
function enumerateStructuredPaths(input) {
|
|
480
|
+
const parentOf = buildParentMap(input.statements);
|
|
481
|
+
const isAncestorOrSelf = (a, b) => {
|
|
482
|
+
let current = b;
|
|
483
|
+
while (current !== null) {
|
|
484
|
+
if (current === a) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
current = parentOf.get(current) ?? null;
|
|
488
|
+
}
|
|
489
|
+
return false;
|
|
490
|
+
};
|
|
491
|
+
const state = {
|
|
492
|
+
byTerminal: /* @__PURE__ */ new Map(),
|
|
493
|
+
fallthrough: [],
|
|
494
|
+
pathCount: 0
|
|
495
|
+
};
|
|
496
|
+
const ctx = {
|
|
497
|
+
terminalsByStmt: input.terminalsByStmt,
|
|
498
|
+
state,
|
|
499
|
+
isAncestorOrSelf
|
|
500
|
+
};
|
|
501
|
+
const continuations = enumerate(ctx, input.statements, []);
|
|
502
|
+
for (const path of continuations) {
|
|
503
|
+
chargeBudget(state);
|
|
504
|
+
state.fallthrough.push(classify(path, null, isAncestorOrSelf));
|
|
505
|
+
}
|
|
506
|
+
return { byTerminal: state.byTerminal, fallthrough: state.fallthrough };
|
|
507
|
+
}
|
|
508
|
+
function enumerateOrDegrade(input, terminals) {
|
|
509
|
+
try {
|
|
510
|
+
return { ...enumerateStructuredPaths(input), degraded: null };
|
|
511
|
+
} catch (error) {
|
|
512
|
+
const reason = reasonToDegrade(error);
|
|
513
|
+
if (reason === null) {
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
const marker = {
|
|
517
|
+
sourceText: `unmodeled control flow (${reason})`,
|
|
518
|
+
polarity: "positive",
|
|
519
|
+
source: "explicit",
|
|
520
|
+
expression: null
|
|
521
|
+
};
|
|
522
|
+
const byTerminal = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const terminal of terminals) {
|
|
524
|
+
byTerminal.set(terminal, [[marker]]);
|
|
525
|
+
}
|
|
526
|
+
return { byTerminal, fallthrough: [[marker]], degraded: reason };
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function reasonToDegrade(error) {
|
|
530
|
+
if (error instanceof PathBudgetExceeded) {
|
|
531
|
+
return error.message === "" ? "path budget exceeded" : error.message;
|
|
532
|
+
}
|
|
533
|
+
if (error instanceof UnmodeledFlow) {
|
|
534
|
+
return error.message;
|
|
535
|
+
}
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/paths/gatingConditions.ts
|
|
540
|
+
var keyOf = (condition) => `${condition.polarity}:${condition.sourceText}`;
|
|
541
|
+
function sharedGatingConditions(paths, readPredicate) {
|
|
542
|
+
const [first, ...rest] = paths ?? [];
|
|
543
|
+
if (first === void 0) {
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
const shared = new Set(first.map(keyOf));
|
|
547
|
+
for (const path of rest) {
|
|
548
|
+
const here = new Set(path.map(keyOf));
|
|
549
|
+
for (const value of shared) {
|
|
550
|
+
if (!here.has(value)) {
|
|
551
|
+
shared.delete(value);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return first.filter((condition) => shared.has(keyOf(condition))).map((condition) => ({
|
|
556
|
+
sourceText: condition.sourceText,
|
|
557
|
+
structured: readPredicate === void 0 || condition.expression === null ? null : readPredicate(condition.expression),
|
|
558
|
+
polarity: condition.polarity,
|
|
559
|
+
source: condition.source
|
|
560
|
+
}));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/paths/nodeIdentity.ts
|
|
564
|
+
var IdSet = class {
|
|
565
|
+
byKey = /* @__PURE__ */ new Map();
|
|
566
|
+
constructor(nodes = []) {
|
|
567
|
+
for (const node of nodes) {
|
|
568
|
+
this.add(node);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
add(node) {
|
|
572
|
+
this.byKey.set(node.id, node);
|
|
573
|
+
return this;
|
|
574
|
+
}
|
|
575
|
+
has(node) {
|
|
576
|
+
return this.byKey.has(node.id);
|
|
577
|
+
}
|
|
578
|
+
/** The node this set was built with, which is the caller's own handle for it. */
|
|
579
|
+
get(node) {
|
|
580
|
+
return this.byKey.get(node.id);
|
|
581
|
+
}
|
|
582
|
+
get size() {
|
|
583
|
+
return this.byKey.size;
|
|
584
|
+
}
|
|
585
|
+
[Symbol.iterator]() {
|
|
586
|
+
return this.byKey.values();
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
var IdMap = class {
|
|
590
|
+
entries = /* @__PURE__ */ new Map();
|
|
591
|
+
set(node, value) {
|
|
592
|
+
this.entries.set(node.id, [node, value]);
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
get(node) {
|
|
596
|
+
return this.entries.get(node.id)?.[1];
|
|
597
|
+
}
|
|
598
|
+
has(node) {
|
|
599
|
+
return this.entries.has(node.id);
|
|
600
|
+
}
|
|
601
|
+
get size() {
|
|
602
|
+
return this.entries.size;
|
|
603
|
+
}
|
|
604
|
+
[Symbol.iterator]() {
|
|
605
|
+
return this.entries.values();
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/reading.ts
|
|
610
|
+
function writtenReading(value, range) {
|
|
611
|
+
return { kind: "written", value, range };
|
|
612
|
+
}
|
|
613
|
+
var absentReading = { kind: "absent" };
|
|
614
|
+
function unreadableReading(reason, range) {
|
|
615
|
+
return { kind: "unreadable", reason, range };
|
|
616
|
+
}
|
|
617
|
+
function ambiguousReading(candidates, reason, range) {
|
|
618
|
+
return { kind: "ambiguous", candidates, reason, range };
|
|
619
|
+
}
|
|
620
|
+
function dispatchReading(table, reading) {
|
|
621
|
+
return table[reading.kind](reading);
|
|
622
|
+
}
|
|
623
|
+
function mapReading(reading, f) {
|
|
624
|
+
const table = {
|
|
625
|
+
written: (r) => writtenReading(f(r.value), r.range),
|
|
626
|
+
absent: () => absentReading,
|
|
627
|
+
unreadable: (r) => unreadableReading(r.reason, r.range),
|
|
628
|
+
ambiguous: (r) => ambiguousReading(r.candidates.map(f), r.reason, r.range)
|
|
629
|
+
};
|
|
630
|
+
return dispatchReading(table, reading);
|
|
631
|
+
}
|
|
632
|
+
function andThenReading(reading, f) {
|
|
633
|
+
const table = {
|
|
634
|
+
written: (r) => f(r.value, r.range),
|
|
635
|
+
absent: () => absentReading,
|
|
636
|
+
unreadable: (r) => unreadableReading(r.reason, r.range),
|
|
637
|
+
ambiguous: (r) => ambiguousReading(
|
|
638
|
+
r.candidates.flatMap((candidate) => {
|
|
639
|
+
const next = f(candidate, r.range);
|
|
640
|
+
return next.kind === "written" ? [next.value] : [];
|
|
641
|
+
}),
|
|
642
|
+
r.reason,
|
|
643
|
+
r.range
|
|
644
|
+
)
|
|
645
|
+
};
|
|
646
|
+
return dispatchReading(table, reading);
|
|
647
|
+
}
|
|
648
|
+
function firstWrittenReading(readings) {
|
|
649
|
+
const written = readings.find((reading) => reading.kind === "written");
|
|
650
|
+
const chosen = written ?? readings.find((reading) => reading.kind !== "absent") ?? absentReading;
|
|
651
|
+
return {
|
|
652
|
+
reading: chosen,
|
|
653
|
+
passedOver: readings.filter(
|
|
654
|
+
(reading) => reading !== chosen && (reading.kind === "unreadable" || reading.kind === "ambiguous")
|
|
655
|
+
)
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function valueToReadFurtherFrom(reading) {
|
|
659
|
+
return reading.kind === "written" ? reading.value : null;
|
|
660
|
+
}
|
|
661
|
+
|
|
134
662
|
// src/index.ts
|
|
135
663
|
function valuesOfOutput(output) {
|
|
136
664
|
if (output.type === "response" && output.statusCode !== null) {
|
|
@@ -146,8 +674,69 @@ function makeTransitionId(functionName, branch) {
|
|
|
146
674
|
const conditionHash = (0, import_node_crypto.createHash)("sha1").update(conditionSig).digest("hex").slice(0, 7);
|
|
147
675
|
return `${functionName}:${terminal.kind}:${statusKey}:${conditionHash}`;
|
|
148
676
|
}
|
|
677
|
+
function collapseReading(table, reading) {
|
|
678
|
+
return table[reading.kind](reading);
|
|
679
|
+
}
|
|
680
|
+
function claimedValue(defaulted) {
|
|
681
|
+
const table = {
|
|
682
|
+
written: (r) => r.value,
|
|
683
|
+
absent: () => defaulted.libraryDefault ?? null,
|
|
684
|
+
unreadable: () => null,
|
|
685
|
+
ambiguous: () => null
|
|
686
|
+
};
|
|
687
|
+
return collapseReading(table, defaulted.reading);
|
|
688
|
+
}
|
|
689
|
+
function unreadReasonOf(reading) {
|
|
690
|
+
const table = {
|
|
691
|
+
written: () => null,
|
|
692
|
+
absent: () => null,
|
|
693
|
+
unreadable: (r) => r.reason,
|
|
694
|
+
ambiguous: (r) => r.reason
|
|
695
|
+
};
|
|
696
|
+
return collapseReading(table, reading);
|
|
697
|
+
}
|
|
698
|
+
function bodyWithClaimedShape(terminal, reading) {
|
|
699
|
+
return {
|
|
700
|
+
typeText: terminal.body?.typeText ?? null,
|
|
701
|
+
shape: claimedValue(reading)
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function branchWithCollapsedReadings(branch) {
|
|
705
|
+
const { statusCodeReading, bodyShapeReading } = branch;
|
|
706
|
+
if (statusCodeReading === void 0 && bodyShapeReading === void 0) {
|
|
707
|
+
return branch;
|
|
708
|
+
}
|
|
709
|
+
const status = statusCodeReading !== void 0 ? claimedValue(statusCodeReading) : null;
|
|
710
|
+
return {
|
|
711
|
+
...branch,
|
|
712
|
+
terminal: {
|
|
713
|
+
...branch.terminal,
|
|
714
|
+
...statusCodeReading !== void 0 ? {
|
|
715
|
+
statusCode: status !== null ? { type: "literal", value: status } : null
|
|
716
|
+
} : {},
|
|
717
|
+
...bodyShapeReading !== void 0 ? { body: bodyWithClaimedShape(branch.terminal, bodyShapeReading) } : {}
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function readingsOfBranch(branch) {
|
|
722
|
+
return [
|
|
723
|
+
...branch.bodyShapeReading !== void 0 ? [branch.bodyShapeReading.reading] : [],
|
|
724
|
+
...branch.statusCodeReading !== void 0 ? [branch.statusCodeReading.reading] : []
|
|
725
|
+
];
|
|
726
|
+
}
|
|
727
|
+
function unreadSentences(raw) {
|
|
728
|
+
const handedOver = [
|
|
729
|
+
...raw.readings ?? [],
|
|
730
|
+
...raw.branches.flatMap(readingsOfBranch)
|
|
731
|
+
];
|
|
732
|
+
return [
|
|
733
|
+
...raw.unreadBinding !== void 0 ? [raw.unreadBinding] : [],
|
|
734
|
+
...handedOver.map(unreadReasonOf).filter((reason) => reason !== null)
|
|
735
|
+
];
|
|
736
|
+
}
|
|
149
737
|
function assembleSummary(raw, options = DEFAULT_OPTIONS) {
|
|
150
|
-
const transitions = raw.branches.map((
|
|
738
|
+
const transitions = raw.branches.map((rawBranch) => {
|
|
739
|
+
const branch = branchWithCollapsedReadings(rawBranch);
|
|
151
740
|
const conditions = branch.conditions.map(
|
|
152
741
|
rawConditionToPredicate
|
|
153
742
|
);
|
|
@@ -184,6 +773,7 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
|
|
|
184
773
|
},
|
|
185
774
|
identity: {
|
|
186
775
|
name: raw.identity.name,
|
|
776
|
+
...raw.identity.nameKind !== void 0 ? { nameKind: raw.identity.nameKind } : {},
|
|
187
777
|
exportPath: raw.identity.exportPath,
|
|
188
778
|
boundaryBinding: raw.boundaryBinding ?? null,
|
|
189
779
|
...raw.deployableUnit !== void 0 ? { deployableUnit: raw.deployableUnit } : {}
|
|
@@ -198,21 +788,25 @@ function assembleSummary(raw, options = DEFAULT_OPTIONS) {
|
|
|
198
788
|
};
|
|
199
789
|
}
|
|
200
790
|
function buildMetadata(raw) {
|
|
201
|
-
|
|
202
|
-
const http =
|
|
791
|
+
let metadata = {};
|
|
792
|
+
const http = buildHttpMetadataValue(raw);
|
|
203
793
|
if (http !== null) {
|
|
204
|
-
metadata
|
|
794
|
+
metadata = (0, import_behavioral_ir.withHttpMetadata)(metadata, http);
|
|
205
795
|
}
|
|
206
|
-
const graphql =
|
|
796
|
+
const graphql = buildGraphqlMetadataValue(raw);
|
|
207
797
|
if (graphql !== null) {
|
|
208
|
-
metadata
|
|
798
|
+
metadata = (0, import_behavioral_ir.withGraphqlMetadata)(metadata, graphql);
|
|
209
799
|
}
|
|
210
800
|
return Object.keys(metadata).length > 0 ? metadata : null;
|
|
211
801
|
}
|
|
212
|
-
function
|
|
802
|
+
function buildHttpMetadataValue(raw) {
|
|
213
803
|
const http = {};
|
|
214
804
|
if (raw.declaredContract !== null) {
|
|
215
|
-
http.declaredContract =
|
|
805
|
+
http.declaredContract = {
|
|
806
|
+
framework: raw.declaredContract.framework,
|
|
807
|
+
responses: raw.declaredContract.responses,
|
|
808
|
+
provenance: raw.declaredContract.provenance ?? "independent"
|
|
809
|
+
};
|
|
216
810
|
}
|
|
217
811
|
if (raw.bodyAccessors !== void 0 && raw.bodyAccessors.length > 0) {
|
|
218
812
|
http.bodyAccessors = raw.bodyAccessors;
|
|
@@ -222,7 +816,7 @@ function buildHttpMetadata(raw) {
|
|
|
222
816
|
}
|
|
223
817
|
return Object.keys(http).length > 0 ? http : null;
|
|
224
818
|
}
|
|
225
|
-
function
|
|
819
|
+
function buildGraphqlMetadataValue(raw) {
|
|
226
820
|
const graphql = {};
|
|
227
821
|
if (raw.graphqlDocument !== void 0) {
|
|
228
822
|
graphql.document = raw.graphqlDocument;
|
|
@@ -238,6 +832,12 @@ function buildGraphqlMetadata(raw) {
|
|
|
238
832
|
}
|
|
239
833
|
return Object.keys(graphql).length > 0 ? graphql : null;
|
|
240
834
|
}
|
|
835
|
+
function answersWithHttpResponses(raw) {
|
|
836
|
+
if (raw.boundaryBinding === null || raw.boundaryBinding === void 0) {
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
return (0, import_behavioral_ir.exchangesHttpResponses)(raw.boundaryBinding);
|
|
840
|
+
}
|
|
241
841
|
function detectGaps(raw, transitions, options) {
|
|
242
842
|
if (options.gapHandling === "silent") {
|
|
243
843
|
return [];
|
|
@@ -246,20 +846,18 @@ function detectGaps(raw, transitions, options) {
|
|
|
246
846
|
if (raw.unmatchedReturns !== void 0 && raw.unmatchedReturns > 0) {
|
|
247
847
|
const count = raw.unmatchedReturns;
|
|
248
848
|
gaps.push({
|
|
249
|
-
// Not a contract violation: the handler may be answering
|
|
250
|
-
// correctly, in a shape nobody taught the pack.
|
|
251
849
|
type: "unreadOutcome",
|
|
252
850
|
conditions: [],
|
|
253
851
|
consequence: "unknown",
|
|
254
852
|
description: count === 1 ? "One return in this function matches none of the terminal shapes this pack looks for, so what it produces is not described here" : `${count} returns in this function match none of the terminal shapes this pack looks for, so what they produce is not described here`
|
|
255
853
|
});
|
|
256
854
|
}
|
|
257
|
-
|
|
855
|
+
for (const sentence of unreadSentences(raw)) {
|
|
258
856
|
gaps.push({
|
|
259
857
|
type: "unreadOutcome",
|
|
260
858
|
conditions: [],
|
|
261
859
|
consequence: "unknown",
|
|
262
|
-
description:
|
|
860
|
+
description: sentence
|
|
263
861
|
});
|
|
264
862
|
}
|
|
265
863
|
const unreadBody = describeUnreadBody(raw);
|
|
@@ -271,7 +869,7 @@ function detectGaps(raw, transitions, options) {
|
|
|
271
869
|
description: unreadBody
|
|
272
870
|
});
|
|
273
871
|
}
|
|
274
|
-
if (raw.declaredContract) {
|
|
872
|
+
if (raw.declaredContract && answersWithHttpResponses(raw)) {
|
|
275
873
|
const producedStatuses = new Set(
|
|
276
874
|
transitions.flatMap((t) => {
|
|
277
875
|
if (t.output.type !== "response") {
|
|
@@ -451,12 +1049,27 @@ function paramToInput(param) {
|
|
|
451
1049
|
}
|
|
452
1050
|
// Annotate the CommonJS export names for ESM import in node:
|
|
453
1051
|
0 && (module.exports = {
|
|
1052
|
+
IdMap,
|
|
1053
|
+
IdSet,
|
|
1054
|
+
PathBudgetExceeded,
|
|
1055
|
+
UnmodeledFlow,
|
|
1056
|
+
absentReading,
|
|
1057
|
+
ambiguousReading,
|
|
1058
|
+
andThenReading,
|
|
454
1059
|
assembleSummary,
|
|
455
1060
|
assessConfidence,
|
|
456
1061
|
detectGaps,
|
|
457
1062
|
effectToIR,
|
|
1063
|
+
enumerateOrDegrade,
|
|
1064
|
+
enumerateStructuredPaths,
|
|
1065
|
+
firstWrittenReading,
|
|
458
1066
|
httpRouteDiscovery,
|
|
459
1067
|
makeTransitionId,
|
|
1068
|
+
mapReading,
|
|
460
1069
|
paramToInput,
|
|
461
|
-
|
|
1070
|
+
sharedGatingConditions,
|
|
1071
|
+
terminalToOutput,
|
|
1072
|
+
unreadableReading,
|
|
1073
|
+
valueToReadFurtherFrom,
|
|
1074
|
+
writtenReading
|
|
462
1075
|
});
|