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 | // Helpers
|
---|
17 | //------------------------------------------------------------------------------
|
---|
18 |
|
---|
19 | /**
|
---|
20 | * Checks if the given node is considered to be an access to a property of `Object.prototype`.
|
---|
21 | * @param {ASTNode} node `MemberExpression` node to evaluate.
|
---|
22 | * @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
|
---|
23 | */
|
---|
24 | function hasLeftHandObject(node) {
|
---|
25 |
|
---|
26 | /*
|
---|
27 | * ({}).hasOwnProperty.call(obj, prop) - `true`
|
---|
28 | * ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
|
---|
29 | */
|
---|
30 | if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
|
---|
31 | return true;
|
---|
32 | }
|
---|
33 |
|
---|
34 | const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;
|
---|
35 |
|
---|
36 | if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
|
---|
37 | return true;
|
---|
38 | }
|
---|
39 |
|
---|
40 | return false;
|
---|
41 | }
|
---|
42 |
|
---|
43 | //------------------------------------------------------------------------------
|
---|
44 | // Rule Definition
|
---|
45 | //------------------------------------------------------------------------------
|
---|
46 |
|
---|
47 | /** @type {import('../shared/types').Rule} */
|
---|
48 | module.exports = {
|
---|
49 | meta: {
|
---|
50 | type: "suggestion",
|
---|
51 | docs: {
|
---|
52 | description:
|
---|
53 | "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
|
---|
54 | recommended: false,
|
---|
55 | url: "https://eslint.org/docs/latest/rules/prefer-object-has-own"
|
---|
56 | },
|
---|
57 | schema: [],
|
---|
58 | messages: {
|
---|
59 | useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
|
---|
60 | },
|
---|
61 | fixable: "code"
|
---|
62 | },
|
---|
63 | create(context) {
|
---|
64 |
|
---|
65 | const sourceCode = context.sourceCode;
|
---|
66 |
|
---|
67 | return {
|
---|
68 | CallExpression(node) {
|
---|
69 | if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
|
---|
70 | return;
|
---|
71 | }
|
---|
72 |
|
---|
73 | const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
|
---|
74 | const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
|
---|
75 | const isObject = hasLeftHandObject(node.callee.object);
|
---|
76 |
|
---|
77 | // check `Object` scope
|
---|
78 | const scope = sourceCode.getScope(node);
|
---|
79 | const variable = astUtils.getVariableByName(scope, "Object");
|
---|
80 |
|
---|
81 | if (
|
---|
82 | calleePropertyName === "call" &&
|
---|
83 | objectPropertyName === "hasOwnProperty" &&
|
---|
84 | isObject &&
|
---|
85 | variable && variable.scope.type === "global"
|
---|
86 | ) {
|
---|
87 | context.report({
|
---|
88 | node,
|
---|
89 | messageId: "useHasOwn",
|
---|
90 | fix(fixer) {
|
---|
91 |
|
---|
92 | if (sourceCode.getCommentsInside(node.callee).length > 0) {
|
---|
93 | return null;
|
---|
94 | }
|
---|
95 |
|
---|
96 | const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true });
|
---|
97 |
|
---|
98 | // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
|
---|
99 | if (
|
---|
100 | tokenJustBeforeNode &&
|
---|
101 | tokenJustBeforeNode.range[1] === node.callee.range[0] &&
|
---|
102 | !astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn")
|
---|
103 | ) {
|
---|
104 | return fixer.replaceText(node.callee, " Object.hasOwn");
|
---|
105 | }
|
---|
106 |
|
---|
107 | return fixer.replaceText(node.callee, "Object.hasOwn");
|
---|
108 | }
|
---|
109 | });
|
---|
110 | }
|
---|
111 | }
|
---|
112 | };
|
---|
113 | }
|
---|
114 | };
|
---|