Repository: eslint/eslint (js)

Issue:
func-name-matching: New option for property descriptors to match against property name
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for requesting a rule change. If you are here for another reason, please see below:

    1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
    2. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**What rule do you want to change?**

func-name-matching

**Does this change cause the rule to produce more or fewer warnings?**

Sometimes more, sometimes fewer. See examples.

**How will the change be implemented? (New option, new default behavior, etc.)?**

New options: `considerPropertyDescriptor: true` (false by default).

**Please provide some example code that this change will affect:**

<!-- Put your code examples here -->
```js
Object.defineProperty(model, 'key', {
  value: function key() {
    //
  }
});

Object.defineProperties(model, {
  key: {
    value: function key() {
      //
    }
  }
});

Object.create(Foo.prototype, {
  key: {
    value: function key() {
      //
    }
  }
});
```

**What does the rule currently do for this code?**

All three have errors: "Function `key` should match property name 'value'."

**What will the rule do after it's changed?**

If `{ considerPropertyDescriptor: true }` is provided in configuration, all 3 examples should pass.

However, examples like these should then start to be warned:

```js
Object.defineProperty(model, 'key', {
  value: function value() {  // Function name `value` should match property name `key`.
    //
  }
});

Object.defineProperties(model, {
  key: {
    value: function value() {  // Function name `value` should match property name `key`.
      //
    }
  }
});

Object.create(Foo.prototype, {
  key: {
    value: function value() {  // Function name `value` should match property name `key`.
      //
    }
  }
});
```

-------

This is another attempt to propose the same thing as #7423. Unfortunately, there were some process hiccups and the contributor no longer seems interested. So I want to carry it forward.

I'll champion.

Pull request:
Update: Add considerPropertyDescriptor option to func-name-matching
Fixes #9926.

**What is the purpose of this pull request?**

Change an existing rule.

**What rule do you want to change?**

`func-name-matching`

**Does this change cause the rule to produce more or fewer warnings?**

Fewer.

**How will the change be implemented?**

Add option `{ considerPropertyDescriptor: true }` to have rule do some additional checks on what the function name should be.

Previously only property key was checked. With this option enabled it will check if the property is within `Object.defineProperty`, `Object.defineProperties`, or `Object.create`.

**Please provide some example code that this change will affect:**

```js
/** eslint func-name-matching: ["always", { "considerPropertyDescriptor": true }] */
Object.defineProperty(foo, 'bar', {
  value: function baz() {}
})
```

**What does the rule currently do for this code?**

    Function name `baz` should match property name `value`

**What will the rule do after it's changed?**

    Function name `baz` should match property name `bar`


Patch:
diff --git a/docs/rules/func-name-matching.md b/docs/rules/func-name-matching.md
index 564671d57..4d502869c 100644
--- a/docs/rules/func-name-matching.md
+++ b/docs/rules/func-name-matching.md
@@ -4,10 +4,6 @@
 
 This rule requires function names to match the name of the variable or property to which they are assigned. The rule will ignore property assignments where the property name is a literal that is not a valid identifier in the ECMAScript version specified in your configuration (default ES5).
 
-## Options
-
-This rule takes an optional string of "always" or "never" (when omitted, it defaults to "always"), and an optional options object with one key, `includeCommonJSModuleExports`, and a boolean value. This option defaults to `false`, which means that `module.exports` and `module["exports"]` are ignored by this rule. If `includeCommonJSModuleExports` is set to true, `module.exports` and `module["exports"]` will be checked by this rule.
-
 Examples of **incorrect** code for this rule:
 
 ```js
@@ -21,14 +17,6 @@ var obj = {foo: function bar() {}};
 ({['foo']: function bar() {}});
 ```
 
-```js
-/*eslint func-name-matching: ["error", { "includeCommonJSModuleExports": true }]*/
-/*eslint func-name-matching: ["error", "always", { "includeCommonJSModuleExports": true }]*/ // these are equivalent
-
-module.exports = function foo(name) {};
-module['exports'] = function foo(name) {};
-```
-
 ```js
 /*eslint func-name-matching: ["error", "never"] */
 
@@ -97,6 +85,52 @@ module.exports = function foo(name) {};
 module['exports'] = function foo(name) {};
 ```
 
