no-array-method-this-argument.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. 'use strict';
  2. const {hasSideEffect} = require('eslint-utils');
  3. const {methodCallSelector, notFunctionSelector} = require('./selectors/index.js');
  4. const {removeArgument} = require('./fix/index.js');
  5. const {getParentheses, getParenthesizedText} = require('./utils/parentheses.js');
  6. const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
  7. const {isNodeMatches} = require('./utils/is-node-matches.js');
  8. const ERROR = 'error';
  9. const SUGGESTION_BIND = 'suggestion-bind';
  10. const SUGGESTION_REMOVE = 'suggestion-remove';
  11. const messages = {
  12. [ERROR]: 'Do not use the `this` argument in `Array#{{method}}()`.',
  13. [SUGGESTION_REMOVE]: 'Remove the second argument.',
  14. [SUGGESTION_BIND]: 'Use a bound function.',
  15. };
  16. const ignored = [
  17. 'lodash.every',
  18. '_.every',
  19. 'underscore.every',
  20. 'lodash.filter',
  21. '_.filter',
  22. 'underscore.filter',
  23. 'Vue.filter',
  24. 'R.filter',
  25. 'lodash.find',
  26. '_.find',
  27. 'underscore.find',
  28. 'R.find',
  29. 'lodash.findIndex',
  30. '_.findIndex',
  31. 'underscore.findIndex',
  32. 'R.findIndex',
  33. 'lodash.flatMap',
  34. '_.flatMap',
  35. 'lodash.forEach',
  36. '_.forEach',
  37. 'React.Children.forEach',
  38. 'Children.forEach',
  39. 'R.forEach',
  40. 'lodash.map',
  41. '_.map',
  42. 'underscore.map',
  43. 'React.Children.map',
  44. 'Children.map',
  45. 'jQuery.map',
  46. '$.map',
  47. 'R.map',
  48. 'lodash.some',
  49. '_.some',
  50. 'underscore.some',
  51. ];
  52. const selector = [
  53. methodCallSelector({
  54. methods: [
  55. 'every',
  56. 'filter',
  57. 'find',
  58. 'findIndex',
  59. 'flatMap',
  60. 'forEach',
  61. 'map',
  62. 'some',
  63. ],
  64. argumentsLength: 2,
  65. }),
  66. notFunctionSelector('arguments.0'),
  67. ].join('');
  68. function removeThisArgument(callExpression, sourceCode) {
  69. return fixer => removeArgument(fixer, callExpression.arguments[1], sourceCode);
  70. }
  71. function useBoundFunction(callExpression, sourceCode) {
  72. return function * (fixer) {
  73. yield removeThisArgument(callExpression, sourceCode)(fixer);
  74. const [callback, thisArgument] = callExpression.arguments;
  75. const callbackParentheses = getParentheses(callback, sourceCode);
  76. const isParenthesized = callbackParentheses.length > 0;
  77. const callbackLastToken = isParenthesized
  78. ? callbackParentheses[callbackParentheses.length - 1]
  79. : callback;
  80. if (
  81. !isParenthesized
  82. && shouldAddParenthesesToMemberExpressionObject(callback, sourceCode)
  83. ) {
  84. yield fixer.insertTextBefore(callbackLastToken, '(');
  85. yield fixer.insertTextAfter(callbackLastToken, ')');
  86. }
  87. const thisArgumentText = getParenthesizedText(thisArgument, sourceCode);
  88. // `thisArgument` was a argument, no need add extra parentheses
  89. yield fixer.insertTextAfter(callbackLastToken, `.bind(${thisArgumentText})`);
  90. };
  91. }
  92. /** @param {import('eslint').Rule.RuleContext} context */
  93. const create = context => {
  94. const sourceCode = context.getSourceCode();
  95. return {
  96. [selector](callExpression) {
  97. const {callee} = callExpression;
  98. if (isNodeMatches(callee, ignored)) {
  99. return;
  100. }
  101. const method = callee.property.name;
  102. const [callback, thisArgument] = callExpression.arguments;
  103. const problem = {
  104. node: thisArgument,
  105. messageId: ERROR,
  106. data: {method},
  107. };
  108. const thisArgumentHasSideEffect = hasSideEffect(thisArgument, sourceCode);
  109. const isArrowCallback = callback.type === 'ArrowFunctionExpression';
  110. if (isArrowCallback) {
  111. if (thisArgumentHasSideEffect) {
  112. problem.suggest = [
  113. {
  114. messageId: SUGGESTION_REMOVE,
  115. fix: removeThisArgument(callExpression, sourceCode),
  116. },
  117. ];
  118. } else {
  119. problem.fix = removeThisArgument(callExpression, sourceCode);
  120. }
  121. return problem;
  122. }
  123. problem.suggest = [
  124. {
  125. messageId: SUGGESTION_REMOVE,
  126. fix: removeThisArgument(callExpression, sourceCode),
  127. },
  128. {
  129. messageId: SUGGESTION_BIND,
  130. fix: useBoundFunction(callExpression, sourceCode),
  131. },
  132. ];
  133. return problem;
  134. },
  135. };
  136. };
  137. /** @type {import('eslint').Rule.RuleModule} */
  138. module.exports = {
  139. create,
  140. meta: {
  141. type: 'suggestion',
  142. docs: {
  143. description: 'Disallow using the `this` argument in array methods.',
  144. },
  145. fixable: 'code',
  146. hasSuggestions: true,
  147. messages,
  148. },
  149. };