123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- 'use strict';
- const {methodCallSelector, matches, memberExpressionSelector} = require('./selectors/index.js');
- const isSameReference = require('./utils/is-same-reference.js');
- const {getParenthesizedRange} = require('./utils/parentheses.js');
- const messages = {
- 'non-zero': 'The non-empty check is useless as `Array#some()` returns `false` for an empty array.',
- zero: 'The empty check is useless as `Array#every()` returns `true` for an empty array.',
- };
- const logicalExpressionSelector = [
- 'LogicalExpression',
- matches(['[operator="||"]', '[operator="&&"]']),
- ].join('');
- // We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
- const lengthCompareZeroSelector = [
- logicalExpressionSelector,
- ' > ',
- 'BinaryExpression',
- memberExpressionSelector({path: 'left', property: 'length'}),
- '[right.type="Literal"]',
- '[right.raw="0"]',
- ].join('');
- const zeroLengthCheckSelector = [
- lengthCompareZeroSelector,
- '[operator="==="]',
- ].join('');
- const nonZeroLengthCheckSelector = [
- lengthCompareZeroSelector,
- matches(['[operator=">"]', '[operator="!=="]']),
- ].join('');
- const arraySomeCallSelector = methodCallSelector('some');
- const arrayEveryCallSelector = methodCallSelector('every');
- function flatLogicalExpression(node) {
- return [node.left, node.right].flatMap(child =>
- child.type === 'LogicalExpression' && child.operator === node.operator
- ? flatLogicalExpression(child)
- : [child],
- );
- }
- /** @param {import('eslint').Rule.RuleContext} context */
- const create = context => {
- const logicalExpressions = [];
- const zeroLengthChecks = new Set();
- const nonZeroLengthChecks = new Set();
- const arraySomeCalls = new Set();
- const arrayEveryCalls = new Set();
- function isUselessLengthCheckNode({node, operator, siblings}) {
- return (
- (
- operator === '||'
- && zeroLengthChecks.has(node)
- && siblings.some(condition =>
- arrayEveryCalls.has(condition)
- && isSameReference(node.left.object, condition.callee.object),
- )
- )
- || (
- operator === '&&'
- && nonZeroLengthChecks.has(node)
- && siblings.some(condition =>
- arraySomeCalls.has(condition)
- && isSameReference(node.left.object, condition.callee.object),
- )
- )
- );
- }
- function getUselessLengthCheckNode(logicalExpression) {
- const {operator} = logicalExpression;
- return flatLogicalExpression(logicalExpression)
- .filter((node, index, conditions) => isUselessLengthCheckNode({
- node,
- operator,
- siblings: [
- conditions[index - 1],
- conditions[index + 1],
- ].filter(Boolean),
- }));
- }
- return {
- [zeroLengthCheckSelector](node) {
- zeroLengthChecks.add(node);
- },
- [nonZeroLengthCheckSelector](node) {
- nonZeroLengthChecks.add(node);
- },
- [arraySomeCallSelector](node) {
- arraySomeCalls.add(node);
- },
- [arrayEveryCallSelector](node) {
- arrayEveryCalls.add(node);
- },
- [logicalExpressionSelector](node) {
- logicalExpressions.push(node);
- },
- * 'Program:exit'() {
- const nodes = new Set(
- logicalExpressions.flatMap(logicalExpression =>
- getUselessLengthCheckNode(logicalExpression),
- ),
- );
- for (const node of nodes) {
- yield {
- loc: {
- start: node.left.property.loc.start,
- end: node.loc.end,
- },
- messageId: zeroLengthChecks.has(node) ? 'zero' : 'non-zero',
- /** @param {import('eslint').Rule.RuleFixer} fixer */
- fix(fixer) {
- const sourceCode = context.getSourceCode();
- const {left, right} = node.parent;
- const leftRange = getParenthesizedRange(left, sourceCode);
- const rightRange = getParenthesizedRange(right, sourceCode);
- const range = [];
- if (left === node) {
- range[0] = leftRange[0];
- range[1] = rightRange[0];
- } else {
- range[0] = leftRange[1];
- range[1] = rightRange[1];
- }
- return fixer.removeRange(range);
- },
- };
- }
- },
- };
- };
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create,
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Disallow useless array length check.',
- },
- fixable: 'code',
- messages,
- },
- };
|