From ee3a2e525e0a5cf1ade9cecb800cafb1e3c9a810 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 10 Feb 2021 01:35:33 +0800 Subject: [PATCH] `prefer-includes`: Add `Array#some()` check (#1097) Co-authored-by: Fabrice TIERCELIN --- docs/rules/prefer-includes.md | 85 +++++- readme.md | 2 +- rules/prefer-array-index-of.js | 117 +-------- rules/prefer-includes.js | 14 +- rules/shared/simple-array-search-rule.js | 129 +++++++++ test/prefer-array-index-of.js | 147 +---------- test/prefer-includes.js | 9 + test/shared/simple-array-search-rule-tests.js | 158 +++++++++++ test/snapshots/prefer-includes.js.md | 247 ++++++++++++++++++ test/snapshots/prefer-includes.js.snap | Bin 656 -> 1586 bytes 10 files changed, 650 insertions(+), 258 deletions(-) create mode 100644 rules/shared/simple-array-search-rule.js create mode 100644 test/shared/simple-array-search-rule-tests.js diff --git a/docs/rules/prefer-includes.md b/docs/rules/prefer-includes.md index 312768406e..4b14312e94 100644 --- a/docs/rules/prefer-includes.md +++ b/docs/rules/prefer-includes.md @@ -1,29 +1,108 @@ -# Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence +# Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence -All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()` +All built-ins have `.includes()` in addition to `.indexOf()`. Prefer `.includes()` over comparing the value of `.indexOf()`. -This rule is fixable. +[`Array#some()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) is intended for more complex needs. If you are just looking for the index where the given item is present, the code can be simplified to use [`Array#includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This applies to any search with a literal, a variable, or any expression that doesn't have any explicit side effects. However, if the expression you are looking for relies on an item related to the function (its arguments, the function self, etc.), the case is still valid. +This rule is fixable, unless the search expression in `Array#some()` has side effects. ## Fail ```js [].indexOf('foo') !== -1; +``` + +```js x.indexOf('foo') != -1; +``` + +```js str.indexOf('foo') > -1; +``` + +```js 'foobar'.indexOf('foo') >= 0; +``` + +```js x.indexOf('foo') === -1 ``` +```js +const isFound = foo.some(x => x === 'foo'); +``` + +```js +const isFound = foo.some(x => 'foo' === x); +``` + +```js +const isFound = foo.some(x => { + return x === 'foo'; +}); +``` ## Pass ```js const str = 'foobar'; +``` + +```js str.indexOf('foo') !== -n; +``` + +```js str.indexOf('foo') !== 1; +``` + +```js !str.indexOf('foo') === 1; +``` + +```js !str.indexOf('foo') === -n; +``` + +```js str.includes('foo'); +``` + +```js [1,2,3].includes(4); ``` + +```js +const isFound = foo.includes('foo'); +``` + +```js +const isFound = foo.some(x => x == undefined); +``` + +```js +const isFound = foo.some(x => x !== 'foo'); +``` + +```js +const isFound = foo.some((x, index) => x === index); +``` + +```js +const isFound = foo.some(x => (x === 'foo') && isValid()); +``` + +```js +const isFound = foo.some(x => y === 'foo'); +``` + +```js +const isFound = foo.some(x => y.x === 'foo'); +``` + +```js +const isFound = foo.some(x => { + const bar = getBar(); + return x === bar; +}); +``` diff --git a/readme.md b/readme.md index 8d92f1d288..0dcfd0fcc0 100644 --- a/readme.md +++ b/readme.md @@ -162,7 +162,7 @@ Configure it in `package.json`. - [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)* - [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) - Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. *(fixable)* - [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) - Prefer `.textContent` over `.innerText`. *(fixable)* -- [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence. *(fixable)* +- [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. *(partly fixable)* - [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) - Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. *(partly fixable)* - [prefer-math-trunc](docs/rules/prefer-math-trunc.md) - Enforce the use of `Math.trunc` instead of bitwise operators. *(partly fixable)* - [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) - Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. *(fixable)* diff --git a/rules/prefer-array-index-of.js b/rules/prefer-array-index-of.js index e7b2fcdd33..4941cd747d 100644 --- a/rules/prefer-array-index-of.js +++ b/rules/prefer-array-index-of.js @@ -1,118 +1,13 @@ 'use strict'; -const {hasSideEffect, isParenthesized, findVariable} = require('eslint-utils'); const getDocumentationUrl = require('./utils/get-documentation-url'); -const methodSelector = require('./utils/method-selector'); -const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside'); +const simpleArraySearchRule = require('./shared/simple-array-search-rule'); -const MESSAGE_ID_FIND_INDEX = 'findIndex'; -const MESSAGE_ID_REPLACE = 'replaceFindIndex'; -const messages = { - [MESSAGE_ID_FIND_INDEX]: 'Use `.indexOf()` instead of `.findIndex()` when looking for the index of an item.', - [MESSAGE_ID_REPLACE]: 'Replace `.findIndex()` with `.indexOf()`.' -}; - -const getBinaryExpressionSelector = path => [ - `[${path}.type="BinaryExpression"]`, - `[${path}.operator="==="]`, - `:matches([${path}.left.type="Identifier"], [${path}.right.type="Identifier"])` -].join(''); -const getFunctionSelector = path => [ - `[${path}.generator=false]`, - `[${path}.async=false]`, - `[${path}.params.length=1]`, - `[${path}.params.0.type="Identifier"]` -].join(''); -const selector = [ - methodSelector({ - name: 'findIndex', - length: 1 - }), - `:matches(${ - [ - // Matches `foo.findIndex(bar => bar === baz)` - [ - '[arguments.0.type="ArrowFunctionExpression"]', - getFunctionSelector('arguments.0'), - getBinaryExpressionSelector('arguments.0.body') - ].join(''), - // Matches `foo.findIndex(bar => {return bar === baz})` - // Matches `foo.findIndex(function (bar) {return bar === baz})` - [ - ':matches([arguments.0.type="ArrowFunctionExpression"], [arguments.0.type="FunctionExpression"])', - getFunctionSelector('arguments.0'), - '[arguments.0.body.type="BlockStatement"]', - '[arguments.0.body.body.length=1]', - '[arguments.0.body.body.0.type="ReturnStatement"]', - getBinaryExpressionSelector('arguments.0.body.body.0.argument') - ].join('') - ].join(', ') - })` -].join(''); - -const isIdentifierNamed = ({type, name}, expectName) => type === 'Identifier' && name === expectName; - -const create = context => { - const sourceCode = context.getSourceCode(); - const {scopeManager} = sourceCode; - - return { - [selector](node) { - const [callback] = node.arguments; - const binaryExpression = callback.body.type === 'BinaryExpression' ? - callback.body : - callback.body.body[0].argument; - const [parameter] = callback.params; - const {left, right} = binaryExpression; - const {name} = parameter; - - let searchValueNode; - let parameterInBinaryExpression; - if (isIdentifierNamed(left, name)) { - searchValueNode = right; - parameterInBinaryExpression = left; - } else if (isIdentifierNamed(right, name)) { - searchValueNode = left; - parameterInBinaryExpression = right; - } else { - return; - } +const {messages, createListeners} = simpleArraySearchRule({ + method: 'findIndex', + replacement: 'indexOf' +}); - const callbackScope = scopeManager.acquire(callback); - if ( - // `parameter` is used somewhere else - findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression) || - isFunctionSelfUsedInside(callback, callbackScope) - ) { - return; - } - - const method = node.callee.property; - const problem = { - node: method, - messageId: MESSAGE_ID_FIND_INDEX, - suggest: [] - }; - - const fix = function * (fixer) { - let text = sourceCode.getText(searchValueNode); - if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) { - text = `(${text})`; - } - - yield fixer.replaceText(method, 'indexOf'); - yield fixer.replaceText(callback, text); - }; - - if (hasSideEffect(searchValueNode, sourceCode)) { - problem.suggest.push({messageId: MESSAGE_ID_REPLACE, fix}); - } else { - problem.fix = fix; - } - - context.report(problem); - } - }; -}; +const create = context => createListeners(context); module.exports = { create, diff --git a/rules/prefer-includes.js b/rules/prefer-includes.js index c023d6b92d..256157a063 100644 --- a/rules/prefer-includes.js +++ b/rules/prefer-includes.js @@ -2,6 +2,7 @@ const getDocumentationUrl = require('./utils/get-documentation-url'); const isMethodNamed = require('./utils/is-method-named'); const isLiteralValue = require('./utils/is-literal-value'); +const simpleArraySearchRule = require('./shared/simple-array-search-rule'); const MESSAGE_ID = 'prefer-includes'; const messages = { @@ -37,6 +38,11 @@ const report = (context, node, target, argumentsNodes) => { }); }; +const includesOverSomeRule = simpleArraySearchRule({ + method: 'some', + replacement: 'includes' +}); + const create = context => ({ BinaryExpression: node => { const {left, right, operator} = node; @@ -69,7 +75,8 @@ const create = context => ({ argumentsNodes ); } - } + }, + ...includesOverSomeRule.createListeners(context) }); module.exports = { @@ -80,6 +87,9 @@ module.exports = { url: getDocumentationUrl(__filename) }, fixable: 'code', - messages + messages: { + ...messages, + ...includesOverSomeRule.messages + } } }; diff --git a/rules/shared/simple-array-search-rule.js b/rules/shared/simple-array-search-rule.js new file mode 100644 index 0000000000..a7cb6d6151 --- /dev/null +++ b/rules/shared/simple-array-search-rule.js @@ -0,0 +1,129 @@ +'use strict'; + +const {hasSideEffect, isParenthesized, findVariable} = require('eslint-utils'); +const methodSelector = require('../utils/method-selector'); +const isFunctionSelfUsedInside = require('../utils/is-function-self-used-inside'); + +const getBinaryExpressionSelector = path => [ + `[${path}.type="BinaryExpression"]`, + `[${path}.operator="==="]`, + `:matches([${path}.left.type="Identifier"], [${path}.right.type="Identifier"])` +].join(''); +const getFunctionSelector = path => [ + `[${path}.generator=false]`, + `[${path}.async=false]`, + `[${path}.params.length=1]`, + `[${path}.params.0.type="Identifier"]` +].join(''); +const callbackFunctionSelector = path => `:matches(${ + [ + // Matches `foo.findIndex(bar => bar === baz)` + [ + `[${path}.type="ArrowFunctionExpression"]`, + getFunctionSelector(path), + getBinaryExpressionSelector(`${path}.body`) + ].join(''), + // Matches `foo.findIndex(bar => {return bar === baz})` + // Matches `foo.findIndex(function (bar) {return bar === baz})` + [ + `:matches([${path}.type="ArrowFunctionExpression"], [${path}.type="FunctionExpression"])`, + getFunctionSelector(path), + `[${path}.body.type="BlockStatement"]`, + `[${path}.body.body.length=1]`, + `[${path}.body.body.0.type="ReturnStatement"]`, + getBinaryExpressionSelector(`${path}.body.body.0.argument`) + ].join('') + ].join(', ') +})`; +const isIdentifierNamed = ({type, name}, expectName) => type === 'Identifier' && name === expectName; + +function simpleArraySearchRule({method, replacement}) { + // Add prefix to avoid conflicts in `prefer-includes` rule + const MESSAGE_ID_PREFIX = `prefer-${replacement}-over-${method}/`; + const ERROR = `${MESSAGE_ID_PREFIX}/error`; + const SUGGESTION = `${MESSAGE_ID_PREFIX}/suggestion`; + const ERROR_MESSAGES = { + findIndex: 'Use `.indexOf()` instead of `.findIndex()` when looking for the index of an item.', + some: `Use \`.${replacement}()\` instead of \`.${method}()\` when checking value existence.` + }; + + const messages = { + [ERROR]: ERROR_MESSAGES[method], + [SUGGESTION]: `Replace \`.${method}()\` with \`.${replacement}()\`.` + }; + + const selector = [ + methodSelector({ + name: method, + length: 1 + }), + callbackFunctionSelector('arguments.0') + ].join(''); + + function createListeners(context) { + const sourceCode = context.getSourceCode(); + const {scopeManager} = sourceCode; + + return { + [selector](node) { + const [callback] = node.arguments; + const binaryExpression = callback.body.type === 'BinaryExpression' ? + callback.body : + callback.body.body[0].argument; + const [parameter] = callback.params; + const {left, right} = binaryExpression; + const {name} = parameter; + + let searchValueNode; + let parameterInBinaryExpression; + if (isIdentifierNamed(left, name)) { + searchValueNode = right; + parameterInBinaryExpression = left; + } else if (isIdentifierNamed(right, name)) { + searchValueNode = left; + parameterInBinaryExpression = right; + } else { + return; + } + + const callbackScope = scopeManager.acquire(callback); + if ( + // `parameter` is used somewhere else + findVariable(callbackScope, parameter).references.some(({identifier}) => identifier !== parameterInBinaryExpression) || + isFunctionSelfUsedInside(callback, callbackScope) + ) { + return; + } + + const method = node.callee.property; + const problem = { + node: method, + messageId: ERROR, + suggest: [] + }; + + const fix = function * (fixer) { + let text = sourceCode.getText(searchValueNode); + if (isParenthesized(searchValueNode, sourceCode) && !isParenthesized(callback, sourceCode)) { + text = `(${text})`; + } + + yield fixer.replaceText(method, replacement); + yield fixer.replaceText(callback, text); + }; + + if (hasSideEffect(searchValueNode, sourceCode)) { + problem.suggest.push({messageId: SUGGESTION, fix}); + } else { + problem.fix = fix; + } + + context.report(problem); + } + }; + } + + return {messages, createListeners}; +} + +module.exports = simpleArraySearchRule; diff --git a/test/prefer-array-index-of.js b/test/prefer-array-index-of.js index ce31919070..04a6f9a410 100644 --- a/test/prefer-array-index-of.js +++ b/test/prefer-array-index-of.js @@ -1,145 +1,10 @@ -import {outdent} from 'outdent'; import {test} from './utils/test.js'; +import tests from './shared/simple-array-search-rule-tests.js'; -test.snapshot({ - valid: [ - 'const findIndex = foo.findIndex', - - // Test `foo.findIndex` - // More/less argument(s) - 'foo.findIndex()', - 'foo.findIndex(function (x) {return x === 1;}, bar)', - 'foo.findIndex(...[function (x) {return x === 1;}])', - // Not `CallExpression` - 'new foo.findIndex(x => x === 1)', - // Not `MemberExpression` - 'findIndex(x => x === 1)', - // `callee.property` is not a `Identifier` - 'foo["findIndex"](x => x === 1)', - // Computed - 'foo[findIndex](x => x === 1)', - // Not `findIndex` - 'foo.notListedMethod(x => x === 1)', - - // Test `callback` part - // Not function - 'foo.findIndex(myFunction)', - // Not one parameter - 'foo.findIndex((x, i) => x === i)', - 'foo.findIndex((x, i) => {return x === i})', - 'foo.findIndex(function(x, i) {return x === i})', - // Parameter is not `Identifier` - 'foo.findIndex(({x}) => x === 1)', - 'foo.findIndex(({x}) => {return x === 1})', - 'foo.findIndex(function({x}) {return x === 1})', - // `generator` - 'foo.findIndex(function * (x) {return x === 1})', - // `async` - 'foo.findIndex(async (x) => x === 1)', - 'foo.findIndex(async (x) => {return x === 1})', - 'foo.findIndex(async function(x) {return x === 1})', - - // Test `callback` body - // Not only `return` - 'foo.findIndex(({x}) => {noop();return x === 1})', - // Not `return` - 'foo.findIndex(({x}) => {bar(x === 1)})', - - // Test `BinaryExpression` - // Not `BinaryExpression` - 'foo.findIndex(x => x - 1)', - 'foo.findIndex(x => {return x - 1})', - 'foo.findIndex(function (x){return x - 1})', - // Not `===` - 'foo.findIndex(x => x !== 1)', - 'foo.findIndex(x => {return x !== 1})', - 'foo.findIndex(function (x){return x !== 1})', - // Neither `left` nor `right` is Identifier - 'foo.findIndex(x => 1 === 1.0)', - 'foo.findIndex(x => {return 1 === 1.0})', - 'foo.findIndex(function (x){return 1 === 1.0})', - // Both `left` and `right` are same as `parameter` - 'foo.findIndex(x => x === x)', - - // Not the same identifier - 'foo.findIndex(x => y === 1)', - - // Dynamical value - 'foo.findIndex(x => x + "foo" === "foo" + x)', - - // Parameter is used - 'foo.findIndex(x => x === "foo" + x)', - // Parameter is used in a deeper scope - 'foo.findIndex(x => x === (function (){return x === "1"})())', - // FunctionName is used - 'foo.findIndex(function fn(x) {return x === fn(y)})', - // `arguments` is used - 'foo.findIndex(function(x) {return x === arguments.length})', - // `this` is used - 'foo.findIndex(function(x) {return x === this.length})', - - // Already valid case - 'foo.indexOf(0)' - ], - - invalid: [ - 'values.findIndex(x => x === "foo")', - 'values.findIndex(x => "foo" === x)', - 'values.findIndex(x => {return x === "foo";})', - 'values.findIndex(function (x) {return x === "foo";})', - outdent` - // 1 - (0, values) - // 2 - ./* 3 */findIndex /* 3 */ ( - /* 4 */ - x /* 5 */ => /* 6 */ x /* 7 */ === /* 8 */ "foo" /* 9 */ - ) /* 10 */ - `, - outdent` - foo.findIndex(function (element) { - return element === bar.findIndex(x => x === 1); - }); - `, - 'values.findIndex(x => x === (0, "foo"))', - 'values.findIndex((x => x === (0, "foo")))', - // `this`/`arguments` in arrow functions - outdent` - function fn() { - foo.findIndex(x => x === arguments.length) - } - `, - outdent` - function fn() { - foo.findIndex(x => x === this[1]) - } - `, - 'values.findIndex(x => x === foo())', - outdent` - foo.findIndex(function a(x) { - return x === (function (a) { - return a(this) === arguments[1] - }).call(thisObject, anotherFunctionNamedA, secondArgument) - }) - ` - ] +const {snapshot, typescript} = tests({ + method: 'findIndex', + replacement: 'indexOf' }); -test.typescript({ - valid: [], - invalid: [ - { - code: outdent` - function foo() { - return (bar as string).findIndex(x => x === "foo"); - } - `, - output: outdent` - function foo() { - return (bar as string).indexOf("foo"); - } - `, - errors: 1 - } - ] -}); +test.snapshot(snapshot); +test.typescript(typescript); diff --git a/test/prefer-includes.js b/test/prefer-includes.js index d6c77b5e0d..56b173aad9 100644 --- a/test/prefer-includes.js +++ b/test/prefer-includes.js @@ -1,4 +1,5 @@ import {test} from './utils/test.js'; +import tests from './shared/simple-array-search-rule-tests.js'; test.snapshot({ valid: [ @@ -31,3 +32,11 @@ test.snapshot({ 'foo.indexOf(bar, 1) !== -1' ] }); + +const {snapshot, typescript} = tests({ + method: 'some', + replacement: 'includes' +}); + +test.snapshot(snapshot); +test.typescript(typescript); diff --git a/test/shared/simple-array-search-rule-tests.js b/test/shared/simple-array-search-rule-tests.js new file mode 100644 index 0000000000..b33ff9a15f --- /dev/null +++ b/test/shared/simple-array-search-rule-tests.js @@ -0,0 +1,158 @@ +'use strict'; +const {outdent} = require('outdent'); + +function snapshotTests({method, replacement}) { + return { + valid: [ + `const ${method} = foo.${method}`, + + // Test `foo.findIndex` + // More/less argument(s) + `foo.${method}()`, + `foo.${method}(function (x) {return x === 1;}, bar)`, + `foo.${method}(...[function (x) {return x === 1;}])`, + // Not `CallExpression` + `new foo.${method}(x => x === 1)`, + // Not `MemberExpression` + `${method}(x => x === 1)`, + // `callee.property` is not a `Identifier` + `foo["${method}"](x => x === 1)`, + // Computed + `foo[${method}](x => x === 1)`, + // Not `findIndex` + 'foo.notListedMethod(x => x === 1)', + + // Test `callback` part + // Not function + `foo.${method}(myFunction)`, + // Not one parameter + `foo.${method}((x, i) => x === i)`, + `foo.${method}((x, i) => {return x === i})`, + `foo.${method}(function(x, i) {return x === i})`, + // Parameter is not `Identifier` + `foo.${method}(({x}) => x === 1)`, + `foo.${method}(({x}) => {return x === 1})`, + `foo.${method}(function({x}) {return x === 1})`, + // `generator` + `foo.${method}(function * (x) {return x === 1})`, + // `async` + `foo.${method}(async (x) => x === 1)`, + `foo.${method}(async (x) => {return x === 1})`, + `foo.${method}(async function(x) {return x === 1})`, + + // Test `callback` body + // Not only `return` + `foo.${method}(({x}) => {noop();return x === 1})`, + // Not `return` + `foo.${method}(({x}) => {bar(x === 1)})`, + + // Test `BinaryExpression` + // Not `BinaryExpression` + `foo.${method}(x => x - 1)`, + `foo.${method}(x => {return x - 1})`, + `foo.${method}(function (x){return x - 1})`, + // Not `===` + `foo.${method}(x => x !== 1)`, + `foo.${method}(x => {return x !== 1})`, + `foo.${method}(function (x){return x !== 1})`, + // Neither `left` nor `right` is Identifier + `foo.${method}(x => 1 === 1.0)`, + `foo.${method}(x => {return 1 === 1.0})`, + `foo.${method}(function (x){return 1 === 1.0})`, + // Both `left` and `right` are same as `parameter` + `foo.${method}(x => x === x)`, + + // Not the same identifier + `foo.${method}(x => y === 1)`, + + // Dynamical value + `foo.${method}(x => x + "foo" === "foo" + x)`, + + // Parameter is used + `foo.${method}(x => x === "foo" + x)`, + // Parameter is used in a deeper scope + `foo.${method}(x => x === (function (){return x === "1"})())`, + // FunctionName is used + `foo.${method}(function fn(x) {return x === fn(y)})`, + // `arguments` is used + `foo.${method}(function(x) {return x === arguments.length})`, + // `this` is used + `foo.${method}(function(x) {return x === this.length})`, + + // Already valid case + `foo.${replacement}(0)` + ], + + invalid: [ + `values.${method}(x => x === "foo")`, + `values.${method}(x => "foo" === x)`, + `values.${method}(x => {return x === "foo";})`, + `values.${method}(function (x) {return x === "foo";})`, + outdent` + // 1 + (0, values) + // 2 + ./* 3 */${method} /* 3 */ ( + /* 4 */ + x /* 5 */ => /* 6 */ x /* 7 */ === /* 8 */ "foo" /* 9 */ + ) /* 10 */ + `, + outdent` + foo.${method}(function (element) { + return element === bar.${method}(x => x === 1); + }); + `, + `values.${method}(x => x === (0, "foo"))`, + `values.${method}((x => x === (0, "foo")))`, + // `this`/`arguments` in arrow functions + outdent` + function fn() { + foo.${method}(x => x === arguments.length) + } + `, + outdent` + function fn() { + foo.${method}(x => x === this[1]) + } + `, + `values.${method}(x => x === foo())`, + outdent` + foo.${method}(function a(x) { + return x === (function (a) { + return a(this) === arguments[1] + }).call(thisObject, anotherFunctionNamedA, secondArgument) + }) + ` + ] + }; +} + +function typescriptTests({method, replacement}) { + return { + valid: [], + invalid: [ + { + code: outdent` + function foo() { + return (bar as string).${method}(x => x === "foo"); + } + `, + output: outdent` + function foo() { + return (bar as string).${replacement}("foo"); + } + `, + errors: 1 + } + ] + }; +} + +function tests(options) { + return { + snapshot: snapshotTests(options), + typescript: typescriptTests(options) + }; +} + +module.exports = tests; diff --git a/test/snapshots/prefer-includes.js.md b/test/snapshots/prefer-includes.js.md index 4f7feab13a..365e357b7d 100644 --- a/test/snapshots/prefer-includes.js.md +++ b/test/snapshots/prefer-includes.js.md @@ -179,3 +179,250 @@ Generated by [AVA](https://avajs.dev). > 1 | foo.indexOf(bar, 1) !== -1␊ | ^^^^^^^ Use `.includes()`, rather than `.indexOf()`, when checking for existence.␊ ` + +## Invalid #1 + 1 | values.some(x => x === "foo") + +> Output + + `␊ + 1 | values.includes("foo")␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some(x => x === "foo")␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #2 + 1 | values.some(x => "foo" === x) + +> Output + + `␊ + 1 | values.includes("foo")␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some(x => "foo" === x)␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #3 + 1 | values.some(x => {return x === "foo";}) + +> Output + + `␊ + 1 | values.includes("foo")␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some(x => {return x === "foo";})␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #4 + 1 | values.some(function (x) {return x === "foo";}) + +> Output + + `␊ + 1 | values.includes("foo")␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some(function (x) {return x === "foo";})␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #5 + 1 | // 1 + 2 | (0, values) + 3 | // 2 + 4 | ./* 3 */some /* 3 */ ( + 5 | /* 4 */ + 6 | x /* 5 */ => /* 6 */ x /* 7 */ === /* 8 */ "foo" /* 9 */ + 7 | ) /* 10 */ + +> Output + + `␊ + 1 | // 1␊ + 2 | (0, values)␊ + 3 | // 2␊ + 4 | ./* 3 */includes /* 3 */ (␊ + 5 | /* 4 */␊ + 6 | "foo" /* 9 */␊ + 7 | ) /* 10 */␊ + ` + +> Error 1/1 + + `␊ + 1 | // 1␊ + 2 | (0, values)␊ + 3 | // 2␊ + > 4 | ./* 3 */some /* 3 */ (␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 5 | /* 4 */␊ + 6 | x /* 5 */ => /* 6 */ x /* 7 */ === /* 8 */ "foo" /* 9 */␊ + 7 | ) /* 10 */␊ + ` + +## Invalid #6 + 1 | foo.some(function (element) { + 2 | return element === bar.some(x => x === 1); + 3 | }); + +> Output + + `␊ + 1 | foo.some(function (element) {␊ + 2 | return element === bar.includes(1);␊ + 3 | });␊ + ` + +> Error 1/2 + + `␊ + > 1 | foo.some(function (element) {␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 2 | return element === bar.some(x => x === 1);␊ + 3 | });␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace `.some()` with `.includes()`.␊ + 1 | foo.includes(bar.some(x => x === 1));␊ + ` + +> Error 2/2 + + `␊ + 1 | foo.some(function (element) {␊ + > 2 | return element === bar.some(x => x === 1);␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 3 | });␊ + ` + +## Invalid #7 + 1 | values.some(x => x === (0, "foo")) + +> Output + + `␊ + 1 | values.includes((0, "foo"))␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some(x => x === (0, "foo"))␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #8 + 1 | values.some((x => x === (0, "foo"))) + +> Output + + `␊ + 1 | values.includes((0, "foo"))␊ + ` + +> Error 1/1 + + `␊ + > 1 | values.some((x => x === (0, "foo")))␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ` + +## Invalid #9 + 1 | function fn() { + 2 | foo.some(x => x === arguments.length) + 3 | } + +> Output + + `␊ + 1 | function fn() {␊ + 2 | foo.includes(arguments.length)␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function fn() {␊ + > 2 | foo.some(x => x === arguments.length)␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 3 | }␊ + ` + +## Invalid #10 + 1 | function fn() { + 2 | foo.some(x => x === this[1]) + 3 | } + +> Output + + `␊ + 1 | function fn() {␊ + 2 | foo.includes(this[1])␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function fn() {␊ + > 2 | foo.some(x => x === this[1])␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 3 | }␊ + ` + +## Invalid #11 + 1 | values.some(x => x === foo()) + +> Error 1/1 + + `␊ + > 1 | values.some(x => x === foo())␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace `.some()` with `.includes()`.␊ + 1 | values.includes(foo())␊ + ` + +## Invalid #12 + 1 | foo.some(function a(x) { + 2 | return x === (function (a) { + 3 | return a(this) === arguments[1] + 4 | }).call(thisObject, anotherFunctionNamedA, secondArgument) + 5 | }) + +> Error 1/1 + + `␊ + > 1 | foo.some(function a(x) {␊ + | ^^^^ Use `.includes()` instead of `.some()` when checking value existence.␊ + 2 | return x === (function (a) {␊ + 3 | return a(this) === arguments[1]␊ + 4 | }).call(thisObject, anotherFunctionNamedA, secondArgument)␊ + 5 | })␊ + ␊ + --------------------------------------------------------------------------------␊ + Suggestion 1/1: Replace `.some()` with `.includes()`.␊ + 1 | foo.includes((function (a) {␊ + 2 | return a(this) === arguments[1]␊ + 3 | }).call(thisObject, anotherFunctionNamedA, secondArgument))␊ + ` diff --git a/test/snapshots/prefer-includes.js.snap b/test/snapshots/prefer-includes.js.snap index 581bf3ef2d70ead05b461e50346d8cb53993863b..821049249ae8c6f13d1743ead1dacb5be6a69746 100644 GIT binary patch literal 1586 zcmV-22F>|FRzV1~SGE!(KHt(GC?3S)?XA~#r--3jFlVbE^0L=ceu4wJ1+kYQc)r>l|>|;k9VOLVziI>nZ z007fx*N2(@YO-`y-PlyMYHcAT>^+K>fdI^VZrhaOcdsleDXdR2ExCFJ5_TWO)W-oZ zm$fhYu=J4s*tWX2cO=h!3li2s@iT@!rDJj17kjs79htKyd3N<^NZ5RpgkLeLcQ=^p z)?L`YYrx2a?|)N&2?;wwEujtL)l>6Q)Qj^{#-49KZNGK?A|&kR6s3azSkpOcZ`V1Y z)s&cciETQb&JeaG5&LC*5og+_@h@-ws`!yH1u*I=I*u>#wt#Nf57j7?lZ#N{Yp5hS3`%c@6cN;%3ca(13u;`-s1SD)p zl7#Z10BnA~`1cPI@{bm~SIwX1dJ#avzDAKU41jar3~yb3^V*s#$Z+QPRLe0)*k>p% zV07lC8s~p~H|J#gx&>QzjNAtat9nAhW{j(C+70QY$t`HY8~UY}E?xN9V3V!fv3@Cj;;g8dtvCb9iCrjks}VT6eC7g!NIN5dg%2fh6yl zcrcV1t47Fx{AelXtmQp=hhTNqR`4Een$6`(W7Wa7umz}%!Nwj}4X^c~0wbz-^WIvw zfbc^>0V3b#H?X*AgstiI628y^50AqJA4 zPy05;%or-!<8^nNpiDg#Eg33PGp33c2RWI9%~mT|Nldl8lds_gFN^B2O*Xbs(fX$^ zNY-Lc2Sgdzd~BM7O&jo?BKxbDpfGjUQSi8XDfPQoA6kdXLG-V(2`z;%;zJ#O5=0eY z<3spJ43!Fnzj`%%;W8@vTJuM)HBD@%hbO6I%>y+rkF0sRV&#g_gr>>pe#K>c@`8Kc zQmfG0$8wA0T$fYaCTN4^FGcxq?#fy*$vt`}FI0N%tej}d`e_3Z+B62*3@wjvP5)Oc zyZnXx@rJm!!!{mI}Yk zmTOs`IUbYaGi4o!J|iuyRh0@extPJvlgTp~k!UwnV)I0n6$wL@$S5SmGto;jDkX^? zP{>j2AbWL+3_nnx*!v{0cN|T8J6XIXWF8@Lbn*QZ@xCWM>7DG7dK5MIVpPpNo{yTt z%|{U?q<;BhsO3RFhQiKG{b+K6uZIgkV0{y}9gpxgokrfqX*uNgBMTd4ID*VWc65&* k9MgMniV*h%c&iZM@bJz<93FyN?8`I5zZhc0JuDdj04lr-egFUf literal 656 zcmV;B0&o36RzVVb9rg6&2=Q*W73bT_w%xv`y83Tq5)8R z3y6b!7h4?t^CZ&8v0A{t@Y!!BuqX>N1or@OZ1j@IwP8DJjw~*pwn8*uG80&ID->I^ zFfe4Vu8l39a(mM%HVK~lAGyvmfkjiH_#+T^oemP`iZU0MySe0S#*4#{B(Q{B7$Hu^L*IeE%zvAvq&8IXg2iT_G*MNFlW%v$!NRFF92Y z#Z8QoP#*vtfG`~BHdLoG)c8+5Xs#%cCs|o$;Bl_c*C2q z8tQ0>Le!8RoGz}x?P5jx2C@v)$%+KsT!q`sHYmxNPJyhbjz1eRR={#0M*c%_FP#Dy zJ>%&p7!sTJ4C$Zuh)))HlJWqR7tw|~Mmol^2n$Tm%I8K{`HWBrvCjlcK-0nDq}49% qglZS8UZAxf6k!QQBT=EIMj=U)NKYUsM@u{O@dN<6)W!Q_2><{%r6-O6