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 24 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
55 changes: 55 additions & 0 deletions docs/rules/prefer-object-has-own.md
@@ -0,0 +1,55 @@
# Prefer `Object.hasOwn()` over `Object.prototype.hasOwnProperty.call()` (prefer-object-has-own)

`Object.hasOwn()` is more accessible than `Object.prototype.hasOwnProperty.call()`.

It is recommended over `Object#hasOwnProperty()` because it works for objects created using `Object.create(null)` and with objects that have overridden the inherited `hasOwnProperty()` method.

```js
const foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Hello World'
};

console.log(Object.hasOwn(foo, 'bar')); // true - re-implementation of hasOwnProperty() does not affect Object

const bar = Object.create(null);
bar.prop = 'exists';

console.log(Object.hasOwn(bar, 'prop')); // true - works irrespective of how the object is created.
```
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

## 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
101 changes: 101 additions & 0 deletions lib/rules/prefer-object-has-own.js
@@ -0,0 +1,101 @@
/**
* @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 to see if a property name 'Object' or an empty 'ObjectExpression' ({}) exists in the subtree.
* @param {ASTnode} node to evalutate.
* @returns {boolean} `true` if object property exists, `false` otherwise.
*/
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
function hasLeftHandObject(node) {
if (!node.object) {
return false;
}
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

/*
* ({}).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 objectPropertyName = astUtils.getStaticPropertyName(node.object);
const objectNodeToCheck = objectPropertyName === "prototype" ? node.object.object : node.object;
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

if (objectNodeToCheck.name === "Object") {
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
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()`",
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
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 = node.callee.object && hasLeftHandObject(node.callee.object);
snitin315 marked this conversation as resolved.
Show resolved Hide resolved
snitin315 marked this conversation as resolved.
Show resolved Hide resolved

// 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");
}
});
}
}
};
}
};