diff --git a/doc/api/modules.md b/doc/api/modules.md index 4ef79b855bd877..6af0dea947f82e 100644 --- a/doc/api/modules.md +++ b/doc/api/modules.md @@ -1114,7 +1114,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 2beadb519c4954..a099b87d795d0c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -180,7 +180,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', @@ -351,6 +350,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 618e48a2b892be..9795bd399c017b 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -258,7 +258,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 f86efb261f0c9d..4132a162996e3c 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 26f2e837d74f6f..0fe4de86cebd7e 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -49,6 +49,7 @@ const { } = require('internal/validators'); const { previewEntries } = internalBinding('util'); const { Buffer: { isBuffer } } = require('buffer'); +const { assignFunctionName } = require('internal/util'); const { inspect, formatWithOptions, @@ -172,9 +173,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 }; @@ -187,19 +188,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, { @@ -222,12 +223,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, @@ -262,12 +263,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 = internalIndentationMap.get(this) || ''; @@ -305,12 +306,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); @@ -325,25 +326,25 @@ ObjectDefineProperties(Console.prototype, { } return color ? kColorInspectOptions : kNoColorInspectOptions; - }, + }), }, [kFormatForStdout]: { __proto__: null, ...consolePropAttributes, - value: function(args) { + value: assignFunctionName(kFormatForStdout, function(args) { 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) { 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 6e16d75d586944..49b42332ae39a2 100644 --- a/lib/internal/modules/esm/hooks.js +++ b/lib/internal/modules/esm/hooks.js @@ -721,7 +721,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 b6ac42302a126b..b6456e1a096b25 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -131,7 +131,7 @@ function loadCJSModule(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 1bc46616556612..acc8257f18a214 100644 --- a/lib/internal/per_context/domexception.js +++ b/lib/internal/per_context/domexception.js @@ -38,7 +38,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 1081db9ffcf661..3af161b9de7551 100644 --- a/lib/internal/streams/writable.js +++ b/lib/internal/streams/writable.js @@ -85,6 +85,8 @@ const { kOnConstructed, } = require('internal/streams/utils'); +const { assignFunctionName } = require('internal/util'); + const { errorOrDestroy } = destroyImpl; ObjectSetPrototypeOf(Writable.prototype, Stream.prototype); @@ -435,12 +437,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/util/types.js b/lib/internal/util/types.js index 393608331aa1f5..e56d3b72010fdc 100644 --- a/lib/internal/util/types.js +++ b/lib/internal/util/types.js @@ -76,40 +76,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/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 57d0bcfd84c0f9..8679767f7250cd 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -49,6 +49,7 @@ const { } = require('internal/errors'); const { Transform, finished } = require('stream'); const { + assignFunctionName, deprecateInstantiation, } = require('internal/util'); const { @@ -937,9 +938,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 b06f6814e4985a..e3d735f0281374 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..82b5598c5a2b25 --- /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); + } + }, + + 'Property[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); + } + }, + }; + }, +};