prefer-reflect-apply.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. 'use strict';
  2. const isLiteralValue = require('./utils/is-literal-value.js');
  3. const getPropertyName = require('./utils/get-property-name.js');
  4. const {not, methodCallSelector} = require('./selectors/index.js');
  5. const MESSAGE_ID = 'prefer-reflect-apply';
  6. const messages = {
  7. [MESSAGE_ID]: 'Prefer `Reflect.apply()` over `Function#apply()`.',
  8. };
  9. const selector = [
  10. methodCallSelector({allowComputed: true}),
  11. not(['Literal', 'ArrayExpression', 'ObjectExpression'].map(type => `[callee.object.type=${type}]`)),
  12. ].join('');
  13. const isApplySignature = (argument1, argument2) => (
  14. (
  15. // eslint-disable-next-line unicorn/no-null
  16. isLiteralValue(argument1, null)
  17. || argument1.type === 'ThisExpression'
  18. )
  19. && (
  20. argument2.type === 'ArrayExpression'
  21. || (argument2.type === 'Identifier' && argument2.name === 'arguments')
  22. )
  23. );
  24. const getReflectApplyCall = (sourceCode, target, receiver, argumentsList) => (
  25. `Reflect.apply(${sourceCode.getText(target)}, ${sourceCode.getText(receiver)}, ${sourceCode.getText(argumentsList)})`
  26. );
  27. const fixDirectApplyCall = (node, sourceCode) => {
  28. if (
  29. getPropertyName(node.callee) === 'apply'
  30. && node.arguments.length === 2
  31. && isApplySignature(node.arguments[0], node.arguments[1])
  32. ) {
  33. return fixer => (
  34. fixer.replaceText(
  35. node,
  36. getReflectApplyCall(sourceCode, node.callee.object, node.arguments[0], node.arguments[1]),
  37. )
  38. );
  39. }
  40. };
  41. const fixFunctionPrototypeCall = (node, sourceCode) => {
  42. if (
  43. getPropertyName(node.callee) === 'call'
  44. && getPropertyName(node.callee.object) === 'apply'
  45. && getPropertyName(node.callee.object.object) === 'prototype'
  46. && node.callee.object.object.object
  47. && node.callee.object.object.object.type === 'Identifier'
  48. && node.callee.object.object.object.name === 'Function'
  49. && node.arguments.length === 3
  50. && isApplySignature(node.arguments[1], node.arguments[2])
  51. ) {
  52. return fixer => (
  53. fixer.replaceText(
  54. node,
  55. getReflectApplyCall(sourceCode, node.arguments[0], node.arguments[1], node.arguments[2]),
  56. )
  57. );
  58. }
  59. };
  60. /** @param {import('eslint').Rule.RuleContext} context */
  61. const create = context => ({
  62. [selector]: node => {
  63. const sourceCode = context.getSourceCode();
  64. const fix = fixDirectApplyCall(node, sourceCode) || fixFunctionPrototypeCall(node, sourceCode);
  65. if (fix) {
  66. return {
  67. node,
  68. messageId: MESSAGE_ID,
  69. fix,
  70. };
  71. }
  72. },
  73. });
  74. /** @type {import('eslint').Rule.RuleModule} */
  75. module.exports = {
  76. create,
  77. meta: {
  78. type: 'suggestion',
  79. docs: {
  80. description: 'Prefer `Reflect.apply()` over `Function#apply()`.',
  81. },
  82. fixable: 'code',
  83. messages,
  84. },
  85. };