prefer-array-some.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 {removeMemberExpressionProperty} = require('./fix/index.js');
  7. const {isLiteral, isUndefined} = require('./ast/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. && isUndefined(node.parent.right)
  34. )
  35. || (
  36. (
  37. node.parent.operator === '!='
  38. || node.parent.operator === '=='
  39. )
  40. // eslint-disable-next-line unicorn/no-null
  41. && isLiteral(node.parent.right, null)
  42. )
  43. );
  44. const arrayFilterCallSelector = [
  45. 'BinaryExpression',
  46. '[right.type="Literal"]',
  47. '[right.raw="0"]',
  48. // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
  49. matches(['[operator=">"]', '[operator="!=="]']),
  50. ' > ',
  51. `${memberExpressionSelector('length')}.left`,
  52. ' > ',
  53. `${methodCallSelector('filter')}.object`,
  54. ].join('');
  55. /** @param {import('eslint').Rule.RuleContext} context */
  56. const create = context => ({
  57. [arrayFindCallSelector](findCall) {
  58. const isCompare = isCheckingUndefined(findCall);
  59. if (!isCompare && !isBooleanNode(findCall)) {
  60. return;
  61. }
  62. const findProperty = findCall.callee.property;
  63. return {
  64. node: findProperty,
  65. messageId: ERROR_ID_ARRAY_SOME,
  66. suggest: [
  67. {
  68. messageId: SUGGESTION_ID_ARRAY_SOME,
  69. * fix(fixer) {
  70. yield fixer.replaceText(findProperty, 'some');
  71. if (!isCompare) {
  72. return;
  73. }
  74. const parenthesizedRange = getParenthesizedRange(findCall, context.getSourceCode());
  75. yield fixer.replaceTextRange([parenthesizedRange[1], findCall.parent.range[1]], '');
  76. if (findCall.parent.operator === '!=' || findCall.parent.operator === '!==') {
  77. return;
  78. }
  79. yield fixer.insertTextBeforeRange(parenthesizedRange, '!');
  80. },
  81. },
  82. ],
  83. };
  84. },
  85. [arrayFilterCallSelector](filterCall) {
  86. const filterProperty = filterCall.callee.property;
  87. return {
  88. node: filterProperty,
  89. messageId: ERROR_ID_ARRAY_FILTER,
  90. * fix(fixer) {
  91. // `.filter` to `.some`
  92. yield fixer.replaceText(filterProperty, 'some');
  93. const sourceCode = context.getSourceCode();
  94. const lengthNode = filterCall.parent;
  95. /*
  96. Remove `.length`
  97. `(( (( array.filter() )).length )) > (( 0 ))`
  98. ------------------------^^^^^^^
  99. */
  100. yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);
  101. const compareNode = lengthNode.parent;
  102. /*
  103. Remove `> 0`
  104. `(( (( array.filter() )).length )) > (( 0 ))`
  105. ----------------------------------^^^^^^^^^^
  106. */
  107. yield fixer.removeRange([
  108. getParenthesizedRange(lengthNode, sourceCode)[1],
  109. compareNode.range[1],
  110. ]);
  111. // The `BinaryExpression` always ends with a number or `)`, no need check for ASI
  112. },
  113. };
  114. },
  115. });
  116. /** @type {import('eslint').Rule.RuleModule} */
  117. module.exports = {
  118. create: checkVueTemplate(create),
  119. meta: {
  120. type: 'suggestion',
  121. docs: {
  122. description: 'Prefer `.some(…)` over `.filter(…).length` check and `.find(…)`.',
  123. },
  124. fixable: 'code',
  125. messages,
  126. hasSuggestions: true,
  127. },
  128. };