Skip to content

Commit

Permalink
Support destructuring assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed May 18, 2020
1 parent 3ec4c02 commit b4d57b8
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 15 deletions.
8 changes: 8 additions & 0 deletions docs/rules/prefer-array-find.md
Expand Up @@ -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 === '🦄');
```
44 changes: 41 additions & 3 deletions rules/prefer-array-find.js
Expand Up @@ -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',
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
});
}
};
};
Expand All @@ -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(…)`.'
}
}
};
95 changes: 83 additions & 12 deletions test/prefer-array-find.js
Expand Up @@ -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: {
Expand Down Expand Up @@ -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)',
Expand All @@ -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]',
Expand All @@ -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: [
{
Expand All @@ -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)',
Expand All @@ -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`
Expand Down Expand Up @@ -194,7 +265,7 @@ ruleTester.run('prefer-array-find', rule, {
// comment 5
;
`,
errors: [{messageId: MESSAGE_ID_DESTRUCTURING}]
errors: [{messageId: MESSAGE_ID_DESTRUCTURING_DECLARATION}]
}
]
});

0 comments on commit b4d57b8

Please sign in to comment.