prefer-object-has-own.js 4.2 KB

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