routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
import {isMethodCall, isMemberExpression} from './ast/index.js';
|
||||
import {getParenthesizedRange, isSameReference, isLogicalExpression} from './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),
|
||||
}));
|
||||
}
|
||||
|
||||
context.on('BinaryExpression', node => {
|
||||
if (isLengthCompareZero(node)) {
|
||||
const {operator} = node;
|
||||
if (operator === '===') {
|
||||
zeroLengthChecks.add(node);
|
||||
} else if (operator === '>' || operator === '!==') {
|
||||
nonZeroLengthChecks.add(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.on('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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.on('LogicalExpression', node => {
|
||||
if (isLogicalExpression(node)) {
|
||||
logicalExpressions.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
context.on('Program:exit', function * () {
|
||||
const nodes = new Set(logicalExpressions.flatMap(logicalExpression =>
|
||||
getUselessLengthCheckNode(logicalExpression)));
|
||||
const {sourceCode} = context;
|
||||
|
||||
for (const node of nodes) {
|
||||
yield {
|
||||
loc: {
|
||||
start: sourceCode.getLoc(node.left.property).start,
|
||||
end: sourceCode.getLoc(node).end,
|
||||
},
|
||||
messageId: zeroLengthChecks.has(node) ? 'zero' : 'non-zero',
|
||||
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
||||
fix(fixer) {
|
||||
const {left, right} = node.parent;
|
||||
const leftRange = getParenthesizedRange(left, context);
|
||||
const rightRange = getParenthesizedRange(right, context);
|
||||
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} */
|
||||
const config = {
|
||||
create,
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description: 'Disallow useless array length check.',
|
||||
recommended: 'unopinionated',
|
||||
},
|
||||
fixable: 'code',
|
||||
messages,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user