Skip to content

Commit 0392f0c

Browse files
authored
lib: add lint rules for reflective function calls
PR-URL: #60825 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Chemi Atlow <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Jordan Harband <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]>
1 parent 0177491 commit 0392f0c

File tree

8 files changed

+32
-20
lines changed

8 files changed

+32
-20
lines changed

lib/_http_server.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -672,8 +672,9 @@ assignFunctionName(EE.captureRejectionSymbol, function(err, event, ...args) {
672672
break;
673673
}
674674
default:
675-
net.Server.prototype[SymbolFor('nodejs.rejection')]
676-
.apply(this, arguments);
675+
ReflectApply(
676+
net.Server.prototype[SymbolFor('nodejs.rejection')],
677+
this, arguments);
677678
}
678679
});
679680

lib/eslint.config_partial.mjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ const noRestrictedSyntax = [
3838
selector: "ThrowStatement > NewExpression[callee.name=/^ERR_[A-Z_]+$/] > ObjectExpression:first-child:not(:has([key.name='message']):has([key.name='code']):has([key.name='syscall']))",
3939
message: 'The context passed into the SystemError constructor must include .code, .syscall, and .message properties.',
4040
},
41+
{
42+
selector: "CallExpression:matches([callee.type='Identifier'][callee.name='FunctionPrototypeApply'], [callee.type='MemberExpression'][callee.property.type='Identifier'][callee.property.name='apply'][arguments.length=2])",
43+
message: 'Use `ReflectApply` instead of %Function.prototype.apply%',
44+
},
4145
];
4246

