prefer-object-has-own.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. 'use strict';
  2. const {isNodeMatches, isNodeMatchesNameOrPath} = require('./utils/is-node-matches.js');
  3. const {
  4. objectPrototypeMethodSelector,
  5. methodCallSelector,
  6. callExpressionSelector,
  7. } = require('./selectors/index.js');
  8. const {fixSpaceAroundKeyword} = require('./fix/index.js');
  9. const MESSAGE_ID = 'prefer-object-has-own';
  10. const messages = {
  11. [MESSAGE_ID]: 'Use `Object.hasOwn(…)` instead of `{{description}}(…)`.',
  12. };
  13. const objectPrototypeHasOwnProperty = [
  14. methodCallSelector({method: 'call', argumentsLength: 2}),
  15. ' > ',
  16. objectPrototypeMethodSelector({
  17. path: 'object',
  18. method: 'hasOwnProperty',
  19. }),
  20. '.callee',
  21. ].join('');
  22. const lodashHasFunctions = [
  23. '_.has',
  24. 'lodash.has',
  25. 'underscore.has',
  26. ];
  27. /** @param {import('eslint').Rule.RuleContext} context */
  28. const create = context => {
  29. const {functions: configFunctions} = {
  30. functions: [],
  31. ...context.options[0],
  32. };
  33. const functions = [...configFunctions, ...lodashHasFunctions];
  34. return Object.fromEntries(
  35. [
  36. {
  37. selector: objectPrototypeHasOwnProperty,
  38. description: 'Object.prototype.hasOwnProperty.call',
  39. },
  40. {
  41. selector: `${callExpressionSelector({argumentsLength: 2})} > .callee`,
  42. test: node => isNodeMatches(node, functions),
  43. description: node => functions.find(nameOrPath => isNodeMatchesNameOrPath(node, nameOrPath)).trim(),
  44. },
  45. ].map(({selector, test, description}) => [
  46. selector,
  47. node => {
  48. if (test && !test(node)) {
  49. return;
  50. }
  51. return {
  52. node,
  53. messageId: MESSAGE_ID,
  54. data: {
  55. description: typeof description === 'string' ? description : description(node),
  56. },
  57. /** @param {import('eslint').Rule.RuleFixer} fixer */
  58. * fix(fixer) {
  59. yield fixer.replaceText(node, 'Object.hasOwn');
  60. if (
  61. node.object
  62. && node.object.object
  63. && node.object.object.type === 'ObjectExpression'
  64. ) {
  65. yield * fixSpaceAroundKeyword(fixer, node.parent, context.getSourceCode());
  66. }
  67. },
  68. };
  69. },
  70. ]),
  71. );
  72. };
  73. const schema = [
  74. {
  75. type: 'object',
  76. additionalProperties: false,
  77. properties: {
  78. functions: {
  79. type: 'array',
  80. uniqueItems: true,
  81. },
  82. },
  83. },
  84. ];
  85. /** @type {import('eslint').Rule.RuleModule} */
  86. module.exports = {
  87. create,
  88. meta: {
  89. type: 'suggestion',
  90. docs: {
  91. description: 'Prefer `Object.hasOwn(…)` over `Object.prototype.hasOwnProperty.call(…)`.',
  92. },
  93. fixable: 'code',
  94. schema,
  95. messages,
  96. },
  97. };