/** * @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"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * 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('../types').Rule.RuleModule} */ 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/latest/rules/prefer-object-has-own", }, schema: [], messages: { useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'.", }, fixable: "code", }, create(context) { const sourceCode = context.sourceCode; 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 = sourceCode.getScope(node); const variable = astUtils.getVariableByName(scope, "Object"); if ( calleePropertyName === "call" && objectPropertyName === "hasOwnProperty" && isObject && variable && variable.scope.type === "global" ) { context.report({ node, messageId: "useHasOwn", fix(fixer) { if ( sourceCode.getCommentsInside(node.callee) .length > 0 ) { return null; } const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true, }); // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335 if ( tokenJustBeforeNode && tokenJustBeforeNode.range[1] === node.callee.range[0] && !astUtils.canTokensBeAdjacent( tokenJustBeforeNode, "Object.hasOwn", ) ) { return fixer.replaceText( node.callee, " Object.hasOwn", ); } return fixer.replaceText( node.callee, "Object.hasOwn", ); }, }); } }, }; }, };