From 61896fe563c4f7e15c12997a82b57a5221aa8c40 Mon Sep 17 00:00:00 2001 From: Kirill Saied Date: Tue, 14 Jul 2026 11:36:15 +0200 Subject: [PATCH 1/3] test: fix stale async-context-frame status entries Signed-off-by: Kirill Saied PR-URL: https://github.com/nodejs/node/pull/64141 Reviewed-By: Stefan Stojanovic --- test/parallel/parallel.status | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index f6305871350c7e..fb285503351bdf 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -22,17 +22,12 @@ test-snapshot-incompatible: SKIP [$system==win32] # https://github.com/nodejs/node/issues/59090 test-inspector-network-fetch: PASS, FLAKY -# https://github.com/nodejs/node/issues/54808 -test-async-context-frame: PASS, FLAKY # https://github.com/nodejs/node/issues/59636 test-fs-cp-sync-error-on-exist: SKIP test-fs-cp-sync-symlink-points-to-dest-error: SKIP test-fs-cp-async-symlink-points-to-dest: SKIP test-fs-cp-sync-unicode-folder-names: SKIP -# https://github.com/nodejs/node/issues/56751 -test-without-async-context-frame: PASS, FLAKY - # Windows on ARM [$system==win32 && $arch==arm64] @@ -146,7 +141,6 @@ test-http-pipeline-flood: SKIP test-inspector-network-fetch: SKIP test-inspector-network-content-type: SKIP test-fetch: SKIP -test-without-async-context-frame: SKIP test-process-cpuUsage: PASS, FLAKY test-web-locks: SKIP test-http2-allow-http1-upgrade-ws: SKIP From 7b2c63c0dd23ce783ea72cff1191db8ebc1710f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 14 Jul 2026 13:07:16 +0100 Subject: [PATCH 2/3] deps: V8: cherry-pick 1158ae719749 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original commit message: [wasm] Fix jump table slot overflow on x64 with CET enabled JumpTableAssembler::EmitJumpSlot for x64 was advancing the PC before checking if the target was reachable via a 32-bit displacement when CET was enabled. This caused a slot overflow when the far-jump fallback was triggered, as the second attempt to emit the slot would start at an incorrect offset. This CL ensures that the displacement check is performed before any instructions are emitted, making the function side-effect-free on failure. It also introduces kJumpTableSlotEntryMarkerSize to clarify the displacement calculation, following the pattern used on ARM64. Reported-by: Fabien Romano TAG=agy R=jkummerow@chromium.org Fixed: 532112743 Change-Id: Iaf6f15b51a66a45711e8556972ac0d353e6117c7 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8063803 Reviewed-by: Jakob Kummerow Commit-Queue: Clemens Backes Cr-Commit-Position: refs/heads/main@{#108556} Refs: https://github.com/v8/v8/commit/1158ae71974987dc6492c7f407e8a7c005756ffc PR-URL: https://github.com/nodejs/node/pull/64432 Fixes: https://github.com/nodejs/node/issues/64424 Reviewed-By: Michaël Zasso Reviewed-By: Richard Lau --- common.gypi | 2 +- deps/v8/src/wasm/jump-table-assembler.cc | 10 +++++----- deps/v8/src/wasm/jump-table-assembler.h | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/common.gypi b/common.gypi index 214615c3e085d9..8cf118455ec827 100644 --- a/common.gypi +++ b/common.gypi @@ -42,7 +42,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.25', + 'v8_embedder_string': '-node.26', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/wasm/jump-table-assembler.cc b/deps/v8/src/wasm/jump-table-assembler.cc index 47d21e29785be8..dfebd423c7c068 100644 --- a/deps/v8/src/wasm/jump-table-assembler.cc +++ b/deps/v8/src/wasm/jump-table-assembler.cc @@ -114,6 +114,10 @@ void JumpTableAssembler::EmitLazyCompileJumpSlot(uint32_t func_index, } bool JumpTableAssembler::EmitJumpSlot(Address target) { + intptr_t displacement = target - (pc_ + kJumpTableSlotEntryMarkerSize + + MacroAssembler::kIntraSegmentJmpInstrSize); + if (!is_int32(displacement)) return false; + #ifdef V8_ENABLE_CET_IBT uint32_t endbr_insn = 0xfa1e0ff3; uint32_t nop = 0x00401f0f; @@ -122,11 +126,7 @@ bool JumpTableAssembler::EmitJumpSlot(Address target) { emit(nop, kRelaxedStore); #endif - intptr_t displacement = - target - (pc_ + MacroAssembler::kIntraSegmentJmpInstrSize); - if (!is_int32(displacement)) return false; - - uint8_t inst[kJumpTableSlotSize] = { + uint8_t inst[8] = { 0xe9, 0, 0, 0, 0, // near_jmp displacement 0xcc, 0xcc, 0xcc, // int3 * 3 }; diff --git a/deps/v8/src/wasm/jump-table-assembler.h b/deps/v8/src/wasm/jump-table-assembler.h index 2024d74928136c..91252dc6e67c03 100644 --- a/deps/v8/src/wasm/jump-table-assembler.h +++ b/deps/v8/src/wasm/jump-table-assembler.h @@ -184,8 +184,10 @@ class V8_EXPORT_PRIVATE JumpTableAssembler { #if V8_TARGET_ARCH_X64 #ifdef V8_ENABLE_CET_IBT static constexpr int kJumpTableSlotSize = 16; + static constexpr int kJumpTableSlotEntryMarkerSize = 8; #else // V8_ENABLE_CET_IBT static constexpr int kJumpTableSlotSize = 8; + static constexpr int kJumpTableSlotEntryMarkerSize = 0; #endif static constexpr int kJumpTableLineSize = kJumpTableSlotSize; static constexpr int kFarJumpTableSlotSize = 16; From 727e2bce8e94b954affd5e9b74353f18b3a0f0f3 Mon Sep 17 00:00:00 2001 From: Livia Medeiros Date: Tue, 14 Jul 2026 21:30:56 +0800 Subject: [PATCH 3/3] lib,tools: add `node-core/func-name-matching` lint rule And rename functions accordingly. Signed-off-by: LiviaMedeiros Co-authored-by: louiellan PR-URL: https://github.com/nodejs/node/pull/57901 Refs: https://github.com/nodejs/node/issues/57899 Reviewed-By: Matteo Collina Reviewed-By: Ethan Arrowood Reviewed-By: Antoine du Hamel --- doc/api/modules.md | 2 +- eslint.config.mjs | 2 +- lib/fs.js | 2 +- .../bootstrap/web/exposed-window-or-worker.js | 2 +- lib/internal/console/constructor.js | 33 +- lib/internal/modules/esm/hooks.js | 2 +- lib/internal/modules/esm/translators.js | 2 +- lib/internal/per_context/domexception.js | 2 +- lib/internal/streams/writable.js | 10 +- lib/internal/test_runner/mock/mock_timers.js | 2 +- lib/internal/util/types.js | 20 +- lib/internal/worker/io.js | 2 +- lib/repl.js | 2 +- lib/test/reporters.js | 16 +- lib/zlib.js | 5 +- .../test-eslint-func-name-matching.js | 1108 +++++++++++++++++ test/parallel/test-util-inspect.js | 2 +- tools/eslint-rules/func-name-matching.js | 329 +++++ 18 files changed, 1492 insertions(+), 51 deletions(-) create mode 100644 test/parallel/test-eslint-func-name-matching.js create mode 100644 tools/eslint-rules/func-name-matching.js diff --git a/doc/api/modules.md b/doc/api/modules.md index 87650979f66508..01a5343e92dd72 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -1133,7 +1133,7 @@ exports = { hello: false }; // Not exported, only available in the module When the `module.exports` property is being completely replaced by a new object, it is common to also reassign `exports`: - + ```js module.exports = exports = function Constructor() { diff --git a/eslint.config.mjs b/eslint.config.mjs index 84c0df7bb31dd9..6d3d8782a0de79 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -179,7 +179,6 @@ export default [ 'default-case-last': 'error', 'dot-notation': 'error', 'eqeqeq': ['error', 'smart'], - 'func-name-matching': 'error', 'func-style': ['error', 'declaration', { allowArrowFunctions: true }], 'no-constant-condition': ['error', { checkLoops: false }], 'no-constructor-return': 'error', @@ -391,6 +390,7 @@ export default [ 'node-core/no-duplicate-requires': 'error', 'node-core/prefer-proto': 'error', 'node-core/prefer-optional-chaining': 'error', + 'node-core/func-name-matching': ['error', { considerPropertyDescriptor: true }], }, }, // #endregion diff --git a/lib/fs.js b/lib/fs.js index 1ea70ff192d6dd..4fbdaf8130180a 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -304,7 +304,7 @@ function exists(path, callback) { ObjectDefineProperty(exists, kCustomPromisifiedSymbol, { __proto__: null, - value: function exists(path) { // eslint-disable-line func-name-matching + value: function exists(path) { return new Promise((resolve) => fs.exists(path, resolve)); }, }); diff --git a/lib/internal/bootstrap/web/exposed-window-or-worker.js b/lib/internal/bootstrap/web/exposed-window-or-worker.js index b138275e92bd4d..e9edc4ceb61650 100644 --- a/lib/internal/bootstrap/web/exposed-window-or-worker.js +++ b/lib/internal/bootstrap/web/exposed-window-or-worker.js @@ -75,7 +75,7 @@ ObjectDefineProperty(globalThis, 'fetch', { configurable: true, enumerable: true, writable: true, - value: function fetch(input, init = undefined) { // eslint-disable-line func-name-matching + value: function fetch(input, init = undefined) { if (!fetchImpl) { // Implement lazy loading of undici module for fetch function const undiciModule = require('internal/deps/undici/undici'); fetchImpl = undiciModule.fetch; diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index bb79678001ec66..0b71abc98f700f 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -51,6 +51,7 @@ const { } = require('internal/validators'); const { previewEntries } = internalBinding('util'); const { Buffer: { isBuffer } } = require('buffer'); +const { assignFunctionName } = require('internal/util'); const { inspect, formatWithOptions, @@ -182,9 +183,9 @@ const consolePropAttributes = { // Fixup global.console instanceof global.console.Console ObjectDefineProperty(Console, SymbolHasInstance, { __proto__: null, - value(instance) { + value: assignFunctionName(SymbolHasInstance, function(instance) { return instance[kIsConsole]; - }, + }), }); const kColorInspectOptions = { colors: true }; @@ -197,19 +198,19 @@ ObjectDefineProperties(Console.prototype, { __proto__: null, ...consolePropAttributes, // Eager version for the Console constructor - value: function(stdout, stderr) { + value: assignFunctionName(kBindStreamsEager, function(stdout, stderr) { ObjectDefineProperties(this, { '_stdout': { __proto__: null, ...consolePropAttributes, value: stdout }, '_stderr': { __proto__: null, ...consolePropAttributes, value: stderr }, }); - }, + }), }, [kBindStreamsLazy]: { __proto__: null, ...consolePropAttributes, // Lazily load the stdout and stderr from an object so we don't // create the stdio streams when they are not even accessed - value: function(object) { + value: assignFunctionName(kBindStreamsLazy, function(object) { let stdout; let stderr; ObjectDefineProperties(this, { @@ -232,12 +233,12 @@ ObjectDefineProperties(Console.prototype, { set(value) { stderr = value; }, }, }); - }, + }), }, [kBindProperties]: { __proto__: null, ...consolePropAttributes, - value: function(ignoreErrors, colorMode, groupIndentation = 2) { + value: assignFunctionName(kBindProperties, function(ignoreErrors, colorMode, groupIndentation = 2) { ObjectDefineProperties(this, { '_stdoutErrorHandler': { __proto__: null, @@ -277,12 +278,12 @@ ObjectDefineProperties(Console.prototype, { value: 'console', }, }); - }, + }), }, [kWriteToConsole]: { __proto__: null, ...consolePropAttributes, - value: function(streamSymbol, string) { + value: assignFunctionName(kWriteToConsole, function(streamSymbol, string) { const ignoreErrors = this._ignoreErrors; const groupIndent = this[kGroupIndentationString]; @@ -320,12 +321,12 @@ ObjectDefineProperties(Console.prototype, { } finally { stream.removeListener('error', noop); } - }, + }), }, [kGetInspectOptions]: { __proto__: null, ...consolePropAttributes, - value: function(stream) { + value: assignFunctionName(kGetInspectOptions, function(stream) { let color = this[kColorMode]; if (color === 'auto') { color = lazyUtilColors().shouldColorize(stream); @@ -341,12 +342,12 @@ ObjectDefineProperties(Console.prototype, { } return color ? kColorInspectOptions : kNoColorInspectOptions; - }, + }), }, [kFormatForStdout]: { __proto__: null, ...consolePropAttributes, - value: function(args) { + value: assignFunctionName(kFormatForStdout, function(args) { if (args.length === 1) { // Fast path: single string, don't call format. // Avoids ReflectApply and validation overhead. @@ -358,12 +359,12 @@ ObjectDefineProperties(Console.prototype, { const opts = this[kGetInspectOptions](this._stdout); ArrayPrototypeUnshift(args, opts); return ReflectApply(formatWithOptions, null, args); - }, + }), }, [kFormatForStderr]: { __proto__: null, ...consolePropAttributes, - value: function(args) { + value: assignFunctionName(kFormatForStderr, function(args) { if (args.length === 1) { // Fast path: single string, don't call format. // Avoids ReflectApply and validation overhead. @@ -375,7 +376,7 @@ ObjectDefineProperties(Console.prototype, { const opts = this[kGetInspectOptions](this._stderr); ArrayPrototypeUnshift(args, opts); return ReflectApply(formatWithOptions, null, args); - }, + }), }, }); diff --git a/lib/internal/modules/esm/hooks.js b/lib/internal/modules/esm/hooks.js index 67ccc9683ee50e..4ced5521aca203 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -742,7 +742,7 @@ function nextHookFactory(current, meta, { validateArgs, validateOutput }) { if (next) { nextNextHook = nextHookFactory(next, meta, { validateArgs, validateOutput }); } else { - // eslint-disable-next-line func-name-matching + // eslint-disable-next-line node-core/func-name-matching nextNextHook = function chainAdvancedTooFar() { throw new ERR_INTERNAL_ASSERTION( `ESM custom loader '${hookName}' advanced beyond the end of the chain.`, diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 37b289fae60dee..c8eb2d857a3d23 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -121,7 +121,7 @@ function loadCJSModuleWithSpecialRequire(module, source, url, filename, isMain, } const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); const __dirname = dirname(filename); - // eslint-disable-next-line func-name-matching,func-style + // eslint-disable-next-line node-core/func-name-matching,func-style const requireFn = function require(specifier) { let importAttributes = kEmptyObject; if (!StringPrototypeStartsWith(specifier, 'node:') && !BuiltinModule.normalizeRequirableId(specifier)) { diff --git a/lib/internal/per_context/domexception.js b/lib/internal/per_context/domexception.js index 67def5017ed626..58a02553a10e5d 100644 --- a/lib/internal/per_context/domexception.js +++ b/lib/internal/per_context/domexception.js @@ -41,7 +41,7 @@ function throwInvalidThisError(Base, type) { }, toString: { __proto__: null, - value() { + value: function toString() { return `${this.name} [${key}]: ${this.message}`; }, enumerable: false, diff --git a/lib/internal/streams/writable.js b/lib/internal/streams/writable.js index 94913461cb6044..47e003ea3a8a88 100644 --- a/lib/internal/streams/writable.js +++ b/lib/internal/streams/writable.js @@ -84,6 +84,8 @@ const { kOnConstructed, } = require('internal/streams/utils'); +const { assignFunctionName } = require('internal/util'); + const { errorOrDestroy } = destroyImpl; ObjectSetPrototypeOf(Writable.prototype, Stream.prototype); @@ -434,12 +436,12 @@ function Writable(options) { ObjectDefineProperty(Writable, SymbolHasInstance, { __proto__: null, - value: function(object) { - if (FunctionPrototypeSymbolHasInstance(this, object)) return true; + value: assignFunctionName(SymbolHasInstance, function(instance) { + if (FunctionPrototypeSymbolHasInstance(this, instance)) return true; if (this !== Writable) return false; - return object && object._writableState instanceof WritableState; - }, + return instance && instance._writableState instanceof WritableState; + }), }); // Otherwise people can pipe Writable streams, which is just wrong. diff --git a/lib/internal/test_runner/mock/mock_timers.js b/lib/internal/test_runner/mock/mock_timers.js index 77c54aee815362..1e425b606ee530 100644 --- a/lib/internal/test_runner/mock/mock_timers.js +++ b/lib/internal/test_runner/mock/mock_timers.js @@ -644,7 +644,7 @@ class MockTimers { __proto__: null, configurable: true, writable: true, - value: function value(delay) { + value: function timeout(delay) { validateUint32(delay, 'delay', false); const controller = new AbortController(); // Don't keep an unused binding to the timer; mock tick controls it diff --git a/lib/internal/util/types.js b/lib/internal/util/types.js index 81efeb6699aedc..3684c21d482a9d 100644 --- a/lib/internal/util/types.js +++ b/lib/internal/util/types.js @@ -81,40 +81,40 @@ module.exports = { isBigUint64Array, }; -let isCryptoKey; -let isKeyObject; +let isCryptoKeyFn; +let isKeyObjectFn; ObjectDefineProperties(module.exports, { isKeyObject: { __proto__: null, configurable: false, enumerable: true, - value(obj) { + value: function isKeyObject(obj) { if (!process.versions.openssl) { return false; } - if (!isKeyObject) { - ({ isKeyObject } = require('internal/crypto/keys')); + if (!isKeyObjectFn) { + ({ isKeyObject: isKeyObjectFn } = require('internal/crypto/keys')); } - return isKeyObject(obj); + return isKeyObjectFn(obj); }, }, isCryptoKey: { __proto__: null, configurable: false, enumerable: true, - value(obj) { + value: function isCryptoKey(obj) { if (!process.versions.openssl) { return false; } - if (!isCryptoKey) { - ({ isCryptoKey } = require('internal/crypto/keys')); + if (!isCryptoKeyFn) { + ({ isCryptoKey: isCryptoKeyFn } = require('internal/crypto/keys')); } - return isCryptoKey(obj); + return isCryptoKeyFn(obj); }, }, }); diff --git a/lib/internal/worker/io.js b/lib/internal/worker/io.js index 786ee36c1927fa..b73256734079b5 100644 --- a/lib/internal/worker/io.js +++ b/lib/internal/worker/io.js @@ -190,7 +190,7 @@ ObjectDefineProperty(MessagePort.prototype, inspect.custom, { __proto__: null, enumerable: false, writable: false, - value: function inspect() { // eslint-disable-line func-name-matching + value: function inspect() { let ref; try { // This may throw when `this` does not refer to a native object, diff --git a/lib/repl.js b/lib/repl.js index a00b7b3f372f38..adb64db323bea4 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -685,7 +685,7 @@ class REPLServer extends Interface { // The function names are needed for stack trace filtering - they must not // be anonymous, but we can't use 'eval' as a name since it's reserved. const originalEval = eval_; - // eslint-disable-next-line func-name-matching + // eslint-disable-next-line node-core/func-name-matching self.eval = function REPLEval(code, context, file, cb) { replContext.run({ replServer: self }, function REPLEvalInContext() { originalEval(code, context, file, cb); diff --git a/lib/test/reporters.js b/lib/test/reporters.js index 52b54da6935130..a03b17a9eb0bbb 100644 --- a/lib/test/reporters.js +++ b/lib/test/reporters.js @@ -7,9 +7,9 @@ const { let dot; let junit; -let spec; +let specFn; let tap; -let lcov; +let lcovFn; ObjectDefineProperties(module.exports, { __proto__: null, @@ -35,9 +35,9 @@ ObjectDefineProperties(module.exports, { __proto__: null, configurable: true, enumerable: true, - value: function value() { - spec ??= require('internal/test_runner/reporter/spec'); - return ReflectConstruct(spec, arguments); + value: function spec() { + specFn ??= require('internal/test_runner/reporter/spec'); + return ReflectConstruct(specFn, arguments); }, }, tap: { @@ -53,9 +53,9 @@ ObjectDefineProperties(module.exports, { __proto__: null, configurable: true, enumerable: true, - value: function value() { - lcov ??= require('internal/test_runner/reporter/lcov'); - return ReflectConstruct(lcov, arguments); + value: function lcov() { + lcovFn ??= require('internal/test_runner/reporter/lcov'); + return ReflectConstruct(lcovFn, arguments); }, }, }); diff --git a/lib/zlib.js b/lib/zlib.js index fc970c306dc437..47726337cf720d 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -48,6 +48,7 @@ const { } = require('internal/errors'); const { Transform, finished } = require('stream'); const { + assignFunctionName, deprecateInstantiation, } = require('internal/util'); const { @@ -978,9 +979,9 @@ function createProperty(ctor) { __proto__: null, configurable: true, enumerable: true, - value: function(options) { + value: assignFunctionName(`create${ctor.name}`, function(options) { return new ctor(options); - }, + }), }; } diff --git a/test/parallel/test-eslint-func-name-matching.js b/test/parallel/test-eslint-func-name-matching.js new file mode 100644 index 00000000000000..588ba9e234b430 --- /dev/null +++ b/test/parallel/test-eslint-func-name-matching.js @@ -0,0 +1,1108 @@ +// @fileoverview Tests for func-name-matching rule. +// @author Annie Zhang + +'use strict'; +const common = require('../common'); +if ((!common.hasCrypto) || (!common.hasIntl)) { + common.skip('ESLint tests require crypto and Intl'); +} +common.skipIfEslintMissing(); + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require('../../tools/eslint-rules/func-name-matching'); +const RuleTester = require('../../tools/eslint/node_modules/eslint').RuleTester; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); + +ruleTester.run('func-name-matching', rule, { + valid: [ + 'var foo;', + 'var foo = function foo() {};', + { code: 'var foo = function foo() {};', options: ['always'] }, + { code: 'var foo = function bar() {};', options: ['never'] }, + 'var foo = function() {}', + { code: 'var foo = () => {}', languageOptions: { ecmaVersion: 6 } }, + 'foo = function foo() {};', + { code: 'foo = function foo() {};', options: ['always'] }, + { code: 'foo = function bar() {};', options: ['never'] }, + { + code: 'foo &&= function foo() {};', + languageOptions: { ecmaVersion: 2021 }, + }, + { + code: 'obj.foo ||= function foo() {};', + languageOptions: { ecmaVersion: 2021 }, + }, + { + code: 'obj[\'foo\'] ??= function foo() {};', + languageOptions: { ecmaVersion: 2021 }, + }, + 'obj.foo = function foo() {};', + { code: 'obj.foo = function foo() {};', options: ['always'] }, + { code: 'obj.foo = function bar() {};', options: ['never'] }, + 'obj.foo = function() {};', + { code: 'obj.foo = function() {};', options: ['always'] }, + { code: 'obj.foo = function() {};', options: ['never'] }, + 'obj.bar.foo = function foo() {};', + { code: 'obj.bar.foo = function foo() {};', options: ['always'] }, + { code: 'obj.bar.foo = function baz() {};', options: ['never'] }, + 'obj[\'foo\'] = function foo() {};', + { code: 'obj[\'foo\'] = function foo() {};', options: ['always'] }, + { code: 'obj[\'foo\'] = function bar() {};', options: ['never'] }, + 'obj[\'foo//bar\'] = function foo() {};', + { code: 'obj[\'foo//bar\'] = function foo() {};', options: ['always'] }, + { code: 'obj[\'foo//bar\'] = function foo() {};', options: ['never'] }, + 'obj[foo] = function bar() {};', + { code: 'obj[foo] = function bar() {};', options: ['always'] }, + { code: 'obj[foo] = function bar() {};', options: ['never'] }, + 'var obj = {foo: function foo() {}};', + { code: 'var obj = {foo: function foo() {}};', options: ['always'] }, + { code: 'var obj = {foo: function bar() {}};', options: ['never'] }, + 'var obj = {\'foo\': function foo() {}};', + { code: 'var obj = {\'foo\': function foo() {}};', options: ['always'] }, + { code: 'var obj = {\'foo\': function bar() {}};', options: ['never'] }, + 'var obj = {\'foo//bar\': function foo() {}};', + { + code: 'var obj = {\'foo//bar\': function foo() {}};', + options: ['always'], + }, + { + code: 'var obj = {\'foo//bar\': function foo() {}};', + options: ['never'], + }, + 'var obj = {foo: function() {}};', + { code: 'var obj = {foo: function() {}};', options: ['always'] }, + { code: 'var obj = {foo: function() {}};', options: ['never'] }, + { + code: 'var obj = {[foo]: function bar() {}} ', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var obj = {[\'x\' + 2]: function bar(){}};', + languageOptions: { ecmaVersion: 6 }, + }, + 'obj[\'x\' + 2] = function bar(){};', + { + code: 'var [ bar ] = [ function bar(){} ];', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'function a(foo = function bar() {}) {}', + languageOptions: { ecmaVersion: 6 }, + }, + 'module.exports = function foo(name) {};', + 'module[\'exports\'] = function foo(name) {};', + { + code: 'module.exports = function foo(name) {};', + options: [{ includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'module.exports = function foo(name) {};', + options: ['always', { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'module.exports = function foo(name) {};', + options: ['never', { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'module[\'exports\'] = function foo(name) {};', + options: [{ includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'module[\'exports\'] = function foo(name) {};', + options: ['always', { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'module[\'exports\'] = function foo(name) {};', + options: ['never', { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[\'foo\']: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[\'foo\']: function foo() {}})', + options: ['always'], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[\'foo\']: function bar() {}})', + options: ['never'], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[\'❤\']: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[foo]: function bar() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[null]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[1]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[true]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[`x`]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[/abc/]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[[1, 2, 3]]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({[{x: 1}]: function foo() {}})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '[] = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({} = function foo() {})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '[a] = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({a} = function foo() {})', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var [] = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {} = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var [a] = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var {a} = function foo() {}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: '({ value: function value() {} })', + options: [{ considerPropertyDescriptor: true }], + }, + { + code: 'obj.foo = function foo() {};', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'obj.bar.foo = function foo() {};', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'var obj = {foo: function foo() {}};', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'var obj = {foo: function() {}};', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'var obj = { value: function value() {} }', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Object.defineProperty(foo, \'bar\', { value: function bar() {} })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Object.defineProperties(foo, { bar: { value: function bar() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Object.create(proto, { bar: { value: function bar() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Object.defineProperty(foo, \'b\' + \'ar\', { value: function bar() {} })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Object.defineProperties(foo, { [\'bar\']: { value: function bar() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'Object.create(proto, { [\'bar\']: { value: function bar() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'Object.defineProperty(foo, \'bar\', { value() {} })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'Object.defineProperties(foo, { bar: { value() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'Object.create(proto, { bar: { value() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'Reflect.defineProperty(foo, \'bar\', { value: function bar() {} })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Reflect.defineProperty(foo, \'b\' + \'ar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + }, + { + code: 'Reflect.defineProperty(foo, \'bar\', { value() {} })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'foo({ value: function value() {} })', + options: ['always', { considerPropertyDescriptor: true }], + }, + + // Class fields, private names are ignored + { + code: 'class C { x = function () {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { x = function () {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'x\' = function () {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'x\' = function () {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function () {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function () {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function () {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function () {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'x\'] = function () {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'x\'] = function () {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { x = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { x = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'x\' = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'x\' = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [x] = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'x\'] = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'x\'] = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'xy \' = function foo() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { \'xy \' = function xy() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'xy \'] = function foo() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [\'xy \'] = function xy() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { 1 = function x0() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { 1 = function x1() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [1] = function x0() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [1] = function x1() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [f()] = function g() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { [f()] = function f() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { static x = function x() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { static x = function y() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { x = (function y() {})(); }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { x = (function x() {})(); }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: '(class { x = function x() {}; })', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: '(class { x = function y() {}; })', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { this.#x = function x() {}; } }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { this.#x = function x() {}; } }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { this.#x = function y() {}; } }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { this.#x = function y() {}; } }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { a.b.#x = function x() {}; } }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { a.b.#x = function x() {}; } }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { a.b.#x = function y() {}; } }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'class C { #x; foo() { a.b.#x = function y() {}; } }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: 'var obj = { \'\\u1885\': function foo() {} };', // Not a valid identifier in es5 + languageOptions: { + ecmaVersion: 5, + sourceType: 'script', + }, + }, + ], + invalid: [ + { + code: 'let foo = function bar() {};', + options: ['always'], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchVariable', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'let foo = function bar() {};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchVariable', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'foo = function bar() {};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchVariable', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'foo &&= function bar() {};', + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: 'matchVariable', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'obj.foo ||= function bar() {};', + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'obj[\'foo\'] ??= function bar() {};', + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'obj.foo = function bar() {};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'obj.bar.foo = function bar() {};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'obj[\'foo\'] = function bar() {};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'let obj = {foo: function bar() {}};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'let obj = {\'foo\': function bar() {}};', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: '({[\'foo\']: function bar() {}})', + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'module.exports = function foo(name) {};', + options: [{ includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: 'exports' }, + }, + ], + }, + { + code: 'module.exports = function foo(name) {};', + options: ['always', { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: 'exports' }, + }, + ], + }, + { + code: 'module.exports = function exports(name) {};', + options: ['never', { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'exports', name: 'exports' }, + }, + ], + }, + { + code: 'module[\'exports\'] = function foo(name) {};', + options: [{ includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: 'exports' }, + }, + ], + }, + { + code: 'module[\'exports\'] = function foo(name) {};', + options: ['always', { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: 'exports' }, + }, + ], + }, + { + code: 'module[\'exports\'] = function exports(name) {};', + options: ['never', { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'exports', name: 'exports' }, + }, + ], + }, + { + code: 'var foo = function foo(name) {};', + options: ['never'], + errors: [ + { + messageId: 'notMatchVariable', + data: { funcName: 'foo', name: 'foo' }, + }, + ], + }, + { + code: 'obj.foo = function foo(name) {};', + options: ['never'], + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'foo', name: 'foo' }, + }, + ], + }, + { + code: 'Object.defineProperty(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'Object.defineProperties(foo, { bar: { value: function baz() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'Object.create(proto, { bar: { value: function baz() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'var obj = { value: function foo(name) {} }', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: 'value' }, + }, + ], + }, + { + code: 'Object.defineProperty(foo, \'bar\', { value: function bar() {} })', + options: ['never', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: 'Object.defineProperties(foo, { bar: { value: function bar() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: 'Object.create(proto, { bar: { value: function bar() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: 'Reflect.defineProperty(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'Reflect.defineProperty(foo, \'bar\', { value: function bar() {} })', + options: ['never', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + // Tests for Node's primordials (ObjectDefineProperty, ObjectDefineProperties, ReflectDefineProperty) + { + code: 'ObjectDefineProperty(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'ReflectDefineProperty(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'ObjectDefineProperties(foo, { bar: { value: function baz() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'foo({ value: function bar() {} })', + options: ['always', { considerPropertyDescriptor: true }], + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'value' }, + }, + ], + }, + + // Optional chaining + { + code: '(obj?.aaa).foo = function bar() {};', + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'bar', name: 'foo' }, + }, + ], + }, + { + code: 'Object?.defineProperty(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: '(Object?.defineProperty)(foo, \'bar\', { value: function baz() {} })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'Object?.defineProperty(foo, \'bar\', { value: function bar() {} })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: '(Object?.defineProperty)(foo, \'bar\', { value: function bar() {} })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: 'Object?.defineProperties(foo, { bar: { value: function baz() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: '(Object?.defineProperties)(foo, { bar: { value: function baz() {} } })', + options: ['always', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'baz', name: 'bar' }, + }, + ], + }, + { + code: 'Object?.defineProperties(foo, { bar: { value: function bar() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + { + code: '(Object?.defineProperties)(foo, { bar: { value: function bar() {} } })', + options: ['never', { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'bar', name: 'bar' }, + }, + ], + }, + + // class fields + { + code: 'class C { x = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'y', name: 'x' }, + }, + ], + }, + { + code: 'class C { x = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'x', name: 'x' }, + }, + ], + }, + { + code: 'class C { \'x\' = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'y', name: 'x' }, + }, + ], + }, + { + code: 'class C { \'x\' = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'x', name: 'x' }, + }, + ], + }, + { + code: 'class C { [\'x\'] = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'y', name: 'x' }, + }, + ], + }, + { + code: 'class C { [\'x\'] = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'x', name: 'x' }, + }, + ], + }, + { + code: 'class C { static x = function y() {}; }', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'y', name: 'x' }, + }, + ], + }, + { + code: 'class C { static x = function x() {}; }', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'x', name: 'x' }, + }, + ], + }, + { + code: '(class { x = function y() {}; })', + options: ['always'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'y', name: 'x' }, + }, + ], + }, + { + code: '(class { x = function x() {}; })', + options: ['never'], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: 'notMatchProperty', + data: { funcName: 'x', name: 'x' }, + }, + ], + }, + { + code: 'var obj = { \'\\u1885\': function foo() {} };', // Valid identifier in es2015 + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: 'matchProperty', + data: { funcName: 'foo', name: '\u1885' }, + }, + ], + }, + ], +}); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 6a1da6d4129fbd..1e305f0a45fb7c 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -107,7 +107,7 @@ assert.strictEqual(util.inspect({}), '{}'); assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }'); assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }'); assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }'); -// eslint-disable-next-line func-name-matching +// eslint-disable-next-line node-core/func-name-matching assert.strictEqual(util.inspect({ a: async function abc() {} }), '{ a: [AsyncFunction: abc] }'); assert.strictEqual(util.inspect({ a: async () => {} }), diff --git a/tools/eslint-rules/func-name-matching.js b/tools/eslint-rules/func-name-matching.js new file mode 100644 index 00000000000000..a2ff661f2cc7d7 --- /dev/null +++ b/tools/eslint-rules/func-name-matching.js @@ -0,0 +1,329 @@ +// @fileoverview Rule to require function names to match the name of the variable or property to which they +// are assigned. +// @author Annie Zhang +// @author Pavel Strashkin + +'use strict'; + +//-------------------------------------------------------------------------- +// Requirements +//-------------------------------------------------------------------------- + +const astUtils = require('../eslint/node_modules/eslint/lib/rules/utils/ast-utils'); +const esutils = require('../eslint/node_modules/esutils'); + +//-------------------------------------------------------------------------- +// Helpers +//-------------------------------------------------------------------------- + +/** + * Determines if a pattern is `module.exports` or `module['exports']` + * @param {ASTNode} pattern The left side of the AssignmentExpression + * @returns {boolean} True if the pattern is `module.exports` or `module['exports']` + */ +function isModuleExports(pattern) { + if ( + pattern.type === 'MemberExpression' && + pattern.object.type === 'Identifier' && + pattern.object.name === 'module' + ) { + // module.exports + if ( + pattern.property.type === 'Identifier' && + pattern.property.name === 'exports' + ) { + return true; + } + + // module['exports'] + if ( + pattern.property.type === 'Literal' && + pattern.property.value === 'exports' + ) { + return true; + } + } + return false; +} + +/** + * Determines if a string name is a valid identifier + * @param {string} name The string to be checked + * @param {number} ecmaVersion The ECMAScript version if specified in the parserOptions config + * @returns {boolean} True if the string is a valid identifier + */ +function isIdentifier(name, ecmaVersion) { + if (ecmaVersion >= 2015) { + return esutils.keyword.isIdentifierES6(name); + } + return esutils.keyword.isIdentifierES5(name); +} + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const alwaysOrNever = { enum: ['always', 'never'] }; +const optionsObject = { + type: 'object', + properties: { + considerPropertyDescriptor: { + type: 'boolean', + }, + includeCommonJSModuleExports: { + type: 'boolean', + }, + }, + additionalProperties: false, +}; + +/** @type {import('../types').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + + docs: { + description: + 'Require function names to match the name of the variable or property to which they are assigned', + recommended: false, + frozen: true, + url: 'https://eslint.org/docs/latest/rules/func-name-matching', + }, + + schema: { + anyOf: [ + { + type: 'array', + additionalItems: false, + items: [alwaysOrNever, optionsObject], + }, + { + type: 'array', + additionalItems: false, + items: [optionsObject], + }, + ], + }, + + messages: { + matchProperty: + 'Function name `{{funcName}}` should match property name `{{name}}`.', + matchVariable: + 'Function name `{{funcName}}` should match variable name `{{name}}`.', + notMatchProperty: + 'Function name `{{funcName}}` should not match property name `{{name}}`.', + notMatchVariable: + 'Function name `{{funcName}}` should not match variable name `{{name}}`.', + }, + }, + + create(context) { + const options = + (typeof context.options[0] === 'object' ? + context.options[0] : context.options[1]) || {}; + const nameMatches = + typeof context.options[0] === 'string' ? + context.options[0] : 'always'; + const considerPropertyDescriptor = options.considerPropertyDescriptor; + const includeModuleExports = options.includeCommonJSModuleExports; + const ecmaVersion = context.languageOptions.ecmaVersion; + + /** + * Check whether node is a certain CallExpression. + * @param {string} objName object name + * @param {string} funcName function name + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if node matches CallExpression + */ + function isPropertyCall(objName, funcName, node) { + if (!node) { + return false; + } + return ( + node.type === 'CallExpression' && + astUtils.isSpecificMemberAccess(node.callee, objName, funcName) + ); + } + + function isIdentifierCall(expectedIdentifierName, node) { + if (!node) { + return false; + } + return node.type === 'CallExpression' && + astUtils.isSpecificId(node.callee, expectedIdentifierName); + } + + /** + * Compares identifiers based on the nameMatches option + * @param {string} x the first identifier + * @param {string} y the second identifier + * @returns {boolean} whether the two identifiers should warn. + */ + function shouldWarn(x, y) { + return ( + (nameMatches === 'always' && x !== y) || + (nameMatches === 'never' && x === y) + ); + } + + /** + * Reports + * @param {ASTNode} node The node to report + * @param {string} name The variable or property name + * @param {string} funcName The function name + * @param {boolean} isProp True if the reported node is a property assignment + * @returns {void} + */ + function report(node, name, funcName, isProp) { + let messageId; + + if (nameMatches === 'always' && isProp) { + messageId = 'matchProperty'; + } else if (nameMatches === 'always') { + messageId = 'matchVariable'; + } else if (isProp) { + messageId = 'notMatchProperty'; + } else { + messageId = 'notMatchVariable'; + } + context.report({ + node, + messageId, + data: { + name, + funcName, + }, + }); + } + + /** + * Determines whether a given node is a string literal + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the node is a string literal + */ + function isStringLiteral(node) { + return node.type === 'Literal' && typeof node.value === 'string'; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + 'VariableDeclarator[init.type="FunctionExpression"][id.type="Identifier"]'(node) { + if ( + node.init.id && + shouldWarn(node.id.name, node.init.id.name) + ) { + report(node, node.id.name, node.init.id.name, false); + } + }, + + 'AssignmentExpression[right.type="FunctionExpression"][left.type=/^(Identifier|MemberExpression)$/]'(node) { + if ( + (node.left.computed && node.left.property.type !== 'Literal') || + (!includeModuleExports && isModuleExports(node.left)) + ) { + return; + } + + const isProp = node.left.type === 'MemberExpression'; + const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name; + + if ( + node.right.id && + name && + isIdentifier(name) && + shouldWarn(name, node.right.id.name) + ) { + report(node, name, node.right.id.name, isProp); + } + }, + + ':matches(Property,PropertyDefinition)[value.type="FunctionExpression"][value.id]'(node) { + + if (node.key.type === 'Identifier' && !node.computed) { + const functionName = node.value.id.name; + let propertyName = node.key.name; + + if ( + considerPropertyDescriptor && + propertyName === 'value' && + node.parent.type === 'ObjectExpression' + ) { + if ( + isPropertyCall( + 'Object', + 'defineProperty', + node.parent.parent, + ) || + isPropertyCall( + 'Reflect', + 'defineProperty', + node.parent.parent, + ) || + isIdentifierCall('ObjectDefineProperty', node.parent.parent) || + isIdentifierCall('ReflectDefineProperty', node.parent.parent) + ) { + const property = node.parent.parent.arguments[1]; + + if ( + isStringLiteral(property) && + shouldWarn(property.value, functionName) + ) { + report( + node, + property.value, + functionName, + true, + ); + } + } else if ( + isPropertyCall( + 'Object', + 'defineProperties', + node.parent.parent.parent.parent, + ) || + isIdentifierCall('ObjectDefineProperties', node.parent.parent.parent.parent) + ) { + propertyName = node.parent.parent.key.name; + if ( + !node.parent.parent.computed && + shouldWarn(propertyName, functionName) + ) { + report(node, propertyName, functionName, true); + } + } else if ( + isPropertyCall( + 'Object', + 'create', + node.parent.parent.parent.parent, + ) + ) { + propertyName = node.parent.parent.key.name; + if ( + !node.parent.parent.computed && + shouldWarn(propertyName, functionName) + ) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + return; + } + + if ( + isStringLiteral(node.key) && + isIdentifier(node.key.value, ecmaVersion) && + shouldWarn(node.key.value, node.value.id.name) + ) { + report(node, node.key.value, node.value.id.name, true); + } + }, + }; + }, +};