Repository: eslint/eslint
Pull request #15419 (2021-12-11 04:16:45)

fix: improve autofix of `prefer-object-has-own`
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[ ] Documentation update
[x] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md))
[ ] Add autofix to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)

Resolve https://github.com/eslint/eslint/pull/15346#issuecomment-991417335

Improve autofix for the case - `return{}.hasOwnProperty.call(object, property)`

#### Is there anything you'd like reviewers to focus on?
No


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1087ab4e4..5d9aa9536 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,9 +31,9 @@ jobs:
         node: [17.x, 16.x, 14.x, 12.x, "12.22.0"]
         include:
         - os: windows-latest
-          node: "12.x"
+          node: "16.x"
         - os: macOS-latest
-          node: "12.x"
+          node: "16.x"
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v2
diff --git a/docs/rules/array-element-newline.md b/docs/rules/array-element-newline.md
index cee739d74..650a00892 100644
--- a/docs/rules/array-element-newline.md
+++ b/docs/rules/array-element-newline.md
@@ -42,7 +42,12 @@ Examples of **incorrect** code for this rule with the default `"always"` option:
 
 var c = [1, 2];
 var d = [1, 2, 3];
-var e = [
+var e = [1, 2, 3
+];
+var f = [
+  1, 2, 3
+];
+var g = [
     function foo() {
         dosomething();
     }, function bar() {
@@ -63,6 +68,11 @@ var c = [1,
 var d = [1,
     2,
     3];
+var d = [
+  1, 
+  2, 
+  3
+];
 var e = [
     function foo() {
         dosomething();
@@ -109,6 +119,11 @@ var b = [1];
 var c = [1, 2];
 var d = [1, 2, 3];
 var e = [
+    1, 2, 3];
+var f = [
+  1, 2, 3
+];
+var g = [
     function foo() {
         dosomething();
     }, function bar() {
diff --git a/lib/rules/prefer-object-has-own.js b/lib/rules/prefer-object-has-own.js
index dd21e95d4..19abdb315 100644
--- a/lib/rules/prefer-object-has-own.js
+++ b/lib/rules/prefer-object-has-own.js
@@ -12,6 +12,10 @@
 
 const astUtils = require("./utils/ast-utils");
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
 /**
  * Checks if the given node is considered to be an access to a property of `Object.prototype`.
  * @param {ASTNode} node `MemberExpression` node to evaluate.
@@ -87,6 +91,17 @@ module.exports = {
                                 return null;
                             }
 
+                            const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true });
+
+                            // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
+                            if (
+                                tokenJustBeforeNode &&
+                                tokenJustBeforeNode.range[1] === node.callee.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn")
+                            ) {
+                                return fixer.replaceText(node.callee, " Object.hasOwn");
+                            }
+
                             return fixer.replaceText(node.callee, "Object.hasOwn");
                         }
                     });


Test patch:
diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js
index 7f590a5ea..fe520a58a 100644
--- a/lib/rule-tester/rule-tester.js
+++ b/lib/rule-tester/rule-tester.js
@@ -216,6 +216,9 @@ function freezeDeeply(x) {
  * @returns {string} The sanitized text.
  */
 function sanitize(text) {
+    if (typeof text !== "string") {
+        return "";
+    }
     return text.replace(
         /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
         c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
@@ -691,6 +694,13 @@ class RuleTester {
          * @private
          */
         function testValidTemplate(item) {
+            const code = typeof item === "object" ? item.code : item;
+
+            assert.ok(typeof code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
+
             const result = runRuleForItem(item);
             const messages = result.messages;
 
@@ -731,6 +741,10 @@ class RuleTester {
          * @private
          */
         function testInvalidTemplate(item) {
+            assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'");
+            if (item.name) {
+                assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string");
+            }
             assert.ok(item.errors || item.errors === 0,
                 `Did not specify errors for an invalid test of ${ruleName}`);
 
diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js
index 5018a037b..c6383837d 100644
--- a/tests/lib/rule-tester/rule-tester.js
+++ b/tests/lib/rule-tester/rule-tester.js
@@ -2441,6 +2441,65 @@ describe("RuleTester", () => {
 
             return assertion;
         });
+
+        it('should throw if "name" property is not a string', () => {
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: [{ code: "foo", name: 123 }],
+                    invalid: [{ code: "foo" }]
+
+                });
+            }, /Optional test case property 'name' must be a string/u);
+
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: ["foo"],
+                    invalid: [{ code: "foo", name: 123 }]
+                });
+            }, /Optional test case property 'name' must be a string/u);
+        });
+
+        it('should throw if "code" property is not a string', () => {
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: [{ code: 123 }],
+                    invalid: [{ code: "foo" }]
+
+                });
+            }, /Test case must specify a string value for 'code'/u);
+
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: [123],
+                    invalid: [{ code: "foo" }]
+
+                });
+            }, /Test case must specify a string value for 'code'/u);
+
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: ["foo"],
+                    invalid: [{ code: 123 }]
+                });
+            }, /Test case must specify a string value for 'code'/u);
+        });
+
+        it('should throw if "code" property is missing', () => {
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: [{ }],
+                    invalid: [{ code: "foo" }]
+
+                });
+            }, /Test case must specify a string value for 'code'/u);
+
+            assert.throws(() => {
+                ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
+                    valid: ["foo"],
+                    invalid: [{ }]
+                });
+            }, /Test case must specify a string value for 'code'/u);
+        });
     });
 
     // https://github.com/eslint/eslint/issues/11615
