8000 feat: add `prefer-object-has-own` rule (#15346) · eslint/eslint@eafaf52 · GitHub
[go: up one dir, main page]

Skip to content

Commit eafaf52

Browse files
snitin315Gautam-Arora24mdjermanovic
authored
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>
1 parent 74cf0a0 commit eafaf52

File tree

5 files changed

+501
-0
lines changed

5 files changed

+501
-0
lines changed

docs/rules/prefer-object-has-own.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Prefer `Object.hasOwn()` over `Object.prototype.hasOwnProperty.call()` (prefer-object-has-own)
2+
3+
It is very common to write code like:
4+
5+
```js
6+
if (Object.prototype.hasOwnProperty.call(object, "foo")) {
7+
console.log("has property foo");
8+
}
9+
```
10+
11+
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).
12+
13+
Introduced in ES2022, `Object.hasOwn()` is a shorter alternative to `Object.prototype.hasOwnProperty.call()`:
14+
15+
```js
16+
if (Object.hasOwn(object, "foo")) {
17+
console.log("has property foo")
18+
}
19+
```
20+
21+
## Rule Details
22+
23+
Examples of **incorrect** code for this rule:
24+
25+
```js
26+
/*eslint prefer-object-has-own: "error"*/
27+
28+
Object.prototype.hasOwnProperty.call(obj, "a");
29+
30+
Object.hasOwnProperty.call(obj, "a");
31+
32+
({}).hasOwnProperty.call(obj, "a");
33+
34+
const hasProperty = Object.prototype.hasOwnProperty.call(object, property);
35+
```
36+
37+
Examples of **correct** code for this rule:
38+
39+
```js
40+
/*eslint prefer-object-has-own: "error"*/
41+
42+
Object.hasOwn(obj, "a");
43+
44+
const hasProperty = Object.hasOwn(object, property);
45+
```
46+
47+
## When Not To Use It
48+
49+
This rule should not be used unless ES2022 is supported in your codebase.
50+
51+
## Further Reading
52+
53+
* [Object.hasOwn()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn)

lib/rules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
255255
"prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
256256
"prefer-named-capture-group": () => require("./prefer-named-capture-group"),
257257
"prefer-numeric-literals": () => require("./prefer-numeric-literals"),
258+
"prefer-object-has-own": () => require("./prefer-object-has-own"),
258259
"prefer-object-spread": () => require("./prefer-object-spread"),
259260
"prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
260261
"prefer-reflect": () => require("./prefer-reflect"),

lib/rules/prefer-object-has-own.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* @fileoverview Prefers Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
3+
* @author Nitin Kumar
4+
* @author Gautam Arora
5+
*/
6+
7+
"use strict";
8+
9+
//------------------------------------------------------------------------------
10+
// Requirements
11+
//------------------------------------------------------------------------------
12+
13+
const astUtils = require("./utils/ast-utils");
14+
15+
/**
16+
* Checks if the given node is considered to be an access to a property of `Object.prototype`.
17+
* @param {ASTNode} node `MemberExpression` node to evaluate.
18+
* @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
19+
*/
20+
function hasLeftHandObject(node) {
21+
22+
/*
23+
* ({}).hasOwnProperty.call(obj, prop) - `true`
24+
* ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
25+
*/
26+
if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
27+
return true;
28+
}
29+
30+
const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;
31+
32+
if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
33+
return true;
34+
}
35+
36+
return false;
37+
}
38+
39+
//------------------------------------------------------------------------------
40+
// Rule Definition
41+
//------------------------------------------------------------------------------
42+
43+
/** @type {import('../shared/types').Rule} */
44+
module.exports = {
45+
meta: {
46+
type: "suggestion",
47+
docs: {
48+
description:
49+
"disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
50+
recommended: false,
51+
url: "https://eslint.org/docs/rules/prefer-object-has-own"
52+
},
53+
schema: [],
54+
messages: {
55+
useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
56+
},
57+
fixable: "code"
58+
},
59+
create(context) {
60+
return {
61+
CallExpression(node) {
62+
if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
63+
return;
64+
}
65+
66+
const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
67+
const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
68+
const isObject = hasLeftHandObject(node.callee.object);
69+
70+
// check `Object` scope
71+
const scope = context.getScope();
72+
const variable = astUtils.getVariableByName(scope, "Object");
73+
74+
if (
75+
calleePropertyName === "call" &&
76+
objectPropertyName === "hasOwnProperty" &&
77+
isObject &&
78+
variable && variable.scope.type === "global"
79+
) {
80+
context.report({
81+
node,
82+
messageId: "useHasOwn",
83+
fix(fixer) {
84+
const sourceCode = context.getSourceCode();
85+
86+
if (sourceCode.getCommentsInside(node.callee).length > 0) {
87+
return null;
88+
}
89+
90+
return fixer.replaceText(node.callee, "Object.hasOwn");
91+
}
92+
});
93+
}
94+
}
95+
};
96+
}
97+
};

0 commit comments

Comments
 (0)
0