no-array-reduce.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. 'use strict';
  2. const {methodCallSelector} = require('./selectors/index.js');
  3. const {arrayPrototypeMethodSelector, notFunctionSelector} = require('./selectors/index.js');
  4. const MESSAGE_ID = 'no-reduce';
  5. const messages = {
  6. [MESSAGE_ID]: '`Array#{{method}}()` is not allowed',
  7. };
  8. const prototypeSelector = method => [
  9. methodCallSelector(method),
  10. arrayPrototypeMethodSelector({
  11. path: 'callee.object',
  12. methods: ['reduce', 'reduceRight'],
  13. }),
  14. ].join('');
  15. const cases = [
  16. // `array.{reduce,reduceRight}()`
  17. {
  18. selector: [
  19. methodCallSelector({methods: ['reduce', 'reduceRight'], minimumArguments: 1, maximumArguments: 2}),
  20. notFunctionSelector('arguments.0'),
  21. ].join(''),
  22. getMethodNode: callExpression => callExpression.callee.property,
  23. isSimpleOperation(callExpression) {
  24. const [callback] = callExpression.arguments;
  25. return (
  26. callback
  27. && (
  28. // `array.reduce((accumulator, element) => accumulator + element)`
  29. (callback.type === 'ArrowFunctionExpression' && callback.body.type === 'BinaryExpression')
  30. // `array.reduce((accumulator, element) => {return accumulator + element;})`
  31. // `array.reduce(function (accumulator, element){return accumulator + element;})`
  32. || (
  33. (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
  34. && callback.body.type === 'BlockStatement'
  35. && callback.body.body.length === 1
  36. && callback.body.body[0].type === 'ReturnStatement'
  37. && callback.body.body[0].argument.type === 'BinaryExpression'
  38. )
  39. )
  40. );
  41. },
  42. },
  43. // `[].{reduce,reduceRight}.call()` and `Array.{reduce,reduceRight}.call()`
  44. {
  45. selector: [
  46. prototypeSelector('call'),
  47. notFunctionSelector('arguments.1'),
  48. ].join(''),
  49. getMethodNode: callExpression => callExpression.callee.object.property,
  50. },
  51. // `[].{reduce,reduceRight}.apply()` and `Array.{reduce,reduceRight}.apply()`
  52. {
  53. selector: prototypeSelector('apply'),
  54. getMethodNode: callExpression => callExpression.callee.object.property,
  55. },
  56. ];
  57. const schema = [
  58. {
  59. type: 'object',
  60. additionalProperties: false,
  61. properties: {
  62. allowSimpleOperations: {
  63. type: 'boolean',
  64. default: true,
  65. },
  66. },
  67. },
  68. ];
  69. /** @param {import('eslint').Rule.RuleContext} context */
  70. const create = context => {
  71. const {allowSimpleOperations} = {allowSimpleOperations: true, ...context.options[0]};
  72. const listeners = {};
  73. for (const {selector, getMethodNode, isSimpleOperation} of cases) {
  74. listeners[selector] = callExpression => {
  75. if (allowSimpleOperations && isSimpleOperation?.(callExpression)) {
  76. return;
  77. }
  78. const methodNode = getMethodNode(callExpression);
  79. return {
  80. node: methodNode,
  81. messageId: MESSAGE_ID,
  82. data: {method: methodNode.name},
  83. };
  84. };
  85. }
  86. return listeners;
  87. };
  88. /** @type {import('eslint').Rule.RuleModule} */
  89. module.exports = {
  90. create,
  91. meta: {
  92. type: 'suggestion',
  93. docs: {
  94. description: 'Disallow `Array#reduce()` and `Array#reduceRight()`.',
  95. },
  96. schema,
  97. messages,
  98. },
  99. };