Skip to content

Commit

Permalink
feat: add prefer-object-has-own rule (#15346)
Browse files Browse the repository at this point in the history
* feat: add `prefer-object-has-own` rule

Co-authored-by: Gautam Arora <gautamarora6248@gmail.com>

* test: add more valid cases

* fix: cover more cases

* chore: add jsdoc type annotation

* fix: cover more cases

* test: add more invalid test cases

* fix: improve meta data

* test: add assertions for location

* docs: update `prefer-object-has-own`

* fix: report for Object with global scope only

* docs: add rule id

* feat: add fixer for `prefer-object-has-own` rule

* chore: udpate comment

* chore: apply suggestions

* docs: udpate

* docs: add example

* chore: update comment

* test: add another valid test case

* docs: fix typo

* fix: improve autofix

* fix: refactor logic and avoid false positives

* refactor: code

* docs: apply latest feedback

* refactor: apply the latest suggestions

* refactor: apply the latest feedback

* test: add more cases

* docs: update

* docs: apply suggestions from code review

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

Co-authored-by: Gautam Arora <gautamarora6248@gmail.com>
Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
3 people committed Dec 11, 2021
1 parent 74cf0a0 commit eafaf52
Show file tree
Hide file tree
Showing 5 changed files with 501 additions and 0 deletions.
53 changes: 53 additions & 0 deletions docs/rules/prefer-object-has-own.md
@@ -0,0 +1,53 @@
# Prefer `Object.hasOwn()` over `Object.prototype.hasOwnProperty.call()` (prefer-object-has-own)

It is very common to write code like:

```js
if (Object.prototype.hasOwnProperty.call(object, "foo")) {
console.log("has property foo");
}
```

This is a common practice because methods on `Object.prototype` can sometimes be unavailable or redefined (see the [no-prototype-builtins](no-prototype-builtins.md) rule).

Introduced in ES2022, `Object.hasOwn()` is a shorter alternative to `Object.prototype.hasOwnProperty.call()`:

```js
if (Object.hasOwn(object, "foo")) {
console.log("has property foo")
}
```

## Rule Details

Examples of **incorrect** code for this rule:

```js
/*eslint prefer-object-has-own: "error"*/

Object.prototype.hasOwnProperty.call(obj, "a");

Object.hasOwnProperty.call(obj, "a");

({}).hasOwnProperty.call(obj, "a");

const hasProperty = Object.prototype.hasOwnProperty.call(object, property);
```

Examples of **correct** code for this rule:

```js
/*eslint prefer-object-has-own: "error"*/

Object.hasOwn(obj, "a");

const hasProperty = Object.hasOwn(object, property);
```

## When Not To Use It

This rule should not be used unless ES2022 is supported in your codebase.

## Further Reading

* [Object.hasOwn()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn)
1 change: 1 addition & 0 deletions lib/rules/index.js
Expand Up @@ -255,6 +255,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
"prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
"prefer-object-has-own": () => require("./prefer-object-has-own"),
"prefer-object-spread": () => require("./prefer-object-spread"),
"prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
"prefer-reflect": () => require("./prefer-reflect"),
Expand Down
97 changes: 97 additions & 0 deletions lib/rules/prefer-object-has-own.js
@@ -0,0 +1,97 @@
/**
* @fileoverview Prefers Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
* @author Nitin Kumar
* @author Gautam Arora
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("./utils/ast-utils");

/**
* Checks if the given node is considered to be an access to a property of `Object.prototype`.
* @param {ASTNode} node `MemberExpression` node to evaluate.
* @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
*/
function hasLeftHandObject(node) {

/*
* ({}).hasOwnProperty.call(obj, prop) - `true`
* ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
*/
if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
return true;
}

const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;

if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
return true;
}

return false;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description:
"disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-object-has-own"
},
schema: [],
messages: {
useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
},
fixable: "code"
},
create(context) {
return {
CallExpression(node) {
if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
return;
}

const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
const isObject = hasLeftHandObject(node.callee.object);

// check `Object` scope
const scope = context.getScope();
const variable = astUtils.getVariableByName(scope, "Object");

if (
calleePropertyName === "call" &&
objectPropertyName === "hasOwnProperty" &&
isObject &&
variable && variable.scope.type === "global"
) {
context.report({
node,
messageId: "useHasOwn",
fix(fixer) {
const sourceCode = context.getSourceCode();

if (sourceCode.getCommentsInside(node.callee).length > 0) {
return null;
}

return fixer.replaceText(node.callee, "Object.hasOwn");
}
});
}
}
};
}
};

0 comments on commit eafaf52

Please sign in to comment.