+## Options
+
+This rule takes an optional string of "always" or "never" (when omitted, it defaults to "always"), and an optional options object with two properties `considerPropertyDescriptor` and `includeCommonJSModuleExports`.
+
+### considerPropertyDescriptor
+
+A boolean value that defaults to `false`. If `considerPropertyDescriptor` is set to true, the check will take into account the use `Object.create`, `Object.defineProperty`, `Object.defineProperties`, and `Reflect.defineProperty`.
+
+Examples of **correct** code for the `{ considerPropertyDescriptor: true }` option:
+
+```js
+/*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/
+/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent
+var obj = {};
+Object.create(obj, {foo:{value: function foo() {}}});
+Object.defineProperty(obj, 'bar', {value: function bar() {}});
+Object.defineProperties(obj, {baz:{value: function baz() {} }});
+Reflect.defineProperty(obj, 'foo', {value: function foo() {}});
+```
+
+Examples of **incorrect** code for the `{ considerPropertyDescriptor: true }` option:
+
+```js
+/*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/
+/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent
+var obj = {};
+Object.create(obj, {foo:{value: function bar() {}}});
+Object.defineProperty(obj, 'bar', {value: function baz() {}});
+Object.defineProperties(obj, {baz:{value: function foo() {} }});
+Reflect.defineProperty(obj, 'foo', {value: function value() {}});
+```
+
+### includeCommonJSModuleExports
+
+A boolean value that defaults to `false`. If `includeCommonJSModuleExports` is set to true, `module.exports` and `module["exports"]` will be checked by this rule.
+
+Examples of **incorrect** code for the `{ includeCommonJSModuleExports: true }` option:
+
+```js
+/*eslint func-name-matching: ["error", { "includeCommonJSModuleExports": true }]*/
+/*eslint func-name-matching: ["error", "always", { "includeCommonJSModuleExports": true }]*/ // these are equivalent
+
+module.exports = function foo(name) {};
+module['exports'] = function foo(name) {};
+```
+
 ## When Not To Use It
 
 Do not use this rule if you want to allow named functions to have different names from the variable or property to which they are assigned.
