prefer-array-some.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use strict';
  2. const {methodCallSelector, matches, memberExpressionSelector} = require('./selectors/index.js');
  3. const {checkVueTemplate} = require('./utils/rule.js');
  4. const {isBooleanNode} = require('./utils/boolean.js');
  5. const {getParenthesizedRange} = require('./utils/parentheses.js');
  6. const isLiteralValue = require('./utils/is-literal-value.js');
  7. const {removeMemberExpressionProperty} = require('./fix/index.js');
  8. const ERROR_ID_ARRAY_SOME = 'some';
  9. const SUGGESTION_ID_ARRAY_SOME = 'some-suggestion';
  10. const ERROR_ID_ARRAY_FILTER = 'filter';
  11. const messages = {
  12. [ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.find(…)`.',
  13. [SUGGESTION_ID_ARRAY_SOME]: 'Replace `.find(…)` with `.some(…)`.',
  14. [ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.',
  15. };
  16. const arrayFindCallSelector = methodCallSelector({
  17. method: 'find',
  18. minimumArguments: 1,
  19. maximumArguments: 2,
  20. });
  21. const isCheckingUndefined = node =>
  22. node.parent.type === 'BinaryExpression'
  23. // Not checking yoda expression `null != foo.find()` and `undefined !== foo.find()
  24. && node.parent.left === node
  25. && (
  26. (
  27. (
  28. node.parent.operator === '!='
  29. || node.parent.operator === '=='
  30. || node.parent.operator === '==='
  31. || node.parent.operator === '!=='
  32. )
  33. && node.parent.right.type === 'Identifier'
  34. && node.parent.right.name === 'undefined'
  35. )
  36. || (
  37. (
  38. node.parent.operator === '!='
  39. || node.parent.operator === '=='
  40. )
  41. // eslint-disable-next-line unicorn/no-null
  42. && isLiteralValue(node.parent.right, null)
  43. )
  44. );
  45. const arrayFilterCallSelector = [
  46. 'BinaryExpression',
  47. '[right.type="Literal"]',
  48. '[right.raw="0"]',
  49. // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
  50. matches(['[operator=">"]', '[operator="!=="]']),
  51. ' > ',
  52. `${memberExpressionSelector('length')}.left`,
  53. ' > ',
  54. `${methodCallSelector('filter')}.object`,
  55. ].join('');
  56. /** @param {import('eslint').Rule.RuleContext} context */
  57. const create = context => ({
  58. [arrayFindCallSelector](findCall) {
  59. const isCompare = isCheckingUndefined(findCall);
  60. if (!isCompare && !isBooleanNode(findCall)) {
  61. return;
  62. }
  63. const findProperty = findCall.callee.property;
  64. return {
  65. node: findProperty,
  66. messageId: ERROR_ID_ARRAY_SOME,
  67. suggest: [
  68. {
  69. messageId: SUGGESTION_ID_ARRAY_SOME,
  70. * fix(fixer) {
  71. yield fixer.replaceText(findProperty, 'some');
  72. if (!isCompare) {
  73. return;
  74. }
  75. const parenthesizedRange = getParenthesizedRange(findCall, context.getSourceCode());
  76. yield fixer.replaceTextRange([parenthesizedRange[1], findCall.parent.range[1]], '');
  77. if (findCall.parent.operator === '!=' || findCall.parent.operator === '!==') {
  78. return;
  79. }
  80. yield fixer.insertTextBeforeRange(parenthesizedRange, '!');
  81. },
  82. },
  83. ],
  84. };
  85. },
  86. [arrayFilterCallSelector](filterCall) {
  87. const filterProperty = filterCall.callee.property;
  88. return {
  89. node: filterProperty,
  90. messageId: ERROR_ID_ARRAY_FILTER,
  91. * fix(fixer) {
  92. // `.filter` to `.some`
  93. yield fixer.replaceText(filterProperty, 'some');
  94. const sourceCode = context.getSourceCode();
  95. const lengthNode = filterCall.parent;
  96. /*
  97. Remove `.length`
  98. `(( (( array.filter() )).length )) > (( 0 ))`
  99. ------------------------^^^^^^^
  100. */
  101. yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);
  102. const compareNode = lengthNode.parent;
  103. /*
  104. Remove `> 0`
  105. `(( (( array.filter() )).length )) > (( 0 ))`
  106. ----------------------------------^^^^^^^^^^
  107. */
  108. yield fixer.removeRange([
  109. getParenthesizedRange(lengthNode, sourceCode)[1],
  110. compareNode.range[1],
  111. ]);
  112. // The `BinaryExpression` always ends with a number or `)`, no need check for ASI
  113. },
  114. };
  115. },
  116. });
  117. /** @type {import('eslint').Rule.RuleModule} */
  118. module.exports = {
  119. create: checkVueTemplate(create),
  120. meta: {
  121. type: 'suggestion',
  122. docs: {
  123. description: 'Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`.',
  124. },
  125. fixable: 'code',
  126. messages,
  127. hasSuggestions: true,
  128. },
  129. };