diff --git a/docs/rules/prefer-array-find.md b/docs/rules/prefer-array-find.md index 05b879dd23..4353c0cbb3 100644 --- a/docs/rules/prefer-array-find.md +++ b/docs/rules/prefer-array-find.md @@ -18,8 +18,16 @@ const item = array.filter(x => x === '🦄').shift(); const [item] = array.filter(x => x === '🦄'); ``` +```js +[item] = array.filter(x => x === '🦄'); +``` + ## Pass ```js const item = array.find(x => x === '🦄'); ``` + +```js +item = array.find(x => x === '🦄'); +``` diff --git a/rules/prefer-array-find.js b/rules/prefer-array-find.js index afb7d2c2f0..45811667a6 100644 --- a/rules/prefer-array-find.js +++ b/rules/prefer-array-find.js @@ -4,7 +4,8 @@ const methodSelector = require('./utils/method-selector'); const MESSAGE_ID_ZERO_INDEX = 'prefer-array-find-over-filter-zero-index'; const MESSAGE_ID_SHIFT = 'prefer-array-find-over-filter-shift'; -const MESSAGE_ID_DESTRUCTURING = 'prefer-array-find-over-filter-destructuring'; +const MESSAGE_ID_DESTRUCTURING_DECLARATION = 'prefer-array-find-over-filter-destructuring-declaration'; +const MESSAGE_ID_DESTRUCTURING_ASSIGNMENT = 'prefer-array-find-over-filter-destructuring-assignment'; const zeroIndexSelector = [ 'MemberExpression', @@ -45,6 +46,19 @@ const destructuringSelector = [ }) ].join(''); +const destructuringAssignmentSelector = [ + 'AssignmentExpression', + '[left.type="ArrayPattern"]', + '[left.elements.length=1]', + '[left.elements.0.type!="AssignmentPattern"]', + methodSelector({ + name: 'filter', + min: 1, + max: 2, + property: 'right' + }) +].join(''); + const create = context => { return { [zeroIndexSelector](node) { @@ -70,12 +84,34 @@ const create = context => { [destructuringSelector](node) { context.report({ node, - messageId: MESSAGE_ID_DESTRUCTURING, + messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION, fix: fixer => [ fixer.replaceText(node.init.callee.property, 'find'), fixer.replaceText(node.id, context.getSourceCode().getText(node.id.elements[0])) ] }); + }, + [destructuringAssignmentSelector](node) { + context.report({ + node, + messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT, + fix: fixer => { + const assignTarget = node.left.elements[0]; + // `AssignmentExpression` always starts with `[` or `(`, so we don't need check ASI + + // Need add `()` to the `AssignmentExpression` + // - `ObjectExpression`: `[{foo}] = array.filter(bar)` fix to `{foo} = array.find(bar)` + // - `ObjectPattern`: `[{foo = baz}] = array.filter(bar)` + const needParenthesize = assignTarget.type === 'ObjectExpression' || assignTarget.type === 'ObjectPattern'; + + return [ + needParenthesize && fixer.insertTextBefore(node, '('), + fixer.replaceText(node.right.callee.property, 'find'), + fixer.replaceText(node.left, context.getSourceCode().getText(assignTarget)), + needParenthesize && fixer.insertTextAfter(node, ')') + ].filter(Boolean) + } + }); } }; }; @@ -91,7 +127,9 @@ module.exports = { messages: { [MESSAGE_ID_ZERO_INDEX]: 'Prefer `.find(…)` over `.filter(…)[0]`.', [MESSAGE_ID_SHIFT]: 'Prefer `.find(…)` over `.filter(…).shift()`.', - [MESSAGE_ID_DESTRUCTURING]: 'Prefer `.find(…)` over destructuring `.filter(…)`.' + [MESSAGE_ID_DESTRUCTURING_DECLARATION]: 'Prefer `.find(…)` over destructuring `.filter(…)`.', + // Same message as `MESSAGE_ID_DESTRUCTURING_DECLARATION`, but different case + [MESSAGE_ID_DESTRUCTURING_ASSIGNMENT]: 'Prefer `.find(…)` over destructuring `.filter(…)`.' } } }; diff --git a/test/prefer-array-find.js b/test/prefer-array-find.js index 4f2036bdab..9bc863bc03 100644 --- a/test/prefer-array-find.js +++ b/test/prefer-array-find.js @@ -5,7 +5,8 @@ import rule from '../rules/prefer-array-find'; const MESSAGE_ID_ZERO_INDEX = 'prefer-array-find-over-filter-zero-index'; const MESSAGE_ID_SHIFT = 'prefer-array-find-over-filter-shift'; -const MESSAGE_ID_DESTRUCTURING = 'prefer-array-find-over-filter-destructuring'; +const MESSAGE_ID_DESTRUCTURING_DECLARATION = 'prefer-array-find-over-filter-destructuring-declaration'; +const MESSAGE_ID_DESTRUCTURING_ASSIGNMENT = 'prefer-array-find-over-filter-destructuring-assignment'; const ruleTester = avaRuleTester(test, { parserOptions: { @@ -40,9 +41,9 @@ ruleTester.run('prefer-array-find', rule, { 'array.filter(foo).shift(extraArgument)', 'array.filter(foo).shift(...[])', - // Test `const [item] = ` + // Test `const [item] = …` // Not `VariableDeclarator` - '[foo] = array.filter(bar)', + 'function a([foo] = array.filter(bar)) {}', // Not `ArrayPattern` 'const foo = array.filter(bar)', 'const {0: foo} = array.filter(bar)', @@ -52,28 +53,47 @@ ruleTester.run('prefer-array-find', rule, { 'const [, foo] = array.filter(bar)', 'const [foo = bar] = array.filter(baz)', 'const [{foo}] = array.filter(bar)', + 'const [foo = baz] = array.filter(bar)', + + // Test `[item] = …` + // Not `AssignmentExpression` + 'function a([foo] = array.filter(bar)) {}', + // Not `ArrayPattern` + 'foo = array.filter(bar)', + '({foo} = array.filter(bar))', + // `elements` + '[] = array.filter(bar)', + '[foo, another] = array.filter(bar)', + '[, foo] = array.filter(bar)', + // `AssignmentPattern` + '[foo = baz] = array.filter(bar)', // Test `.filter()` // Not `CallExpression` 'array.filter[0]', 'array.filter.shift()', 'const [foo] = array.filter', + '[foo] = array.filter', // Not `MemberExpression` 'filter(foo)[0]', 'filter(foo).shift()', 'const [foo] = filter(bar)', + '[foo] = filter(bar)', // `callee.property` is not a `Identifier` 'array["filter"](foo)[0]', 'array["filter"](foo).shift()', 'const [foo] = array["filter"](bar)', + '[foo] = array["filter"](bar)', // Computed 'array[filter](foo)[0]', 'array[filter](foo).shift()', 'const [foo] = array[filter](bar)', + '[foo] = array[filter](bar)', // Not `filter` 'array.notFilter(foo)[0]', 'array.notFilter(foo).shift()', 'const [foo] = array.notFilter(bar)', + '[foo] = array.notFilter(bar)', // More or less argument(s) 'array.filter()[0]', 'array.filter(foo, thisArgument, extraArgument)[0]', @@ -83,7 +103,10 @@ ruleTester.run('prefer-array-find', rule, { 'array.filter(...foo).shift()', 'const [foo] = array.filter()', 'const [foo] = array.filter(bar, thisArgument, extraArgument)', - 'const [foo] = array.filter(...bar)' + 'const [foo] = array.filter(...bar)', + '[foo] = array.filter()', + '[foo] = array.filter(bar, thisArgument, extraArgument)', + '[foo] = array.filter(...bar)' ], invalid: [ { @@ -96,36 +119,84 @@ ruleTester.run('prefer-array-find', rule, { output: 'array.find(foo)', errors: [{messageId: MESSAGE_ID_SHIFT}] }, + + // VariableDeclarator { code: 'const [foo] = array.filter(bar)', output: 'const foo = array.find(bar)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: 'const [foo, ] = array.filter(bar)', output: 'const foo = array.find(bar)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: 'var [foo, ] = array.filter(bar)', output: 'var foo = array.find(bar)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: 'let [foo, ] = array.filter(bar)', output: 'let foo = array.find(bar)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: 'let a = 1, [foo, ] = array.filter(bar)', output: 'let a = 1, foo = array.find(bar)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: 'for (let [i] = array.filter(bar); i< 10; i++) {}', output: 'for (let i = array.find(bar); i< 10; i++) {}', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] + }, + + // AssignmentExpression + { + code: '[foo] = array.filter(bar)', + output: 'foo = array.find(bar)', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: '[foo.bar().baz] = array.filter(fn)', + output: 'foo.bar().baz = array.find(fn)', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: '[{foo}] = array.filter(fn);', + output: '({foo} = array.find(fn));', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: '[{foo = baz}] = array.filter(fn);', + output: '({foo = baz} = array.find(fn));', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: '[foo, ] = array.filter(bar)', + output: 'foo = array.find(bar)', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: 'for ([i] = array.filter(bar); i< 10; i++) {}', + output: 'for (i = array.find(bar); i< 10; i++) {}', + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] + }, + { + code: outdent` + let foo + const bar = [] + ;[foo] = array.filter(bar) + `, + output: outdent` + let foo + const bar = [] + ;foo = array.find(bar) + `, + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_ASSIGNMENT}] }, + { code: 'array.filter(foo, thisArgument)[0]', output: 'array.find(foo, thisArgument)', @@ -139,7 +210,7 @@ ruleTester.run('prefer-array-find', rule, { { code: 'const [foo] = array.filter(bar, thisArgument)', output: 'const foo = array.find(bar, thisArgument)', - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] }, { code: outdent` @@ -194,7 +265,7 @@ ruleTester.run('prefer-array-find', rule, { // comment 5 ; `, - errors: [{messageId: MESSAGE_ID_DESTRUCTURING}] + errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}] } ] });