@afokapu/atdd-bun 0.10.108 → 0.10.109
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.
|
@@ -558,6 +558,7 @@ function resolutionTrainExecution(after, resolution, aliases = [], base = 0) {
|
|
|
558
558
|
return {
|
|
559
559
|
execution: assigned?.[1] ?? null,
|
|
560
560
|
alias: alias?.alias ?? null,
|
|
561
|
+
aliasTraceFields: alias?.traceFields ?? null,
|
|
561
562
|
found: Boolean(assigned || alias || new RegExp(legacy).test(after) || new RegExp(declaration).test(bound)),
|
|
562
563
|
returned: new RegExp(`${returns}${legacy}`).test(after) || returnsMatch(scope, declaration, bound.length) ||
|
|
563
564
|
Boolean(assigned && new RegExp(`${returns}${escaped(assigned[1])}\\s*;?`).test(after)),
|
|
@@ -579,7 +580,9 @@ export function productionExecutionProofs(text, file, croot) {
|
|
|
579
580
|
const proof = resolutionTrainExecution(text.slice(base), resolution,
|
|
580
581
|
file ? transparentCallbackAliases(text, file, resolution) : [], base);
|
|
581
582
|
if (proof.found) proofs.push({ resolution, execution: proof.execution });
|
|
582
|
-
if (proof.alias && proof.alias !== proof.execution)
|
|
583
|
+
if (proof.alias && proof.alias !== proof.execution) {
|
|
584
|
+
proofs.push({ resolution, execution: proof.alias, callbackTraceFields: proof.aliasTraceFields });
|
|
585
|
+
}
|
|
583
586
|
}
|
|
584
587
|
return proofs;
|
|
585
588
|
}
|
|
@@ -634,16 +637,23 @@ export function routeHasProductionProof(route, text, interlockingId = null, file
|
|
|
634
637
|
}
|
|
635
638
|
|
|
636
639
|
export function traceHasProductionProvenance(text, file, croot) {
|
|
637
|
-
return productionExecutionProofs(text, file, croot).some(({ execution, direct }) => {
|
|
640
|
+
return productionExecutionProofs(text, file, croot).some(({ execution, direct, callbackTraceFields }) => {
|
|
638
641
|
if (direct) return direct.traceBound && REQUIRED_TRACE_FIELDS.every(([field]) => direct.traceFields.includes(field));
|
|
639
642
|
if (!execution) return false;
|
|
640
643
|
const trace = boundTraceName(text, execution);
|
|
641
|
-
return Boolean(trace);
|
|
644
|
+
return Boolean(trace) || Boolean(callbackTraceFields?.size);
|
|
642
645
|
});
|
|
643
646
|
}
|
|
644
647
|
|
|
645
|
-
|
|
646
|
-
|
|
648
|
+
// Required trace fields not asserted. A field also counts as asserted when a transparent callback return
|
|
649
|
+
// alias of a production execution compares `<alias>.trace` to an object literal carrying it (the proofs
|
|
650
|
+
// come from productionExecutionProofs, so only an execution proven for this file qualifies).
|
|
651
|
+
export function assertedTraceFields(text, traceName = "trace", file, croot) {
|
|
652
|
+
const comparedWhole = new Set(file
|
|
653
|
+
? productionExecutionProofs(text, file, croot).flatMap(({ callbackTraceFields }) => [...(callbackTraceFields ?? [])])
|
|
654
|
+
: []);
|
|
655
|
+
const asserted = (field) => comparedWhole.has(field) || assertsExpression(text, `${escaped(traceName)}${MEMBER}${field}`);
|
|
656
|
+
return REQUIRED_TRACE_FIELDS.filter(([field]) => !asserted(field)).map(([field]) => field);
|
|
647
657
|
}
|
|
648
658
|
|
|
649
659
|
// A smoke path has a real Station Master call and observes that call's returned value; separately
|
|
@@ -44,7 +44,7 @@ for (const scanRoot of roots) {
|
|
|
44
44
|
if (!isInterlockingTest(text, tokens)) continue;
|
|
45
45
|
const m = TRACE_OBJECT.exec(text);
|
|
46
46
|
if (!m) continue; // not a trace-binding test.
|
|
47
|
-
const missing = assertedTraceFields(text);
|
|
47
|
+
const missing = assertedTraceFields(text, "trace", file, croot);
|
|
48
48
|
const provenance = traceHasProductionProvenance(text, file, croot);
|
|
49
49
|
if (!missing.length && provenance) continue;
|
|
50
50
|
const line = lineOfIndex(text, m.index);
|
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.109",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"README.md": "8aefceaa24bee498f39081b2aaf4c0d5c54bc1ed9849c20e1d98d1427df56eab",
|
|
@@ -384,13 +384,13 @@
|
|
|
384
384
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/server.ts": "d5bc34c5b1b2b0a511e0c4604ce6f9c77491dca9aee994be720540f3052babc1",
|
|
385
385
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/interlocking.ts": "9bcfdb4ecff6efd1f658b9fd587342615179924e25468c75daa07c2626d4d8ab",
|
|
386
386
|
"detectors/bun_interlocking_binding/fixtures/dirty_transcribed_runtime/src/trains/runner.ts": "0e4b885e50f12cc2a8c8f6740bf83e7b2e7137a28e1e143586e9a8550ea5bd45",
|
|
387
|
-
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "
|
|
387
|
+
"detectors/bun_interlocking_coverage/_shared/interlocking.mjs": "6660f3cf9f16116114c4985111fefdb3666cb11d4049afc2854e544374be8168",
|
|
388
388
|
"detectors/bun_interlocking_coverage/atdd.implementation.yaml": "f160517a8138ba3ca05e3d130e6fbfb796e643376f25743fdef4fc210196525b",
|
|
389
389
|
"detectors/bun_interlocking_coverage/checks/_map.json": "0a218ea319cc6258af7bd721c2154cb9561fe67e80d80ef32d08fc77bc9403c3",
|
|
390
390
|
"detectors/bun_interlocking_coverage/checks/interlocking_production_runner.mjs": "97224d46f0c4c3259f15e41f90bb7f9f5a4e651d4facd41f1e037cac919be500",
|
|
391
391
|
"detectors/bun_interlocking_coverage/checks/interlocking_route_coverage.mjs": "a2cff94a6bf9e11fe8e370332551742f9c7db317dcd57a88d08dc6c74f868f49",
|
|
392
392
|
"detectors/bun_interlocking_coverage/checks/interlocking_smoke_coverage.mjs": "28e016e1a4cd394291640679876b16c7d6a725530c7fbfa4bd1da8a354ecff86",
|
|
393
|
-
"detectors/bun_interlocking_coverage/checks/interlocking_trace_binds.mjs": "
|
|
393
|
+
"detectors/bun_interlocking_coverage/checks/interlocking_trace_binds.mjs": "929f7ff757be481d71d38141283a4ca8622ccc7b344b31da06161252e19e25a2",
|
|
394
394
|
"detectors/bun_interlocking_coverage/checks/interlocking_train_sequence.mjs": "28d0dd186f91f7aeb2f6672180d289ba6a02552581abda9562e41cca04bc3485",
|
|
395
395
|
"detectors/bun_interlocking_coverage/detect.mjs": "122b928bbb49affbf187c59cbf8e10d0190ca84cb5b98bc222bd2c1c1aac2f26",
|
|
396
396
|
"detectors/bun_interlocking_coverage/fixtures/clean/interlocking_production_runner/convex/trains/interlocking.ts": "e97288c4ca6084391bfa72d1457de26e99e9fa0c9540be4361e27731c19aeb6f",
|
|
@@ -1033,7 +1033,7 @@
|
|
|
1033
1033
|
"lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
|
|
1034
1034
|
"lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
|
|
1035
1035
|
"lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
|
|
1036
|
-
"lib/journey-chain.mjs": "
|
|
1036
|
+
"lib/journey-chain.mjs": "8ba32f5ab52bec636eb87b97a532427dcca81f3481a588a093ff4b5e643f7c8f",
|
|
1037
1037
|
"lib/scan.mjs": "b6973e411ecd14c548a055c871e01e948f002b772042136be8401f38d998e687",
|
|
1038
1038
|
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "47aa9c78a4bb1754c6b435f5e056038baa04a24cd48df2c205db2bc08a3a3cac",
|
|
1039
1039
|
"planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
|
package/lib/journey-chain.mjs
CHANGED
|
@@ -804,9 +804,12 @@ function localFunction(ctx, identifier) {
|
|
|
804
804
|
// A wrapper whose every return, on every path, IS the unchanged result of its callback parameter:
|
|
805
805
|
// `return work()` / `return await work()`. Anything that replaces, conditions, copies or discards the
|
|
806
806
|
// callback's value (spread, ternary, comma, a second return value, a returning catch) is not transparent.
|
|
807
|
+
// The callback is the wrapper's LAST parameter, so a leading configuration parameter is allowed
|
|
808
|
+
// (`withDeployment(env, work)`); a callback in any other position, or a rest/destructured parameter, is not.
|
|
807
809
|
function transparentWrapper(ctx, fn) {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
+
if (fn.asteriskToken || !fn.parameters.length) return false;
|
|
811
|
+
const param = fn.parameters[fn.parameters.length - 1];
|
|
812
|
+
if (!ts.isIdentifier(param.name) || param.dotDotDotToken) return false;
|
|
810
813
|
const symbol = symbolOf(ctx, param.name);
|
|
811
814
|
if (!symbol) return false;
|
|
812
815
|
const isWork = (expression) => {
|
|
@@ -820,9 +823,42 @@ function transparentWrapper(ctx, fn) {
|
|
|
820
823
|
return !rebound;
|
|
821
824
|
}
|
|
822
825
|
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
+
// The required trace fields asserted by a whole-object comparison of `<execution>.trace` (or `?.trace`):
|
|
827
|
+
// `expect(<execution>?.trace).toEqual|toStrictEqual|toMatchObject({ <field>: "<literal>", ... })`. The
|
|
828
|
+
// expected value must be an object literal without spread, computed or shorthand members, each field a
|
|
829
|
+
// string literal; a negated assertion, another object, or a non-literal expected value asserts nothing.
|
|
830
|
+
const TRACE_COMPARISONS = new Set(["toEqual", "toStrictEqual", "toMatchObject"]);
|
|
831
|
+
|
|
832
|
+
// The plain `name: <value>` members of an object literal (identifier or string-literal names only), or
|
|
833
|
+
// null when any member is a spread, shorthand, method, accessor or computed name.
|
|
834
|
+
function plainMembers(literal) {
|
|
835
|
+
if (!literal || !ts.isObjectLiteralExpression(literal)) return null;
|
|
836
|
+
const plain = literal.properties.every((property) => ts.isPropertyAssignment(property) &&
|
|
837
|
+
(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)));
|
|
838
|
+
return plain ? literal.properties : null;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function wholeTraceFields(ctx, executionSymbol) {
|
|
842
|
+
const fields = new Set();
|
|
843
|
+
if (!executionSymbol) return fields;
|
|
844
|
+
walk(ctx.source, (node) => {
|
|
845
|
+
const assertion = expectAssertion(ctx, node);
|
|
846
|
+
if (!assertion || assertion.negated || !TRACE_COMPARISONS.has(assertion.matcher) ||
|
|
847
|
+
dead(node, ctx.source) || insideCatch(node, ctx.source)) return;
|
|
848
|
+
const path = pathFromSymbol(ctx, assertion.actual, executionSymbol);
|
|
849
|
+
if (path?.length !== 1 || path[0] !== "trace") return;
|
|
850
|
+
for (const property of plainMembers(assertion.expected && unwrap(assertion.expected)) ?? []) {
|
|
851
|
+
if (ts.isStringLiteralLike(property.initializer)) fields.add(property.name.text);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
return fields;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// `const <alias> = await <wrapper>(<callback>)` or `await <wrapper>(<configuration>..., <callback>)` where
|
|
858
|
+
// the local wrapper is transparent and every return of the zero-parameter callback IS the production
|
|
859
|
+
// execution of `resolution` (the call itself, or a `const` bound to it), so `<alias>` IS that execution.
|
|
860
|
+
// Returns [{ alias, start, traceFields }] in source order; `traceFields` are the trace fields the alias
|
|
861
|
+
// asserts as a whole object (see wholeTraceFields).
|
|
826
862
|
// Production forms: `new TrainRunner(<resolution>.trainId|selectedTrainId).execute(...)` and
|
|
827
863
|
// `new TrainRunner(<resolution>.trainPath, ...).execute(<resolution>, ...)`.
|
|
828
864
|
export function transparentCallbackAliases(text, file, resolution) {
|
|
@@ -862,14 +898,19 @@ export function transparentCallbackAliases(text, file, resolution) {
|
|
|
862
898
|
!(ts.isVariableDeclarationList(node.parent) && node.parent.flags & ts.NodeFlags.Const) ||
|
|
863
899
|
dead(node, ctx.source) || insideCatch(node, ctx.source)) return;
|
|
864
900
|
const awaited = awaitedCall(node.initializer);
|
|
865
|
-
if (!awaited || !ts.isCallExpression(awaited) || !ts.isIdentifier(awaited.expression) || awaited.arguments.length
|
|
866
|
-
|
|
901
|
+
if (!awaited || !ts.isCallExpression(awaited) || !ts.isIdentifier(awaited.expression) || !awaited.arguments.length ||
|
|
902
|
+
awaited.arguments.some(ts.isSpreadElement)) return;
|
|
903
|
+
const callback = unwrap(awaited.arguments[awaited.arguments.length - 1]);
|
|
867
904
|
if (!callback || !(ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) ||
|
|
868
905
|
callback.parameters.length > 0 || callback.asteriskToken) return;
|
|
869
906
|
const wrapper = localFunction(ctx, awaited.expression);
|
|
870
|
-
if (!wrapper || !transparentWrapper(ctx, wrapper)) return;
|
|
907
|
+
if (!wrapper || wrapper.parameters.length !== awaited.arguments.length || !transparentWrapper(ctx, wrapper)) return;
|
|
871
908
|
if (!alwaysReturns(callback, (expression) => isIdentity(expression))) return;
|
|
872
|
-
aliases.push({
|
|
909
|
+
aliases.push({
|
|
910
|
+
alias: node.name.text,
|
|
911
|
+
start: node.getStart(ctx.source),
|
|
912
|
+
traceFields: wholeTraceFields(ctx, symbolOf(ctx, node.name)),
|
|
913
|
+
});
|
|
873
914
|
});
|
|
874
915
|
return aliases;
|
|
875
916
|
}
|