prefer-reflect-apply.js 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. 'use strict';
  2. const {getPropertyName} = require('@eslint-community/eslint-utils');
  3. const {not, methodCallSelector} = require('./selectors/index.js');
  4. const {isNullLiteral} = require('./ast/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. isNullLiteral(argument1)
  16. || argument1.type === 'ThisExpression'
  17. )
  18. && (
  19. argument2.type === 'ArrayExpression'
  20. || (argument2.type === 'Identifier' && argument2.name === 'arguments')
  21. )
  22. );
  23. const getReflectApplyCall = (sourceCode, target, receiver, argumentsList) => (
  24. `Reflect.apply(${sourceCode.getText(target)}, ${sourceCode.getText(receiver)}, ${sourceCode.getText(argumentsList)})`
  25. );
  26. const fixDirectApplyCall = (node, sourceCode) => {
  27. if (
  28. getPropertyName(node.callee) === 'apply'
  29. && node.arguments.length === 2
  30. && isApplySignature(node.arguments[0], node.arguments[1])
  31. ) {
  32. return fixer => (
  33. fixer.replaceText(
  34. node,
  35. getReflectApplyCall(sourceCode, node.callee.object, node.arguments[0], node.arguments[1]),
  36. )
  37. );
  38. }
  39. };
  40. const fixFunctionPrototypeCall = (node, sourceCode) => {
  41. if (
  42. getPropertyName(node.callee) === 'call'
  43. && getPropertyName(node.callee.object) === 'apply'
  44. && getPropertyName(node.callee.object.object) === 'prototype'
  45. && node.callee.object.object.object?.type === 'Identifier'
  46. && node.callee.object.object.object.name === 'Function'
  47. && node.arguments.length === 3
  48. && isApplySignature(node.arguments[1], node.arguments[2])
  49. ) {
  50. return fixer => (
  51. fixer.replaceText(
  52. node,
  53. getReflectApplyCall(sourceCode, node.arguments[0], node.arguments[1], node.arguments[2]),
  54. )
  55. );
  56. }
  57. };
  58. /** @param {import('eslint').Rule.RuleContext} context */
  59. const create = context => ({
  60. [selector](node) {
  61. const sourceCode = context.getSourceCode();
  62. const fix = fixDirectApplyCall(node, sourceCode) || fixFunctionPrototypeCall(node, sourceCode);
  63. if (fix) {
  64. return {
  65. node,
  66. messageId: MESSAGE_ID,
  67. fix,
  68. };
  69. }
  70. },
  71. });
  72. /** @type {import('eslint').Rule.RuleModule} */
  73. module.exports = {
  74. create,
  75. meta: {
  76. type: 'suggestion',
  77. docs: {
  78. description: 'Prefer `Reflect.apply()` over `Function#apply()`.',
  79. },
  80. fixable: 'code',
  81. messages,
  82. },
  83. };