/** * @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'); }, }); } }, }; }, };