Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add prefer-object-has-own rule #15346

Merged
merged 28 commits into from Dec 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c2aef44
feat: add `prefer-object-has-own` rule
snitin315 Nov 23, 2021
cf62f3a
test: add more valid cases
snitin315 Nov 23, 2021
f5d1ad5
fix: cover more cases
snitin315 Nov 24, 2021
0576a3c
chore: add jsdoc type annotation
snitin315 Nov 24, 2021
377ca24
fix: cover more cases
snitin315 Nov 25, 2021
03de5a0
test: add more invalid test cases
snitin315 Nov 25, 2021
f58dda7
fix: improve meta data
snitin315 Nov 25, 2021
b7098eb
test: add assertions for location
snitin315 Nov 25, 2021
c12181e
docs: update `prefer-object-has-own`
snitin315 Nov 25, 2021
b955073
fix: report for Object with global scope only
snitin315 Nov 25, 2021
b738771
docs: add rule id
snitin315 Nov 25, 2021
c3ac824
feat: add fixer for `prefer-object-has-own` rule
snitin315 Dec 2, 2021
e14c304
chore: udpate comment
snitin315 Dec 2, 2021
b095205
chore: apply suggestions
snitin315 Dec 3, 2021
a26dd15
docs: udpate
snitin315 Dec 3, 2021
f70a4b1
docs: add example
snitin315 Dec 3, 2021
ae1fb0f
chore: update comment
snitin315 Dec 3, 2021
6c27915
test: add another valid test case
snitin315 Dec 4, 2021
c9406bb
docs: fix typo
snitin315 Dec 4, 2021
f2b219a
fix: improve autofix
snitin315 Dec 7, 2021
828d9d9
fix: refactor logic and avoid false positives
snitin315 Dec 7, 2021
198166b
refactor: code
snitin315 Dec 7, 2021
427c90d
docs: apply latest feedback
snitin315 Dec 7, 2021
1c84057
refactor: apply the latest suggestions
snitin315 Dec 8, 2021
e36503a
refactor: apply the latest feedback
snitin315 Dec 8, 2021
a502439
test: add more cases
snitin315 Dec 8, 2021
46ba42e
docs: update
snitin315 Dec 9, 2021
18040d4
docs: apply suggestions from code review
snitin315 Dec 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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");
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

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);
```

snitin315 marked this conversation as resolved.
Show resolved Hide resolved
## 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");
}
});
}
}
};
}
};