From d3e4b805da31c6ed7275e2e2e770b6b0fbcf11c2 Mon Sep 17 00:00:00 2001 From: Axetroy Date: Mon, 9 Sep 2024 21:57:29 +0800 Subject: [PATCH] Add `consistent-existence-index-check` rule (#2425) Co-authored-by: fisker --- .../rules/consistent-existence-index-check.md | 76 ++++ readme.md | 1 + rules/ast/index.js | 1 + rules/ast/is-negative-one.js | 12 + rules/consistent-existence-index-check.js | 133 ++++++ rules/prefer-includes.js | 3 +- rules/utils/resolve-variable-name.js | 4 +- test/consistent-existence-index-check.mjs | 146 +++++++ test/package.mjs | 1 + .../consistent-existence-index-check.mjs.md | 391 ++++++++++++++++++ .../consistent-existence-index-check.mjs.snap | Bin 0 -> 1203 bytes 11 files changed, 764 insertions(+), 4 deletions(-) create mode 100644 docs/rules/consistent-existence-index-check.md create mode 100644 rules/ast/is-negative-one.js create mode 100644 rules/consistent-existence-index-check.js create mode 100644 test/consistent-existence-index-check.mjs create mode 100644 test/snapshots/consistent-existence-index-check.mjs.md create mode 100644 test/snapshots/consistent-existence-index-check.mjs.snap diff --git a/docs/rules/consistent-existence-index-check.md b/docs/rules/consistent-existence-index-check.md new file mode 100644 index 0000000000..00adbf88dc --- /dev/null +++ b/docs/rules/consistent-existence-index-check.md @@ -0,0 +1,76 @@ +# Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()`. + +Prefer using `index === -1` to check if an element does not exist and `index !== -1` to check if an element does exist. + +Similar to the [`explicit-length-check`](explicit-length-check.md) rule. + +## Examples + +```js +const index = foo.indexOf('bar'); + +// ❌ +if (index < 0) {} + +// βœ… +if (index === -1) {} +``` + +```js +const index = foo.indexOf('bar'); + +// ❌ +if (index >= 0) {} + +// βœ… +if (index !== -1) {} +``` + +```js +const index = foo.indexOf('bar'); + +// ❌ +if (index > -1) {} + +// βœ… +if (index !== -1) {} +``` + +```js +const index = foo.lastIndexOf('bar'); + +// ❌ +if (index >= 0) {} + +// βœ… +if (index !== -1) {} +``` + +```js +const index = array.findIndex(element => element > 10); + +// ❌ +if (index < 0) {} + +// βœ… +if (index === -1) {} +``` + +```js +const index = array.findLastIndex(element => element > 10); + +// ❌ +if (index < 0) {} + +// βœ… +if (index === -1) {} +``` diff --git a/readme.md b/readme.md index 4d1e5864bc..4d2af71eba 100644 --- a/readme.md +++ b/readme.md @@ -114,6 +114,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [catch-error-name](docs/rules/catch-error-name.md) | Enforce a specific parameter name in catch clauses. | βœ… | πŸ”§ | | | [consistent-destructuring](docs/rules/consistent-destructuring.md) | Use destructured variables over properties. | | πŸ”§ | πŸ’‘ | | [consistent-empty-array-spread](docs/rules/consistent-empty-array-spread.md) | Prefer consistent types when spreading a ternary in an array literal. | βœ… | πŸ”§ | | +| [consistent-existence-index-check](docs/rules/consistent-existence-index-check.md) | Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()`. | βœ… | πŸ”§ | | | [consistent-function-scoping](docs/rules/consistent-function-scoping.md) | Move function definitions to the highest possible scope. | βœ… | | | | [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | πŸ”§ | | | [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | βœ… | πŸ”§ | | diff --git a/rules/ast/index.js b/rules/ast/index.js index a75e53f0cc..523910ebe8 100644 --- a/rules/ast/index.js +++ b/rules/ast/index.js @@ -31,6 +31,7 @@ module.exports = { isFunction: require('./is-function.js'), isMemberExpression: require('./is-member-expression.js'), isMethodCall: require('./is-method-call.js'), + isNegativeOne: require('./is-negative-one.js'), isNewExpression, isReferenceIdentifier: require('./is-reference-identifier.js'), isStaticRequire: require('./is-static-require.js'), diff --git a/rules/ast/is-negative-one.js b/rules/ast/is-negative-one.js new file mode 100644 index 0000000000..8d598286a4 --- /dev/null +++ b/rules/ast/is-negative-one.js @@ -0,0 +1,12 @@ +'use strict'; + +const {isNumberLiteral} = require('./literal.js'); + +function isNegativeOne(node) { + return node?.type === 'UnaryExpression' + && node.operator === '-' + && isNumberLiteral(node.argument) + && node.argument.value === 1; +} + +module.exports = isNegativeOne; diff --git a/rules/consistent-existence-index-check.js b/rules/consistent-existence-index-check.js new file mode 100644 index 0000000000..d844245a39 --- /dev/null +++ b/rules/consistent-existence-index-check.js @@ -0,0 +1,133 @@ +'use strict'; +const toLocation = require('./utils/to-location.js'); +const {isMethodCall, isNegativeOne, isNumberLiteral} = require('./ast/index.js'); + +const MESSAGE_ID = 'consistent-existence-index-check'; +const messages = { + [MESSAGE_ID]: 'Prefer `{{replacementOperator}} {{replacementValue}}` over `{{originalOperator}} {{originalValue}}` to check {{existenceOrNonExistence}}.', +}; + +const isZero = node => isNumberLiteral(node) && node.value === 0; + +/** +@param {parent: import('estree').BinaryExpression} binaryExpression +@returns {{ + replacementOperator: string, + replacementValue: string, + originalOperator: string, + originalValue: string, +} | undefined} +*/ +function getReplacement(binaryExpression) { + const {operator, right} = binaryExpression; + + if (operator === '<' && isZero(right)) { + return { + replacementOperator: '===', + replacementValue: '-1', + originalOperator: operator, + originalValue: '0', + }; + } + + if (operator === '>' && isNegativeOne(right)) { + return { + replacementOperator: '!==', + replacementValue: '-1', + originalOperator: operator, + originalValue: '-1', + }; + } + + if (operator === '>=' && isZero(right)) { + return { + replacementOperator: '!==', + replacementValue: '-1', + originalOperator: operator, + originalValue: '0', + }; + } +} + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + /** @param {import('estree').VariableDeclarator} variableDeclarator */ + * VariableDeclarator(variableDeclarator) { + if (!( + variableDeclarator.parent.type === 'VariableDeclaration' + && variableDeclarator.parent.kind === 'const' + && variableDeclarator.id.type === 'Identifier' + && isMethodCall(variableDeclarator.init, {methods: ['indexOf', 'lastIndexOf', 'findIndex', 'findLastIndex']}) + )) { + return; + } + + const variableIdentifier = variableDeclarator.id; + const variables = context.sourceCode.getDeclaredVariables(variableDeclarator); + const [variable] = variables; + + // Just for safety + if ( + variables.length !== 1 + || variable.identifiers.length !== 1 + || variable.identifiers[0] !== variableIdentifier + ) { + return; + } + + for (const {identifier} of variable.references) { + /** @type {{parent: import('estree').BinaryExpression}} */ + const binaryExpression = identifier.parent; + + if (binaryExpression.type !== 'BinaryExpression' || binaryExpression.left !== identifier) { + continue; + } + + const replacement = getReplacement(binaryExpression); + + if (!replacement) { + return; + } + + const {left, operator, right} = binaryExpression; + const {sourceCode} = context; + + const operatorToken = sourceCode.getTokenAfter( + left, + token => token.type === 'Punctuator' && token.value === operator, + ); + + yield { + node: binaryExpression, + loc: toLocation([operatorToken.range[0], right.range[1]], sourceCode), + messageId: MESSAGE_ID, + data: { + ...replacement, + existenceOrNonExistence: `${replacement.replacementOperator === '===' ? 'non-' : ''}existence`, + }, + * fix(fixer) { + yield fixer.replaceText(operatorToken, replacement.replacementOperator); + + if (replacement.replacementValue !== replacement.originalValue) { + yield fixer.replaceText(right, replacement.replacementValue); + } + }, + }; + } + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'problem', + docs: { + description: + 'Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()`.', + recommended: true, + }, + fixable: 'code', + messages, + }, +}; diff --git a/rules/prefer-includes.js b/rules/prefer-includes.js index e9e878beb0..01095c74f2 100644 --- a/rules/prefer-includes.js +++ b/rules/prefer-includes.js @@ -1,7 +1,7 @@ 'use strict'; const isMethodNamed = require('./utils/is-method-named.js'); const simpleArraySearchRule = require('./shared/simple-array-search-rule.js'); -const {isLiteral} = require('./ast/index.js'); +const {isLiteral, isNegativeOne} = require('./ast/index.js'); const MESSAGE_ID = 'prefer-includes'; const messages = { @@ -10,7 +10,6 @@ const messages = { // Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}` const ignoredVariables = new Set(['_', 'lodash', 'underscore']); const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name); -const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1; const isLiteralZero = node => isLiteral(node, 0); const isNegativeResult = node => ['===', '==', '<'].includes(node.operator); diff --git a/rules/utils/resolve-variable-name.js b/rules/utils/resolve-variable-name.js index f5b50d126a..4d2c34cc16 100644 --- a/rules/utils/resolve-variable-name.js +++ b/rules/utils/resolve-variable-name.js @@ -4,8 +4,8 @@ Finds a variable named `name` in the scope `scope` (or it's parents). @param {string} name - The variable name to be resolve. -@param {Scope} scope - The scope to look for the variable in. -@returns {Variable?} - The found variable, if any. +@param {import('eslint').Scope.Scope} scope - The scope to look for the variable in. +@returns {import('eslint').Scope.Variable | void} - The found variable, if any. */ module.exports = function resolveVariableName(name, scope) { while (scope) { diff --git a/test/consistent-existence-index-check.mjs b/test/consistent-existence-index-check.mjs new file mode 100644 index 0000000000..af75c9b14e --- /dev/null +++ b/test/consistent-existence-index-check.mjs @@ -0,0 +1,146 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + // Skip checking if indexOf() method is not a method call from a object + outdent` + const index = indexOf('bar'); + + if (index > -1) {} + `, + outdent` + const index = foo.indexOf('bar'); + + if (index === -1) {} + `, + outdent` + const index = foo.indexOf('bar'); + + if (-1 === index) {} + `, + outdent` + const index = foo.indexOf('bar'); + + if (index !== -1) {} + `, + outdent` + const index = foo.indexOf('bar'); + + if (-1 !== index) {} + `, + // Variable index is not from indexOf + outdent` + const index = 0; + + if (index < 0) {} + `, + // If index is not declared via VariableDeclarator, it will not be check here. + outdent` + function foo (index) { + if (index < 0) {} + } + `, + // Since the variable is references from function parameter, it will not be checked here + outdent` + const index = foo.indexOf('bar'); + + function foo (index) { + if (index < 0) {} + } + `, + // To prevent false positives, it will not check if the index is not declared via const + outdent` + let index = foo.indexOf("bar"); + + index < 0 + `, + // To prevent false positives, it will not check if the index is not declared via const + outdent` + var index = foo.indexOf("bar"); + + index < 0 + `, + // To prevent false positives, it will not check if the index is not declared via const + outdent` + let index; + + // do stuff + + index = arr.findLastIndex(element => element > 10); + + index < 0; + `, + 'const indexOf = "indexOf"; const index = foo[indexOf](foo); index < 0;', + 'const index = foo.indexOf?.(foo); index < 0;', + 'const index = foo?.indexOf(foo); index < 0;', + ], + invalid: [ + ...[ + 'index < 0', + 'index >= 0', + 'index > -1', + ].map(code => `const index = foo.indexOf(bar); ${code}`), + ...[ + 'foo.indexOf(bar)', + 'foo.lastIndexOf(bar)', + 'foo.findIndex(bar)', + 'foo.findLastIndex(bar)', + ].map(code => `const index = ${code}; index < 0`), + // It will search the scope chain for 'index' and find the 'index' variable declared above. + outdent` + const index = foo.indexOf(bar); + + function foo () { + if (index < 0) {} + } + `, + outdent` + const index1 = foo.indexOf("1"), + index2 = foo.indexOf("2"); + index1 < 0; + index2 >= 0; + `, + outdent` + const index = foo.indexOf('1'); + (( + /* comment 1 */ + (( + /* comment 2 */ + index + /* comment 3 */ + )) + /* comment 4 */ + < + /* comment 5 */ + (( + /* comment 6 */ + 0 + /* comment 7 */ + )) + /* comment 8 */ + )); + `, + outdent` + const index = foo.indexOf('1'); + (( + /* comment 1 */ + (( + /* comment 2 */ + index + /* comment 3 */ + )) + /* comment 4 */ + > + (( + /* comment 5 */ + - /* comment 6 */ (( /* comment 7 */ 1 /* comment 8 */ )) + /* comment 9 */ + )) + )); + `, + 'const index = _.indexOf([1, 2, 1, 2], 2); index < 0;', + ], +}); diff --git a/test/package.mjs b/test/package.mjs index 7434537ca7..7e68fe5b62 100644 --- a/test/package.mjs +++ b/test/package.mjs @@ -31,6 +31,7 @@ const RULES_WITHOUT_PASS_FAIL_SECTIONS = new Set([ // Intended to not use `pass`/`fail` section in this rule. 'prefer-modern-math-apis', 'prefer-math-min-max', + 'consistent-existence-index-check', ]); test('Every rule is defined in index file in alphabetical order', t => { diff --git a/test/snapshots/consistent-existence-index-check.mjs.md b/test/snapshots/consistent-existence-index-check.mjs.md new file mode 100644 index 0000000000..86b92821fc --- /dev/null +++ b/test/snapshots/consistent-existence-index-check.mjs.md @@ -0,0 +1,391 @@ +# Snapshot report for `test/consistent-existence-index-check.mjs` + +The actual snapshot is saved in `consistent-existence-index-check.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): const index = foo.indexOf(bar); index < 0 + +> Input + + `␊ + 1 | const index = foo.indexOf(bar); index < 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf(bar); index === -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.indexOf(bar); index < 0␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` + +## invalid(2): const index = foo.indexOf(bar); index >= 0 + +> Input + + `␊ + 1 | const index = foo.indexOf(bar); index >= 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf(bar); index !== -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.indexOf(bar); index >= 0␊ + | ^^^^ Prefer \`!== -1\` over \`>= 0\` to check existence.␊ + ` + +## invalid(3): const index = foo.indexOf(bar); index > -1 + +> Input + + `␊ + 1 | const index = foo.indexOf(bar); index > -1␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf(bar); index !== -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.indexOf(bar); index > -1␊ + | ^^^^ Prefer \`!== -1\` over \`> -1\` to check existence.␊ + ` + +## invalid(4): const index = foo.indexOf(bar); index < 0 + +> Input + + `␊ + 1 | const index = foo.indexOf(bar); index < 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf(bar); index === -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.indexOf(bar); index < 0␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` + +## invalid(5): const index = foo.lastIndexOf(bar); index < 0 + +> Input + + `␊ + 1 | const index = foo.lastIndexOf(bar); index < 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.lastIndexOf(bar); index === -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.lastIndexOf(bar); index < 0␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` + +## invalid(6): const index = foo.findIndex(bar); index < 0 + +> Input + + `␊ + 1 | const index = foo.findIndex(bar); index < 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.findIndex(bar); index === -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.findIndex(bar); index < 0␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` + +## invalid(7): const index = foo.findLastIndex(bar); index < 0 + +> Input + + `␊ + 1 | const index = foo.findLastIndex(bar); index < 0␊ + ` + +> Output + + `␊ + 1 | const index = foo.findLastIndex(bar); index === -1␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = foo.findLastIndex(bar); index < 0␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` + +## invalid(8): const index = foo.indexOf(bar); function foo () { if (index < 0) {} } + +> Input + + `␊ + 1 | const index = foo.indexOf(bar);␊ + 2 |␊ + 3 | function foo () {␊ + 4 | if (index < 0) {}␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf(bar);␊ + 2 |␊ + 3 | function foo () {␊ + 4 | if (index === -1) {}␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | const index = foo.indexOf(bar);␊ + 2 |␊ + 3 | function foo () {␊ + > 4 | if (index < 0) {}␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + 5 | }␊ + ` + +## invalid(9): const index1 = foo.indexOf("1"), index2 = foo.indexOf("2"); index1 < 0; index2 >= 0; + +> Input + + `␊ + 1 | const index1 = foo.indexOf("1"),␊ + 2 | index2 = foo.indexOf("2");␊ + 3 | index1 < 0;␊ + 4 | index2 >= 0;␊ + ` + +> Output + + `␊ + 1 | const index1 = foo.indexOf("1"),␊ + 2 | index2 = foo.indexOf("2");␊ + 3 | index1 === -1;␊ + 4 | index2 !== -1;␊ + ` + +> Error 1/2 + + `␊ + 1 | const index1 = foo.indexOf("1"),␊ + 2 | index2 = foo.indexOf("2");␊ + > 3 | index1 < 0;␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + 4 | index2 >= 0;␊ + ` + +> Error 2/2 + + `␊ + 1 | const index1 = foo.indexOf("1"),␊ + 2 | index2 = foo.indexOf("2");␊ + 3 | index1 < 0;␊ + > 4 | index2 >= 0;␊ + | ^^^^ Prefer \`!== -1\` over \`>= 0\` to check existence.␊ + ` + +## invalid(10): const index = foo.indexOf('1'); (( /* comment 1 */ (( /* comment 2 */ index /* comment 3 */ )) /* comment 4 */ < /* comment 5 */ (( /* comment 6 */ 0 /* comment 7 */ )) /* comment 8 */ )); + +> Input + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + 10 | <␊ + 11 | /* comment 5 */␊ + 12 | ((␊ + 13 | /* comment 6 */␊ + 14 | 0␊ + 15 | /* comment 7 */␊ + 16 | ))␊ + 17 | /* comment 8 */␊ + 18 | ));␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + 10 | ===␊ + 11 | /* comment 5 */␊ + 12 | ((␊ + 13 | /* comment 6 */␊ + 14 | -1␊ + 15 | /* comment 7 */␊ + 16 | ))␊ + 17 | /* comment 8 */␊ + 18 | ));␊ + ` + +> Error 1/1 + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + > 10 | <␊ + | ^␊ + > 11 | /* comment 5 */␊ + | ^^^^^^^^^^^^^^^^␊ + > 12 | ((␊ + | ^^^^^^^^^^^^^^^^␊ + > 13 | /* comment 6 */␊ + | ^^^^^^^^^^^^^^^^␊ + > 14 | 0␊ + | ^^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + 15 | /* comment 7 */␊ + 16 | ))␊ + 17 | /* comment 8 */␊ + 18 | ));␊ + ` + +## invalid(11): const index = foo.indexOf('1'); (( /* comment 1 */ (( /* comment 2 */ index /* comment 3 */ )) /* comment 4 */ > (( /* comment 5 */ - /* comment 6 */ (( /* comment 7 */ 1 /* comment 8 */ )) /* comment 9 */ )) )); + +> Input + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + 10 | >␊ + 11 | ((␊ + 12 | /* comment 5 */␊ + 13 | - /* comment 6 */ (( /* comment 7 */ 1 /* comment 8 */ ))␊ + 14 | /* comment 9 */␊ + 15 | ))␊ + 16 | ));␊ + ` + +> Output + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + 10 | !==␊ + 11 | ((␊ + 12 | /* comment 5 */␊ + 13 | - /* comment 6 */ (( /* comment 7 */ 1 /* comment 8 */ ))␊ + 14 | /* comment 9 */␊ + 15 | ))␊ + 16 | ));␊ + ` + +> Error 1/1 + + `␊ + 1 | const index = foo.indexOf('1');␊ + 2 | ((␊ + 3 | /* comment 1 */␊ + 4 | ((␊ + 5 | /* comment 2 */␊ + 6 | index␊ + 7 | /* comment 3 */␊ + 8 | ))␊ + 9 | /* comment 4 */␊ + > 10 | >␊ + | ^␊ + > 11 | ((␊ + | ^^^␊ + > 12 | /* comment 5 */␊ + | ^^^␊ + > 13 | - /* comment 6 */ (( /* comment 7 */ 1 /* comment 8 */ ))␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer \`!== -1\` over \`> -1\` to check existence.␊ + 14 | /* comment 9 */␊ + 15 | ))␊ + 16 | ));␊ + ` + +## invalid(12): const index = _.indexOf([1, 2, 1, 2], 2); index < 0; + +> Input + + `␊ + 1 | const index = _.indexOf([1, 2, 1, 2], 2); index < 0;␊ + ` + +> Output + + `␊ + 1 | const index = _.indexOf([1, 2, 1, 2], 2); index === -1;␊ + ` + +> Error 1/1 + + `␊ + > 1 | const index = _.indexOf([1, 2, 1, 2], 2); index < 0;␊ + | ^^^ Prefer \`=== -1\` over \`< 0\` to check non-existence.␊ + ` diff --git a/test/snapshots/consistent-existence-index-check.mjs.snap b/test/snapshots/consistent-existence-index-check.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..7275469ad09802b4622ce1dcc90abe5e68c5d557 GIT binary patch literal 1203 zcmV;k1WfxuRzV6f z6Yiu?xF3rM00000000B+nZIw_Mij@T7DXU!{SThKL=5~xNBwjp(s9}ZNzfn-&>==p zRkB3FjAhc0XxD({rOB8r88T!@oFNDbv?#iCDNtm~pV96avS%oczr=UEV@R0>EFc^N z>fO`hyYJ`m9eH}GZw;;f&iL_(W*hcUJFFSzL2qabN<_L0eajr%V3-5#5ERfYYsERg zqnEaNBdO($w4rQ_&EDR4+p@dY-MhE`W$r?b9{I0wU*|~?fWRbqz@j$nZD~X8 zrn$Fodm*o)kkwQn6-9w%;ziZ(?Az0@ufk!!c$zD!DN_Xim_V|l)9JwXBTd&v&~4X#RZT(C?s#x)i8{Ntigv%=+^r@a6gnoq07r8Vr0?VCx7eS_q!3FVCm%A9w|8$<3_MDDqmTwKa&=$!$< zhEK50C8!(bz{&j6{5Fu#6`|0=kUVORfx8j`sYE3+^xkKYXx`0@;aV;KI97`}*SC{8==9hf=GyFSZif;CUyH~Y3>nREzB z5*&e{Ln)Yz1V_hk93^Zc5PGu3s|X;&qzF)fN%X=+fGSL&5V?L4pav5-<`u-xC=cUo z@^G1OQo;4}H>#iCa+xHfibbJiRycJP;Tc7%sq0tM8m=b7OlFZRN=xpL%nr$m{vASS z{w?2)K7O~R#r4P&@)$w2qB?S~?FtD6aSEQup+r2^;dFRa_6thLKBI(OXiQEEo{?8@ zZ&AVh$tk#5^HMoNg}U0zr6v$Ns5;r3**|Dze`GkbXbGs%tk}t(^7T<(mHoTvU9dM( zj&05t$wjFJr4p=Og1)u8tC=6%)RHDNQ~Mbz%*sgCg-(3vK6qu5F%dPeaSm|0W1(*|7^+OP@*Mg}@icLto`^lO9l zt9X7*Iv`2(bd9-O)pa3dYH!ms!g$m?4>?GPRM-pEJs&6O0(~YRk)#`*Pd!M6G?+^@ zJr|m{1*`0ld40T7%cQgM13IE^x3a^H$-F>L&P@4dNOTb|bP)j){$P6l1TM2JJQ