diff --git a/tests/lib/rules/prefer-object-has-own.js b/tests/lib/rules/prefer-object-has-own.js
index a34cf7745..d58f7f5c2 100644
--- a/tests/lib/rules/prefer-object-has-own.js
+++ b/tests/lib/rules/prefer-object-has-own.js
@@ -224,6 +224,17 @@ ruleTester.run("prefer-object-has-own", rule, {
                 endColumn: 61
             }]
         },
+        {
+            code: "const hasProperty={}.hasOwnProperty.call(object, property);",
+            output: "const hasProperty=Object.hasOwn(object, property);",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 19,
+                endLine: 1,
+                endColumn: 59
+            }]
+        },
         {
             code: "const hasProperty = (( {}.hasOwnProperty.call(object, property) ));",
             output: "const hasProperty = (( Object.hasOwn(object, property) ));",
@@ -279,6 +290,107 @@ ruleTester.run("prefer-object-has-own", rule, {
                 endColumn: 63
             }]
         },
+
+        // https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
+        {
+            code: "function foo(){return{}.hasOwnProperty.call(object, property)}",
+            output: "function foo(){return Object.hasOwn(object, property)}",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 22,
+                endLine: 1,
+                endColumn: 62
+            }]
+        },
+        {
+            code: "function foo(){return/*comment*/{}.hasOwnProperty.call(object, property)}",
+            output: "function foo(){return/*comment*/Object.hasOwn(object, property)}",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 33,
+                endLine: 1,
+                endColumn: 73
+            }]
+        },
+        {
+            code: "async function foo(){return await{}.hasOwnProperty.call(object, property)}",
+            output: "async function foo(){return await Object.hasOwn(object, property)}",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 34,
+                endLine: 1,
+                endColumn: 74
+            }]
+        },
+        {
+            code: "async function foo(){return await/*comment*/{}.hasOwnProperty.call(object, property)}",
+            output: "async function foo(){return await/*comment*/Object.hasOwn(object, property)}",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 45,
+                endLine: 1,
+                endColumn: 85
+            }]
+        },
+        {
+            code: "for (const x of{}.hasOwnProperty.call(object, property).toString());",
+            output: "for (const x of Object.hasOwn(object, property).toString());",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 16,
+                endLine: 1,
+                endColumn: 56
+            }]
+        },
+        {
+            code: "for (const x of/*comment*/{}.hasOwnProperty.call(object, property).toString());",
+            output: "for (const x of/*comment*/Object.hasOwn(object, property).toString());",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 27,
+                endLine: 1,
+                endColumn: 67
+            }]
+        },
+        {
+            code: "for (const x in{}.hasOwnProperty.call(object, property).toString());",
+            output: "for (const x in Object.hasOwn(object, property).toString());",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 16,
+                endLine: 1,
+                endColumn: 56
+            }]
+        },
+        {
+            code: "for (const x in/*comment*/{}.hasOwnProperty.call(object, property).toString());",
+            output: "for (const x in/*comment*/Object.hasOwn(object, property).toString());",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 27,
+                endLine: 1,
+                endColumn: 67
+            }]
+        },
+        {
+            code: "function foo(){return({}.hasOwnProperty.call)(object, property)}",
+            output: "function foo(){return(Object.hasOwn)(object, property)}",
+            errors: [{
+                messageId: "useHasOwn",
+                line: 1,
+                column: 22,
+                endLine: 1,
+                endColumn: 64
+            }]
+        },
         {
             code: "Object['prototype']['hasOwnProperty']['call'](object, property);",
             output: "Object.hasOwn(object, property);",