prefer-object-has-own.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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 (
  25. node.object.type === "ObjectExpression" &&
  26. node.object.properties.length === 0
  27. ) {
  28. return true;
  29. }
  30. const objectNodeToCheck =
  31. node.object.type === "MemberExpression" &&
  32. astUtils.getStaticPropertyName(node.object) === "prototype"
  33. ? node.object.object
  34. : node.object;
  35. if (
  36. objectNodeToCheck.type === "Identifier" &&
  37. objectNodeToCheck.name === "Object"
  38. ) {
  39. return true;
  40. }
  41. return false;
  42. }
  43. //------------------------------------------------------------------------------
  44. // Rule Definition
  45. //------------------------------------------------------------------------------
  46. /** @type {import('../types').Rule.RuleModule} */
  47. module.exports = {
  48. meta: {
  49. type: "suggestion",
  50. docs: {
  51. description:
  52. "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
  53. recommended: false,
  54. url: "https://eslint.org/docs/latest/rules/prefer-object-has-own",
  55. },
  56. schema: [],
  57. messages: {
  58. useHasOwn:
  59. "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'.",
  60. },
  61. fixable: "code",
  62. },
  63. create(context) {
  64. const sourceCode = context.sourceCode;
  65. return {
  66. CallExpression(node) {
  67. if (
  68. !(
  69. node.callee.type === "MemberExpression" &&
  70. node.callee.object.type === "MemberExpression"
  71. )
  72. ) {
  73. return;
  74. }
  75. const calleePropertyName = astUtils.getStaticPropertyName(
  76. node.callee,
  77. );
  78. const objectPropertyName = astUtils.getStaticPropertyName(
  79. node.callee.object,
  80. );
  81. const isObject = hasLeftHandObject(node.callee.object);
  82. // check `Object` scope
  83. const scope = sourceCode.getScope(node);
  84. const variable = astUtils.getVariableByName(scope, "Object");
  85. if (
  86. calleePropertyName === "call" &&
  87. objectPropertyName === "hasOwnProperty" &&
  88. isObject &&
  89. variable &&
  90. variable.scope.type === "global"
  91. ) {
  92. context.report({
  93. node,
  94. messageId: "useHasOwn",
  95. fix(fixer) {
  96. if (
  97. sourceCode.getCommentsInside(node.callee)
  98. .length > 0
  99. ) {
  100. return null;
  101. }
  102. const tokenJustBeforeNode =
  103. sourceCode.getTokenBefore(node.callee, {
  104. includeComments: true,
  105. });
  106. // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
  107. if (
  108. tokenJustBeforeNode &&
  109. tokenJustBeforeNode.range[1] ===
  110. node.callee.range[0] &&
  111. !astUtils.canTokensBeAdjacent(
  112. tokenJustBeforeNode,
  113. "Object.hasOwn",
  114. )
  115. ) {
  116. return fixer.replaceText(
  117. node.callee,
  118. " Object.hasOwn",
  119. );
  120. }
  121. return fixer.replaceText(
  122. node.callee,
  123. "Object.hasOwn",
  124. );
  125. },
  126. });
  127. }
  128. },
  129. };
  130. },
  131. };