SourceTermAnalysisSystem_vue/node_modules/eslint-plugin-unicorn/rules/no-useless-length-check.js
2026-05-15 10:22:44 +08:00

154 lines
3.9 KiB
JavaScript

'use strict';
const {isMethodCall, isMemberExpression} = require('./ast/index.js');
const {
getParenthesizedRange,
isSameReference,
isLogicalExpression,
} = require('./utils/index.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.',
};
// We assume the user already follows `unicorn/explicit-length-check`. These are allowed in that rule.
const isLengthCompareZero = node =>
node.type === 'BinaryExpression'
&& node.right.type === 'Literal'
&& node.right.raw === '0'
&& isMemberExpression(node.left, {property: 'length', optional: false})
&& isLogicalExpression(node.parent);
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 {
BinaryExpression(node) {
if (isLengthCompareZero(node)) {
const {operator} = node;
if (operator === '===') {
zeroLengthChecks.add(node);
} else if (operator === '>' || operator === '!==') {
nonZeroLengthChecks.add(node);
}
}
},
CallExpression(node) {
if (
isMethodCall(node, {
optionalCall: false,
optionalMember: false,
computed: false,
})
&& node.callee.property.type === 'Identifier'
) {
if (node.callee.property.name === 'some') {
arraySomeCalls.add(node);
} else if (node.callee.property.name === 'every') {
arrayEveryCalls.add(node);
}
}
},
LogicalExpression(node) {
if (isLogicalExpression(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;
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.',
recommended: true,
},
fixable: 'code',
messages,
},
};