-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
prefer-object-has-own
rule (#15346)
* 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
1 parent
74cf0a0
commit eafaf52
Showing
5 changed files
with
501 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); | ||
} | ||
}); | ||
} | ||
} | ||
}; | ||
} | ||
}; |
Oops, something went wrong.