Skip to content

Commit e46e697

Browse files
committed
fix(hooks): honor arg forwarding from every middleware in the chain
decorateMethod's chain pre-bound the original args into every link, so next(...newArgs) was only honored by the innermost middleware; any outer middleware's forwarding was silently discarded. Each link now passes the args it was invoked with down the chain. Single-middleware behavior and argument-less next() calls are unchanged.
1 parent 3785152 commit e46e697

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

lib/common/helpers.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,19 @@ export function decorateMethod(
536536
const replacementMethods = _.filter(newMethods, (f) => _.isFunction(f));
537537
if (replacementMethods.length > 0) {
538538
hasBeenReplaced = true;
539+
// Each link passes the args it was invoked with down the chain, so
540+
// any middleware's next(...newArgs) — not just the innermost one's —
541+
// is seen by the rest of the chain; next() with no arguments keeps
542+
// the current args.
539543
const chainedReplacementMethod = _.reduce(
540544
replacementMethods,
541-
(prev, next) => next.bind(next, args, prev),
545+
(prev: Function, next: Function) =>
546+
(...forwardedArgs: any[]) =>
547+
next.call(
548+
next,
549+
forwardedArgs.length ? forwardedArgs : args,
550+
prev,
551+
),
542552
sink.bind(this),
543553
);
544554
result = chainedReplacementMethod();

lib/common/test/unit-tests/helpers.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,4 +1034,81 @@ const test = require("./test");`,
10341034
assert.isTrue(helpers.isInteractive());
10351035
});
10361036
});
1037+
1038+
describe("decorateMethod", () => {
1039+
const decorate = (middlewares: Function[], sink: Function): Function => {
1040+
const descriptor: TypedPropertyDescriptor<Function> = { value: sink };
1041+
helpers.decorateMethod(
1042+
async () => middlewares,
1043+
async (method, self, result) => result,
1044+
)(null, "method", descriptor);
1045+
return descriptor.value;
1046+
};
1047+
1048+
it("passes the original args and the sink to a single middleware", async () => {
1049+
let received: any[];
1050+
const middleware = async (args: any[], next: Function) => {
1051+
received = args;
1052+
return next.apply(null, ["replaced"]);
1053+
};
1054+
const decorated = decorate([middleware], async function (...args: any[]) {
1055+
return args;
1056+
});
1057+
1058+
const result = await decorated.call({}, "original");
1059+
1060+
assert.deepStrictEqual(received, ["original"]);
1061+
assert.deepStrictEqual(result, ["replaced"]);
1062+
});
1063+
1064+
it("forwards args changed by every middleware, not only the innermost", async () => {
1065+
const seen: { [key: string]: any[] } = {};
1066+
// The last middleware in the array is the outermost link, so it runs
1067+
// first.
1068+
const runsSecond = async (args: any[], next: Function) => {
1069+
seen.second = args.slice();
1070+
return next.apply(null, args.concat("second"));
1071+
};
1072+
const runsFirst = async (args: any[], next: Function) => {
1073+
seen.first = args.slice();
1074+
return next.apply(null, args.concat("first"));
1075+
};
1076+
const decorated = decorate(
1077+
[runsSecond, runsFirst],
1078+
async function (...args: any[]) {
1079+
seen.sink = args;
1080+
return "done";
1081+
},
1082+
);
1083+
1084+
const result = await decorated.call({}, "start");
1085+
1086+
assert.deepStrictEqual(seen.first, ["start"]);
1087+
assert.deepStrictEqual(seen.second, ["start", "first"]);
1088+
assert.deepStrictEqual(seen.sink, ["start", "first", "second"]);
1089+
assert.strictEqual(result, "done");
1090+
});
1091+
1092+
it("keeps the current args when a middleware calls next() without arguments", async () => {
1093+
const seen: { [key: string]: any[] } = {};
1094+
const inner = async (args: any[], next: Function) => {
1095+
seen.inner = args.slice();
1096+
return next.apply(null, args);
1097+
};
1098+
const outer = async (args: any[], next: Function) => {
1099+
return next();
1100+
};
1101+
const decorated = decorate(
1102+
[inner, outer],
1103+
async function (...args: any[]) {
1104+
seen.sink = args;
1105+
},
1106+
);
1107+
1108+
await decorated.call({}, "kept");
1109+
1110+
assert.deepStrictEqual(seen.inner, ["kept"]);
1111+
assert.deepStrictEqual(seen.sink, ["kept"]);
1112+
});
1113+
});
10371114
});

0 commit comments

Comments
 (0)