diff --git a/lib/rules/func-name-matching.js b/lib/rules/func-name-matching.js
index 85fd7d4f1..b71799596 100644
--- a/lib/rules/func-name-matching.js
+++ b/lib/rules/func-name-matching.js
@@ -58,6 +58,9 @@ const alwaysOrNever = { enum: ["always", "never"] };
 const optionsObject = {
     type: "object",
     properties: {
+        considerPropertyDescriptor: {
+            type: "boolean"
+        },
         includeCommonJSModuleExports: {
             type: "boolean"
         }
@@ -90,9 +93,26 @@ module.exports = {
     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.parserOptions && context.parserOptions.ecmaVersion ? context.parserOptions.ecmaVersion : 5;
 
+        /**
+         * 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" &&
+                node.callee.object.name === objName &&
+                node.callee.property.name === funcName;
+        }
+
         /**
          * Compares identifiers based on the nameMatches option
          * @param {string} x the first identifier
@@ -147,7 +167,6 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
-
             VariableDeclarator(node) {
                 if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
                     return;
@@ -179,9 +198,38 @@ module.exports = {
                 if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
                     return;
                 }
-                if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
-                    report(node, node.key.name, node.value.id.name, true);
-                } else if (
+
+                if (node.key.type === "Identifier") {
+                    const functionName = node.value.id.name;
+                    let propertyName = node.key.name;
+
+                    if (considerPropertyDescriptor && propertyName === "value") {
+                        if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", 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)) {
+                            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)
diff --git a/lib/rules/no-catch-shadow.js b/lib/rules/no-catch-shadow.js
index 907792278..f7abc931c 100644
--- a/lib/rules/no-catch-shadow.js
+++ b/lib/rules/no-catch-shadow.js
@@ -53,7 +53,7 @@ module.exports = {
 
         return {
 
-            CatchClause(node) {
+            "CatchClause[param!=null]"(node) {
                 let scope = context.getScope();
 
                 /*
diff --git a/lib/rules/no-shadow-restricted-names.js b/lib/rules/no-shadow-restricted-names.js
index 7b92521b0..865cee46a 100644
--- a/lib/rules/no-shadow-restricted-names.js
+++ b/lib/rules/no-shadow-restricted-names.js
@@ -46,22 +46,13 @@ module.exports = {
             VariableDeclarator(node) {
                 checkForViolation(node.id);
             },
-            ArrowFunctionExpression(node) {
-                [].map.call(node.params, checkForViolation);
-            },
-            FunctionExpression(node) {
-                if (node.id) {
-                    checkForViolation(node.id);
-                }
-                [].map.call(node.params, checkForViolation);
-            },
-            FunctionDeclaration(node) {
+            ":function"(node) {
                 if (node.id) {
                     checkForViolation(node.id);
-                    [].map.call(node.params, checkForViolation);
                 }
+                node.params.forEach(checkForViolation);
             },
-            CatchClause(node) {
+            "CatchClause[param!=null]"(node) {
                 checkForViolation(node.param);
             }
         };


Test patch:
diff --git a/tests/lib/rules/func-name-matching.js b/tests/lib/rules/func-name-matching.js
index 6f33fbe9a..1459dc1e4 100644
--- a/tests/lib/rules/func-name-matching.js
+++ b/tests/lib/rules/func-name-matching.js
@@ -177,6 +177,84 @@ ruleTester.run("func-name-matching", rule, {
         {
             code: "var {a} = function foo() {}",
             parserOptions: { 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 }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "Object.create(proto, { ['bar']: { value: function bar() {} } })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "Object.defineProperty(foo, 'bar', { value() {} })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "Object.defineProperties(foo, { bar: { value() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "Object.create(proto, { bar: { value() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { 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 }],
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
     invalid: [
@@ -305,6 +383,69 @@ ruleTester.run("func-name-matching", rule, {
             errors: [
                 { message: "Function name `foo` should not match property name `foo`" }
             ]
+        },
+        {
+            code: "Object.defineProperty(foo, 'bar', { value: function baz() {} })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `baz` should match property name `bar`" }
+            ]
+        },
+        {
+            code: "Object.defineProperties(foo, { bar: { value: function baz() {} } })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `baz` should match property name `bar`" }
+            ]
+        },
+        {
+            code: "Object.create(proto, { bar: { value: function baz() {} } })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `baz` should match property name `bar`" }
+            ]
+        },
+        {
+            code: "var obj = { value: function foo(name) {} }",
+            options: ["always", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `foo` should match property name `value`" }
+            ]
+        },
+        {
+            code: "Object.defineProperty(foo, 'bar', { value: function bar() {} })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `bar` should not match property name `bar`" }
+            ]
+        },
+        {
+            code: "Object.defineProperties(foo, { bar: { value: function bar() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `bar` should not match property name `bar`" }
+            ]
+        },
+        {
+            code: "Object.create(proto, { bar: { value: function bar() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `bar` should not match property name `bar`" }
+            ]
+        },
+        {
+            code: "Reflect.defineProperty(foo, 'bar', { value: function baz() {} })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `baz` should match property name `bar`" }
+            ]
+        },
+        {
+            code: "Reflect.defineProperty(foo, 'bar', { value: function bar() {} })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            errors: [
+                { message: "Function name `bar` should not match property name `bar`" }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/no-catch-shadow.js b/tests/lib/rules/no-catch-shadow.js
index cd6c3f32b..c04927ec1 100644
--- a/tests/lib/rules/no-catch-shadow.js
+++ b/tests/lib/rules/no-catch-shadow.js
@@ -40,6 +40,10 @@ ruleTester.run("no-catch-shadow", rule, {
         {
             code: "try {} catch (error) {}",
             env: { shelljs: false }
+        },
+        {
+            code: "try {} catch {}",
+            parserOptions: { ecmaVersion: 2019 }
         }
     ],
     invalid: [
diff --git a/tests/lib/rules/no-shadow-restricted-names.js b/tests/lib/rules/no-shadow-restricted-names.js
index d3b84906e..4655bf715 100644
--- a/tests/lib/rules/no-shadow-restricted-names.js
+++ b/tests/lib/rules/no-shadow-restricted-names.js
@@ -20,7 +20,11 @@ ruleTester.run("no-shadow-restricted-names", rule, {
         "!function foo(bar){ var baz; }",
         "!function(bar){ var baz; }",
         "try {} catch(e) {}",
-        { code: "export default function() {}", parserOptions: { sourceType: "module" } }
+        { code: "export default function() {}", parserOptions: { sourceType: "module" } },
+        {
+            code: "try {} catch {}",
+            parserOptions: { ecmaVersion: 2019 }
+        }
     ],
     invalid: [
         {