123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- 'use strict';
- const {methodCallSelector, matches, memberExpressionSelector} = require('./selectors/index.js');
- const {checkVueTemplate} = require('./utils/rule.js');
- const {isBooleanNode} = require('./utils/boolean.js');
- const {getParenthesizedRange} = require('./utils/parentheses.js');
- const {removeMemberExpressionProperty} = require('./fix/index.js');
- const {isLiteral, isUndefined} = require('./ast/index.js');
- const ERROR_ID_ARRAY_SOME = 'some';
- const SUGGESTION_ID_ARRAY_SOME = 'some-suggestion';
- const ERROR_ID_ARRAY_FILTER = 'filter';
- const messages = {
- [ERROR_ID_ARRAY_SOME]: 'Prefer `.some(…)` over `.{{method}}(…)`.',
- [SUGGESTION_ID_ARRAY_SOME]: 'Replace `.{{method}}(…)` with `.some(…)`.',
- [ERROR_ID_ARRAY_FILTER]: 'Prefer `.some(…)` over non-zero length check from `.filter(…)`.',
- };
- const arrayFindOrFindLastCallSelector = methodCallSelector({
- methods: ['find', 'findLast'],
- minimumArguments: 1,
- maximumArguments: 2,
- });
- const isCheckingUndefined = node =>
- node.parent.type === 'BinaryExpression'
- // Not checking yoda expression `null != foo.find()` and `undefined !== foo.find()
- && node.parent.left === node
- && (
- (
- (
- node.parent.operator === '!='
- || node.parent.operator === '=='
- || node.parent.operator === '==='
- || node.parent.operator === '!=='
- )
- && isUndefined(node.parent.right)
- )
- || (
- (
- node.parent.operator === '!='
- || node.parent.operator === '=='
- )
- // eslint-disable-next-line unicorn/no-null
- && isLiteral(node.parent.right, null)
- )
- );
- const arrayFilterCallSelector = [
- 'BinaryExpression',
- '[right.type="Literal"]',
- '[right.raw="0"]',
- // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
- matches(['[operator=">"]', '[operator="!=="]']),
- ' > ',
- `${memberExpressionSelector('length')}.left`,
- ' > ',
- `${methodCallSelector('filter')}.object`,
- ].join('');
- /** @param {import('eslint').Rule.RuleContext} context */
- const create = context => ({
- [arrayFindOrFindLastCallSelector](callExpression) {
- const isCompare = isCheckingUndefined(callExpression);
- if (!isCompare && !isBooleanNode(callExpression)) {
- return;
- }
- const methodNode = callExpression.callee.property;
- return {
- node: methodNode,
- messageId: ERROR_ID_ARRAY_SOME,
- data: {method: methodNode.name},
- suggest: [
- {
- messageId: SUGGESTION_ID_ARRAY_SOME,
- * fix(fixer) {
- yield fixer.replaceText(methodNode, 'some');
- if (!isCompare) {
- return;
- }
- const parenthesizedRange = getParenthesizedRange(callExpression, context.getSourceCode());
- yield fixer.replaceTextRange([parenthesizedRange[1], callExpression.parent.range[1]], '');
- if (callExpression.parent.operator === '!=' || callExpression.parent.operator === '!==') {
- return;
- }
- yield fixer.insertTextBeforeRange(parenthesizedRange, '!');
- },
- },
- ],
- };
- },
- [arrayFilterCallSelector](filterCall) {
- const filterProperty = filterCall.callee.property;
- return {
- node: filterProperty,
- messageId: ERROR_ID_ARRAY_FILTER,
- * fix(fixer) {
- // `.filter` to `.some`
- yield fixer.replaceText(filterProperty, 'some');
- const sourceCode = context.getSourceCode();
- const lengthNode = filterCall.parent;
- /*
- Remove `.length`
- `(( (( array.filter() )).length )) > (( 0 ))`
- ------------------------^^^^^^^
- */
- yield removeMemberExpressionProperty(fixer, lengthNode, sourceCode);
- const compareNode = lengthNode.parent;
- /*
- Remove `> 0`
- `(( (( array.filter() )).length )) > (( 0 ))`
- ----------------------------------^^^^^^^^^^
- */
- yield fixer.removeRange([
- getParenthesizedRange(lengthNode, sourceCode)[1],
- compareNode.range[1],
- ]);
- // The `BinaryExpression` always ends with a number or `)`, no need check for ASI
- },
- };
- },
- });
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create: checkVueTemplate(create),
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Prefer `.some(…)` over `.filter(…).length` check and `.{find,findLast}(…)`.',
- },
- fixable: 'code',
- messages,
- hasSuggestions: true,
- },
- };
|