4347
export default [
@@ -57,7 +61,13 @@ export default [
5761
rules: {
5862
'prefer-object-spread': 'error',
5963
'no-buffer-constructor': 'error',
60-
'no-restricted-syntax': noRestrictedSyntax,
64+
'no-restricted-syntax': [
65+
...noRestrictedSyntax,
66+
{
67+
selector: "CallExpression[callee.type='Identifier'][callee.name='ReflectApply'][arguments.2.type='ArrayExpression']",
68+
message: 'Use `FunctionPrototypeCall` to avoid creating an ad-hoc array',
69+
},
70+
],
6171
'no-restricted-globals': [
6272
'error',
6373
{

lib/events.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
505505
return false;
506506

507507
if (typeof handler === 'function') {
508-
const result = handler.apply(this, args);
508+
const result = ReflectApply(handler, this, args);
509509

510510
// We check if result is undefined first because that
511511
// is the most common case so we do not pay any perf
@@ -517,7 +517,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
517517
const len = handler.length;
518518
const listeners = arrayClone(handler);
519519
for (let i = 0; i < len; ++i) {
520-
const result = listeners[i].apply(this, args);
520+
const result = ReflectApply(listeners[i], this, args);
521521

522522
// We check if result is undefined first because that
523523
// is the most common case so we do not pay any perf
@@ -620,7 +620,7 @@ function onceWrapper() {
620620
this.fired = true;
621621
if (arguments.length === 0)
622622
return this.listener.call(this.target);
623-
return this.listener.apply(this.target, arguments);
623+
return ReflectApply(this.listener, this.target, arguments);
624624
}
625625
}
626626

lib/internal/async_hooks.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const {
55
ErrorCaptureStackTrace,
66
ObjectDefineProperty,
77
ObjectPrototypeHasOwnProperty,
8+
ReflectApply,
89
Symbol,
910
} = primordials;
1011

@@ -125,9 +126,9 @@ function callbackTrampoline(asyncId, resource, cb, ...args) {
125126
let result;
126127
if (asyncId === 0 && typeof domain_cb === 'function') {
127128
args.unshift(cb);
128-
result = domain_cb.apply(this, args);
129+
result = ReflectApply(domain_cb, this, args);
129130
} else {
130-
result = cb.apply(this, args);
131+
result = ReflectApply(cb, this, args);
131132
}
132133

133134
if (asyncId !== 0 && hasHooks(kAfter))
@@ -462,14 +463,14 @@ function clearDefaultTriggerAsyncId() {
462463
*/
463464
function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) {
464465
if (triggerAsyncId === undefined)
465-
return block.apply(null, args);
466+
return ReflectApply(block, null, args);
466467
// CHECK(NumberIsSafeInteger(triggerAsyncId))
467468
// CHECK(triggerAsyncId > 0)
468469
const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
469470
async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
470471

471472
try {
472-
return block.apply(null, args);
473+
return ReflectApply(block, null, args);
473474
} finally {
474475
async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
475476
}

lib/internal/modules/esm/translators.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const {
66
JSONParse,
77
ObjectAssign,
88
ObjectPrototypeHasOwnProperty,
9-
ReflectApply,
109
SafeArrayIterator,
1110
SafeMap,
1211
SafeSet,
@@ -198,8 +197,8 @@ function loadCJSModule(module, source, url, filename, isMain) {
198197
});
199198
setOwnProperty(requireFn, 'main', process.mainModule);
200199

201-
ReflectApply(compiledWrapper, module.exports,
202-
[module.exports, requireFn, module, filename, __dirname]);
200+
FunctionPrototypeCall(compiledWrapper, module.exports,
201+
module.exports, requireFn, module, filename, __dirname);
203202
setOwnProperty(module, 'loaded', true);
204203
}
205204

lib/internal/streams/end-of-stream.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
const {
77
Promise,
88
PromisePrototypeThen,
9+
ReflectApply,
910
SymbolDispose,
1011
} = primordials;
1112

@@ -280,7 +281,7 @@ function eos(stream, options, callback) {
280281
const originalCallback = callback;
281282
callback = once((...args) => {
282283
disposable[SymbolDispose]();
283-
originalCallback.apply(stream, args);
284+
ReflectApply(originalCallback, stream, args);
284285
});
285286
}
286287
}
@@ -304,13 +305,13 @@ function eosWeb(stream, options, callback) {
304305
const originalCallback = callback;
305306
callback = once((...args) => {
306307
disposable[SymbolDispose]();
307-
originalCallback.apply(stream, args);
308+
ReflectApply(originalCallback, stream, args);
308309
});
309310
}
310311
}
311312
const resolverFn = (...args) => {
312313
if (!isAborted) {
313-
process.nextTick(() => callback.apply(stream, args));
314+
process.nextTick(() => ReflectApply(callback, stream, args));
314315
}
315316
};
316317
PromisePrototypeThen(

lib/internal/streams/readable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const {
3030
ObjectKeys,
3131
ObjectSetPrototypeOf,
3232
Promise,
33+
ReflectApply,
3334
SafeSet,
3435
Symbol,
3536
SymbolAsyncDispose,
@@ -1182,8 +1183,7 @@ Readable.prototype.removeListener = function(ev, fn) {
11821183
Readable.prototype.off = Readable.prototype.removeListener;
11831184

11841185
Readable.prototype.removeAllListeners = function(ev) {
1185-
const res = Stream.prototype.removeAllListeners.apply(this,
1186-
arguments);
1186+
const res = ReflectApply(Stream.prototype.removeAllListeners, this, arguments);
11871187

11881188
if (ev === 'readable' || ev === undefined) {
11891189
// We need to check if there is someone still listening to

lib/internal/test_runner/mock/mock_timers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const {
55
ArrayPrototypeIncludes,
66
DatePrototypeGetTime,
77
DatePrototypeToString,
8-
FunctionPrototypeApply,
98
FunctionPrototypeBind,
109
FunctionPrototypeToString,
1110
NumberIsNaN,
@@ -14,6 +13,7 @@ const {
1413
ObjectGetOwnPropertyDescriptor,
1514
ObjectGetOwnPropertyDescriptors,
1615
PromiseWithResolvers,
16+
ReflectApply,
1717
Symbol,
1818
SymbolDispose,
1919
globalThis,
@@ -645,7 +645,7 @@ class MockTimers {
645645
let timer = this.#executionQueue.peek();
646646
while (timer) {
647647
if (timer.runAt > this.#now) break;
648-
FunctionPrototypeApply(timer.callback, undefined, timer.args);
648+
ReflectApply(timer.callback, undefined, timer.args);
649649

650650
// Check if the timeout was cleared by calling clearTimeout inside its own callback
651651
const afterCallback = this.#executionQueue.peek();

0 commit comments

Comments
 (0)