diff --git a/docs/rules/no-useless-length-check.md b/docs/rules/no-useless-length-check.md new file mode 100644 index 0000000000..2cd5179616 --- /dev/null +++ b/docs/rules/no-useless-length-check.md @@ -0,0 +1,52 @@ +# Disallow useless array length check + +- `Array#some()` returns `false` for an empty array. There is no need to check if the array is not empty. +- `Array#every()` returns `true` for an empty array. There is no need to check if the array is empty. + +We only check `.length === 0`, `.length !== 0`, and `.length > 0`. These zero and non-zero length check styles are allowed in the [`unicorn/explicit-length-check`](./explicit-length-check.md#options) rule. It is recommended to use them together. + +This rule is fixable. + +## Fail + +```js +if (array.length === 0 || array.every(Boolean)); +``` + +```js +if (array.length !== 0 && array.some(Boolean)); +``` + +```js +if (array.length > 0 && array.some(Boolean)); +``` + +```js +const isAllTrulyOrEmpty = array.length === 0 || array.every(Boolean); +``` + +## Pass + +```js +if (array.every(Boolean)); +``` + +```js +if (array.some(Boolean)); +``` + +```js +const isAllTrulyOrEmpty = array.every(Boolean); +``` + +```js +if (array.length === 0 || anotherCheck() || array.every(Boolean)); +``` + +```js +const isNonEmptyAllTrulyArray = array.length > 0 && array.every(Boolean); +``` + +```js +const isEmptyArrayOrAllTruly = array.length === 0 || array.some(Boolean); +``` diff --git a/index.js b/index.js index ec977e5188..4a14814464 100644 --- a/index.js +++ b/index.js @@ -77,6 +77,7 @@ module.exports = { 'unicorn/no-unreadable-array-destructuring': 'error', 'unicorn/no-unsafe-regex': 'off', 'unicorn/no-unused-properties': 'off', + 'unicorn/no-useless-length-check': 'error', 'unicorn/no-useless-undefined': 'error', 'unicorn/no-zero-fractions': 'error', 'unicorn/number-literal-case': 'error', diff --git a/readme.md b/readme.md index cea6ea6802..4caac33fe0 100644 --- a/readme.md +++ b/readme.md @@ -74,6 +74,7 @@ Configure it in `package.json`. "unicorn/no-unreadable-array-destructuring": "error", "unicorn/no-unsafe-regex": "off", "unicorn/no-unused-properties": "off", + "unicorn/no-useless-length-check": "error", "unicorn/no-useless-undefined": "error", "unicorn/no-zero-fractions": "error", "unicorn/number-literal-case": "error", @@ -178,6 +179,7 @@ Each rule has emojis denoting: | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | βœ… | πŸ”§ | | | [no-unsafe-regex](docs/rules/no-unsafe-regex.md) | Disallow unsafe regular expressions. | | | | | [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | | +| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | βœ… | πŸ”§ | | | [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | βœ… | πŸ”§ | | | [no-zero-fractions](docs/rules/no-zero-fractions.md) | Disallow number literals with zero fractions or dangling dots. | βœ… | πŸ”§ | | | [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | βœ… | πŸ”§ | | diff --git a/rules/no-useless-length-check.js b/rules/no-useless-length-check.js new file mode 100644 index 0000000000..a8088ab087 --- /dev/null +++ b/rules/no-useless-length-check.js @@ -0,0 +1,148 @@ +'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', name: '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); + } + }; + } + } + }; +}; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Disallow useless array length check.' + }, + fixable: 'code', + messages + } +}; diff --git a/test/no-useless-length-check.mjs b/test/no-useless-length-check.mjs new file mode 100644 index 0000000000..cca77db199 --- /dev/null +++ b/test/no-useless-length-check.mjs @@ -0,0 +1,167 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + // `.length === 0 || .every()` + 'array.length === 0 ?? array.every(Boolean)', + 'array.length === 0 && array.every(Boolean)', + '(array.length === 0) + (array.every(Boolean))', + 'array.length === 1 || array.every(Boolean)', + 'array.length === "0" || array.every(Boolean)', + 'array.length === 0. || array.every(Boolean)', + 'array.length === 0x0 || array.every(Boolean)', + 'array.length !== 0 || array.every(Boolean)', + 'array.length == 0 || array.every(Boolean)', + '0 === array.length || array.every(Boolean)', + 'array?.length === 0 || array.every(Boolean)', + 'array.notLength === 0 || array.every(Boolean)', + 'array[length] === 0 || array.every(Boolean)', + 'array.length === 0 || array.every?.(Boolean)', + 'array.length === 0 || array?.every(Boolean)', + 'array.length === 0 || array.every', + 'array.length === 0 || array[every](Boolean)', + 'array1.length === 0 || array2.every(Boolean)', + + // `.length !== 0 && .some()` + 'array.length !== 0 ?? array.some(Boolean)', + 'array.length !== 0 || array.some(Boolean)', + '(array.length !== 0) - (array.some(Boolean))', + 'array.length !== 1 && array.some(Boolean)', + 'array.length !== "0" && array.some(Boolean)', + 'array.length !== 0. && array.some(Boolean)', + 'array.length !== 0x0 && array.some(Boolean)', + 'array.length === 0 && array.some(Boolean)', + 'array.length <= 0 && array.some(Boolean)', + 'array.length != 0 && array.some(Boolean)', + '0 !== array.length && array.some(Boolean)', + 'array?.length !== 0 && array.some(Boolean)', + 'array.notLength !== 0 && array.some(Boolean)', + 'array[length] !== 0 && array.some(Boolean)', + 'array.length !== 0 && array.some?.(Boolean)', + 'array.length !== 0 && array?.some(Boolean)', + 'array.length !== 0 && array.some', + 'array.length !== 0 && array.notSome(Boolean)', + 'array.length !== 0 && array[some](Boolean)', + 'array1.length !== 0 && array2.some(Boolean)', + + // `.length > 0 && .some()` + 'array.length > 0 ?? array.some(Boolean)', + 'array.length > 0 || array.some(Boolean)', + '(array.length > 0) - (array.some(Boolean))', + 'array.length > 1 && array.some(Boolean)', + 'array.length > "0" && array.some(Boolean)', + 'array.length > 0. && array.some(Boolean)', + 'array.length > 0x0 && array.some(Boolean)', + 'array.length >= 0 && array.some(Boolean)', + '0 > array.length && array.some(Boolean)', + '0 < array.length && array.some(Boolean)', + 'array?.length > 0 && array.some(Boolean)', + 'array.notLength > 0 && array.some(Boolean)', + 'array.length > 0 && array.some?.(Boolean)', + 'array.length > 0 && array?.some(Boolean)', + 'array.length > 0 && array.some', + 'array.length > 0 && array.notSome(Boolean)', + 'array.length > 0 && array[some](Boolean)', + 'array1.length > 0 && array2.some(Boolean)', + outdent` + if ( + foo && + array.length !== 0 && + bar && + array.some(Boolean) + ) { + // ... + } + `, + + '(foo && array.length === 0) || array.every(Boolean) && foo', + 'array.length === 0 || (array.every(Boolean) && foo)', + '(foo || array.length > 0) && array.some(Boolean)', + 'array.length > 0 && (array.some(Boolean) || foo)' + ], + invalid: [ + 'array.length === 0 || array.every(Boolean)', + 'array.length > 0 && array.some(Boolean)', + 'array.length !== 0 && array.some(Boolean)', + outdent` + (( + (( + (( array )).length + )) === (( 0 )) + || + (( + (( array )).every(Boolean) + )) + )) + `, + outdent` + (( + (( + (( array )).every(Boolean) + )) + || + (( + (( array )).length + )) === (( 0 )) + )) + `, + 'if ((( array.length > 0 )) && array.some(Boolean));', + outdent` + if ( + array.length !== 0 && + array.some(Boolean) && + foo + ) { + // ... + } + `, + '(array.length === 0 || array.every(Boolean)) || foo', + 'foo || (array.length === 0 || array.every(Boolean))', + '(array.length > 0 && array.some(Boolean)) && foo', + 'foo && (array.length > 0 && array.some(Boolean))', + 'array.every(Boolean) || array.length === 0', + 'array.some(Boolean) && array.length !== 0', + 'array.some(Boolean) && array.length > 0', + 'foo && array.length > 0 && array.some(Boolean)', + 'foo || array.length === 0 || array.every(Boolean)', + '(foo || array.length === 0) || array.every(Boolean)', + 'array.length === 0 || (array.every(Boolean) || foo)', + '(foo && array.length > 0) && array.some(Boolean)', + 'array.length > 0 && (array.some(Boolean) && foo)', + 'array.every(Boolean) || array.length === 0 || array.every(Boolean)', + 'array.length === 0 || array.every(Boolean) || array.length === 0', + outdent` + array1.every(Boolean) + || (( array1.length === 0 || array2.length === 0 )) // Both useless + || array2.every(Boolean) + `, + // Real world case from this rule initial implementation, but added useless length check + outdent` + function isUselessLengthCheckNode({node, operator, siblings}) { + return ( + ( + operator === '||' && + zeroLengthChecks.has(node) && + siblings.length > 0 && + siblings.some(condition => + arrayEveryCalls.has(condition) && + isSameReference(node.left.object, condition.callee.object) + ) + ) || + ( + operator === '&&' && + nonZeroLengthChecks.has(node) && + siblings.length > 0 && + siblings.some(condition => + arraySomeCalls.has(condition) && + isSameReference(node.left.object, condition.callee.object) + ) + ) + ); + } + ` + ] +}); diff --git a/test/snapshots/no-useless-length-check.mjs.md b/test/snapshots/no-useless-length-check.mjs.md new file mode 100644 index 0000000000..25ccf77920 --- /dev/null +++ b/test/snapshots/no-useless-length-check.mjs.md @@ -0,0 +1,558 @@ +# Snapshot report for `test/no-useless-length-check.mjs` + +The actual snapshot is saved in `no-useless-length-check.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## Invalid #1 + 1 | array.length === 0 || array.every(Boolean) + +> Output + + `␊ + 1 | array.every(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.length === 0 || array.every(Boolean)␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #2 + 1 | array.length > 0 && array.some(Boolean) + +> Output + + `␊ + 1 | array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.length > 0 && array.some(Boolean)␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #3 + 1 | array.length !== 0 && array.some(Boolean) + +> Output + + `␊ + 1 | array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.length !== 0 && array.some(Boolean)␊ + | ^^^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #4 + 1 | (( + 2 | (( + 3 | (( array )).length + 4 | )) === (( 0 )) + 5 | || + 6 | (( + 7 | (( array )).every(Boolean) + 8 | )) + 9 | )) + +> Output + + `␊ + 1 | ((␊ + 2 | ((␊ + 3 | (( array )).every(Boolean)␊ + 4 | ))␊ + 5 | ))␊ + ` + +> Error 1/1 + + `␊ + 1 | ((␊ + 2 | ((␊ + > 3 | (( array )).length␊ + | ^^^^^^␊ + > 4 | )) === (( 0 ))␊ + | ^^^^^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + 5 | ||␊ + 6 | ((␊ + 7 | (( array )).every(Boolean)␊ + 8 | ))␊ + 9 | ))␊ + ` + +## Invalid #5 + 1 | (( + 2 | (( + 3 | (( array )).every(Boolean) + 4 | )) + 5 | || + 6 | (( + 7 | (( array )).length + 8 | )) === (( 0 )) + 9 | )) + +> Output + + `␊ + 1 | ((␊ + 2 | ((␊ + 3 | (( array )).every(Boolean)␊ + 4 | ))␊ + 5 | ))␊ + ` + +> Error 1/1 + + `␊ + 1 | ((␊ + 2 | ((␊ + 3 | (( array )).every(Boolean)␊ + 4 | ))␊ + 5 | ||␊ + 6 | ((␊ + > 7 | (( array )).length␊ + | ^^^^^^␊ + > 8 | )) === (( 0 ))␊ + | ^^^^^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + 9 | ))␊ + ` + +## Invalid #6 + 1 | if ((( array.length > 0 )) && array.some(Boolean)); + +> Output + + `␊ + 1 | if (array.some(Boolean));␊ + ` + +> Error 1/1 + + `␊ + > 1 | if ((( array.length > 0 )) && array.some(Boolean));␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #7 + 1 | if ( + 2 | array.length !== 0 && + 3 | array.some(Boolean) && + 4 | foo + 5 | ) { + 6 | // ... + 7 | } + +> Output + + `␊ + 1 | if (␊ + 2 | array.some(Boolean) &&␊ + 3 | foo␊ + 4 | ) {␊ + 5 | // ...␊ + 6 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | if (␊ + > 2 | array.length !== 0 &&␊ + | ^^^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + 3 | array.some(Boolean) &&␊ + 4 | foo␊ + 5 | ) {␊ + 6 | // ...␊ + 7 | }␊ + ` + +## Invalid #8 + 1 | (array.length === 0 || array.every(Boolean)) || foo + +> Output + + `␊ + 1 | (array.every(Boolean)) || foo␊ + ` + +> Error 1/1 + + `␊ + > 1 | (array.length === 0 || array.every(Boolean)) || foo␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #9 + 1 | foo || (array.length === 0 || array.every(Boolean)) + +> Output + + `␊ + 1 | foo || (array.every(Boolean))␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo || (array.length === 0 || array.every(Boolean))␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #10 + 1 | (array.length > 0 && array.some(Boolean)) && foo + +> Output + + `␊ + 1 | (array.some(Boolean)) && foo␊ + ` + +> Error 1/1 + + `␊ + > 1 | (array.length > 0 && array.some(Boolean)) && foo␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #11 + 1 | foo && (array.length > 0 && array.some(Boolean)) + +> Output + + `␊ + 1 | foo && (array.some(Boolean))␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo && (array.length > 0 && array.some(Boolean))␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #12 + 1 | array.every(Boolean) || array.length === 0 + +> Output + + `␊ + 1 | array.every(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.every(Boolean) || array.length === 0␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #13 + 1 | array.some(Boolean) && array.length !== 0 + +> Output + + `␊ + 1 | array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.some(Boolean) && array.length !== 0␊ + | ^^^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #14 + 1 | array.some(Boolean) && array.length > 0 + +> Output + + `␊ + 1 | array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.some(Boolean) && array.length > 0␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #15 + 1 | foo && array.length > 0 && array.some(Boolean) + +> Output + + `␊ + 1 | foo && array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo && array.length > 0 && array.some(Boolean)␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #16 + 1 | foo || array.length === 0 || array.every(Boolean) + +> Output + + `␊ + 1 | foo || array.every(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo || array.length === 0 || array.every(Boolean)␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #17 + 1 | (foo || array.length === 0) || array.every(Boolean) + +> Output + + `␊ + 1 | (foo) || array.every(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | (foo || array.length === 0) || array.every(Boolean)␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #18 + 1 | array.length === 0 || (array.every(Boolean) || foo) + +> Output + + `␊ + 1 | (array.every(Boolean) || foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.length === 0 || (array.every(Boolean) || foo)␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #19 + 1 | (foo && array.length > 0) && array.some(Boolean) + +> Output + + `␊ + 1 | (foo) && array.some(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | (foo && array.length > 0) && array.some(Boolean)␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #20 + 1 | array.length > 0 && (array.some(Boolean) && foo) + +> Output + + `␊ + 1 | (array.some(Boolean) && foo)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.length > 0 && (array.some(Boolean) && foo)␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + ` + +## Invalid #21 + 1 | array.every(Boolean) || array.length === 0 || array.every(Boolean) + +> Output + + `␊ + 1 | array.every(Boolean) || array.every(Boolean)␊ + ` + +> Error 1/1 + + `␊ + > 1 | array.every(Boolean) || array.length === 0 || array.every(Boolean)␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #22 + 1 | array.length === 0 || array.every(Boolean) || array.length === 0 + +> Output + + `␊ + 1 | array.every(Boolean)␊ + ` + +> Error 1/2 + + `␊ + > 1 | array.length === 0 || array.every(Boolean) || array.length === 0␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +> Error 2/2 + + `␊ + > 1 | array.length === 0 || array.every(Boolean) || array.length === 0␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + ` + +## Invalid #23 + 1 | array1.every(Boolean) + 2 | || (( array1.length === 0 || array2.length === 0 )) // Both useless + 3 | || array2.every(Boolean) + +> Output + + `␊ + 1 | array1.every(Boolean) // Both useless␊ + 2 | || array2.every(Boolean)␊ + ` + +> Error 1/2 + + `␊ + 1 | array1.every(Boolean)␊ + > 2 | || (( array1.length === 0 || array2.length === 0 )) // Both useless␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + 3 | || array2.every(Boolean)␊ + ` + +> Error 2/2 + + `␊ + 1 | array1.every(Boolean)␊ + > 2 | || (( array1.length === 0 || array2.length === 0 )) // Both useless␊ + | ^^^^^^^^^^^^ The empty check is useless as \`Array#every()\` returns \`true\` for an empty array.␊ + 3 | || array2.every(Boolean)␊ + ` + +## Invalid #24 + 1 | function isUselessLengthCheckNode({node, operator, siblings}) { + 2 | return ( + 3 | ( + 4 | operator === '||' && + 5 | zeroLengthChecks.has(node) && + 6 | siblings.length > 0 && + 7 | siblings.some(condition => + 8 | arrayEveryCalls.has(condition) && + 9 | isSameReference(node.left.object, condition.callee.object) + 10 | ) + 11 | ) || + 12 | ( + 13 | operator === '&&' && + 14 | nonZeroLengthChecks.has(node) && + 15 | siblings.length > 0 && + 16 | siblings.some(condition => + 17 | arraySomeCalls.has(condition) && + 18 | isSameReference(node.left.object, condition.callee.object) + 19 | ) + 20 | ) + 21 | ); + 22 | } + +> Output + + `␊ + 1 | function isUselessLengthCheckNode({node, operator, siblings}) {␊ + 2 | return (␊ + 3 | (␊ + 4 | operator === '||' &&␊ + 5 | zeroLengthChecks.has(node) &&␊ + 6 | siblings.some(condition =>␊ + 7 | arrayEveryCalls.has(condition) &&␊ + 8 | isSameReference(node.left.object, condition.callee.object)␊ + 9 | )␊ + 10 | ) ||␊ + 11 | (␊ + 12 | operator === '&&' &&␊ + 13 | nonZeroLengthChecks.has(node) &&␊ + 14 | siblings.some(condition =>␊ + 15 | arraySomeCalls.has(condition) &&␊ + 16 | isSameReference(node.left.object, condition.callee.object)␊ + 17 | )␊ + 18 | )␊ + 19 | );␊ + 20 | }␊ + ` + +> Error 1/2 + + `␊ + 1 | function isUselessLengthCheckNode({node, operator, siblings}) {␊ + 2 | return (␊ + 3 | (␊ + 4 | operator === '||' &&␊ + 5 | zeroLengthChecks.has(node) &&␊ + > 6 | siblings.length > 0 &&␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + 7 | siblings.some(condition =>␊ + 8 | arrayEveryCalls.has(condition) &&␊ + 9 | isSameReference(node.left.object, condition.callee.object)␊ + 10 | )␊ + 11 | ) ||␊ + 12 | (␊ + 13 | operator === '&&' &&␊ + 14 | nonZeroLengthChecks.has(node) &&␊ + 15 | siblings.length > 0 &&␊ + 16 | siblings.some(condition =>␊ + 17 | arraySomeCalls.has(condition) &&␊ + 18 | isSameReference(node.left.object, condition.callee.object)␊ + 19 | )␊ + 20 | )␊ + 21 | );␊ + 22 | }␊ + ` + +> Error 2/2 + + `␊ + 1 | function isUselessLengthCheckNode({node, operator, siblings}) {␊ + 2 | return (␊ + 3 | (␊ + 4 | operator === '||' &&␊ + 5 | zeroLengthChecks.has(node) &&␊ + 6 | siblings.length > 0 &&␊ + 7 | siblings.some(condition =>␊ + 8 | arrayEveryCalls.has(condition) &&␊ + 9 | isSameReference(node.left.object, condition.callee.object)␊ + 10 | )␊ + 11 | ) ||␊ + 12 | (␊ + 13 | operator === '&&' &&␊ + 14 | nonZeroLengthChecks.has(node) &&␊ + > 15 | siblings.length > 0 &&␊ + | ^^^^^^^^^^ The non-empty check is useless as \`Array#some()\` returns \`false\` for an empty array.␊ + 16 | siblings.some(condition =>␊ + 17 | arraySomeCalls.has(condition) &&␊ + 18 | isSameReference(node.left.object, condition.callee.object)␊ + 19 | )␊ + 20 | )␊ + 21 | );␊ + 22 | }␊ + ` diff --git a/test/snapshots/no-useless-length-check.mjs.snap b/test/snapshots/no-useless-length-check.mjs.snap new file mode 100644 index 0000000000..746f748b21 Binary files /dev/null and b/test/snapshots/no-useless-length-check.mjs.snap differ