Skip to content

Commit 29f3c9c

Browse files
authored
prefer-set-has: Supports more types of array (#641)
1 parent 4bee9fb commit 29f3c9c

File tree

3 files changed

+263
-26
lines changed

3 files changed

+263
-26
lines changed

rules/prefer-set-has.js

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,84 @@
11
'use strict';
22
const getDocumentationUrl = require('./utils/get-documentation-url');
33
const getReferences = require('./utils/get-references');
4+
const methodSelector = require('./utils/method-selector');
5+
6+
// `[]`
7+
const arrayExpressionSelector = [
8+
'[init.type="ArrayExpression"]'
9+
].join('');
10+
11+
// `Array()`
12+
const ArraySelector = [
13+
'[init.type="CallExpression"]',
14+
'[init.callee.type="Identifier"]',
15+
'[init.callee.name="Array"]'
16+
].join('');
17+
18+
// `new Array()`
19+
const newArraySelector = [
20+
'[init.type="NewExpression"]',
21+
'[init.callee.type="Identifier"]',
22+
'[init.callee.name="Array"]'
23+
].join('');
24+
25+
// `Array.from()`
26+
// `Array.of()`
27+
const arrayStaticMethodSelector = methodSelector({
28+
object: 'Array',
29+
names: ['from', 'of'],
30+
property: 'init'
31+
});
32+
33+
// `array.concat()`
34+
// `array.copyWithin()`
35+
// `array.fill()`
36+
// `array.filter()`
37+
// `array.flat()`
38+
// `array.flatMap()`
39+
// `array.map()`
40+
// `array.reverse()`
41+
// `array.slice()`
42+
// `array.sort()`
43+
// `array.splice()`
44+
const arrayMethodSelector = methodSelector({
45+
names: [
46+
'concat',
47+
'copyWithin',
48+
'fill',
49+
'filter',
50+
'flat',
51+
'flatMap',
52+
'map',
53+
'reverse',
54+
'slice',
55+
'sort',
56+
'splice'
57+
],
58+
property: 'init'
59+
});
460

561
const selector = [
6-
':not(ExportNamedDeclaration)',
7-
'>',
862
'VariableDeclaration',
63+
// Exclude `export const foo = [];`
64+
`:not(${
65+
[
66+
'ExportNamedDeclaration',
67+
'>',
68+
'VariableDeclaration.declaration'
69+
].join('')
70+
})`,
971
'>',
10-
'VariableDeclarator',
11-
'[init.type="ArrayExpression"]',
72+
'VariableDeclarator.declarations',
73+
`:matches(${
74+
[
75+
arrayExpressionSelector,
76+
ArraySelector,
77+
newArraySelector,
78+
arrayStaticMethodSelector,
79+
arrayMethodSelector
80+
].join(',')
81+
})`,
1282
'>',
1383
'Identifier.id'
1484
].join('');

rules/utils/method-selector.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@ module.exports = options => {
77
length,
88
object,
99
min,
10-
max
10+
max,
11+
property = ''
1112
} = {
1213
min: 0,
1314
max: Infinity,
1415
...options
1516
};
1617

18+
const prefix = property ? `${property}.` : '';
19+
1720
const selector = [
18-
'CallExpression',
19-
'[callee.type="MemberExpression"]',
20-
'[callee.computed=false]',
21-
'[callee.property.type="Identifier"]'
21+
`[${prefix}type="CallExpression"]`,
22+
`[${prefix}callee.type="MemberExpression"]`,
23+
`[${prefix}callee.computed=false]`,
24+
`[${prefix}callee.property.type="Identifier"]`
2225
];
2326

2427
if (name) {
@@ -28,33 +31,33 @@ module.exports = options => {
2831
if (Array.isArray(names) && names.length !== 0) {
2932
selector.push(
3033
':matches(' +
31-
names.map(name => `[callee.property.name="${name}"]`).join(', ') +
34+
names.map(name => `[${prefix}callee.property.name="${name}"]`).join(', ') +
3235
')'
3336
);
3437
}
3538

3639
if (object) {
37-
selector.push('[callee.object.type="Identifier"]');
38-
selector.push(`[callee.object.name="${object}"]`);
40+
selector.push(`[${prefix}callee.object.type="Identifier"]`);
41+
selector.push(`[${prefix}callee.object.name="${object}"]`);
3942
}
4043

4144
if (typeof length === 'number') {
42-
selector.push(`[arguments.length=${length}]`);
45+
selector.push(`[${prefix}arguments.length=${length}]`);
4346
}
4447

4548
if (min !== 0) {
46-
selector.push(`[arguments.length>=${min}]`);
49+
selector.push(`[${prefix}arguments.length>=${min}]`);
4750
}
4851

4952
if (Number.isFinite(max)) {
50-
selector.push(`[arguments.length<=${max}]`);
53+
selector.push(`[${prefix}arguments.length<=${max}]`);
5154
}
5255

5356
const maxArguments = Number.isFinite(max) ? max : length;
5457
if (typeof maxArguments === 'number') {
5558
// Exclude arguments with `SpreadElement` type
5659
for (let index = 0; index < maxArguments; index += 1) {
57-
selector.push(`[arguments.${index}.type!="SpreadElement"]`);
60+
selector.push(`[${prefix}arguments.${index}.type!="SpreadElement"]`);
5861
}
5962
}
6063

test/prefer-set-has.js

Lines changed: 174 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ const createError = name => [
2121
}
2222
];
2323

24+
const methodsReturnsArray = [
25+
'concat',
26+
'copyWithin',
27+
'fill',
28+
'filter',
29+
'flat',
30+
'flatMap',
31+
'map',
32+
'reverse',
33+
'slice',
34+
'sort',
35+
'splice'
36+
];
37+
2438
ruleTester.run(ruleId, rule, {
2539
valid: [
2640
outdent`
@@ -84,19 +98,10 @@ ruleTester.run(ruleId, rule, {
8498
const exists = foo.includes(1);
8599
`,
86100

87-
// Not `ArrayExpression`, maybe we can enable some of those later
88101
outdent`
89102
const foo = bar;
90103
const exists = foo.includes(1);
91104
`,
92-
outdent`
93-
const foo = [1, 2, 3].slice();
94-
const exists = foo.includes(1);
95-
`,
96-
outdent`
97-
const foo = Array.from(bar);
98-
const exists = foo.includes(1);
99-
`,
100105

101106
// Extra arguments
102107
outdent`
@@ -171,6 +176,100 @@ ruleTester.run(ruleId, rule, {
171176
const foo = [1, 2, 3];
172177
module.exports.foo = foo;
173178
const exists = foo.includes(1);
179+
`,
180+
181+
// `Array()`
182+
outdent`
183+
const foo = NotArray(1, 2);
184+
const exists = foo.includes(1);
185+
`,
186+
187+
// `new Array()`
188+
outdent`
189+
const foo = new NotArray(1, 2);
190+
const exists = foo.includes(1);
191+
`,
192+
193+
// `Array.from()` / `Array.of()`
194+
// Not `Array`
195+
outdent`
196+
const foo = NotArray.from({length: 1}, (_, index) => index);
197+
const exists = foo.includes(1);
198+
`,
199+
outdent`
200+
const foo = NotArray.of(1, 2);
201+
const exists = foo.includes(1);
202+
`,
203+
// Not `Listed`
204+
outdent`
205+
const foo = Array.notListed();
206+
const exists = foo.includes(1);
207+
`,
208+
// Computed
209+
outdent`
210+
const foo = Array[from]({length: 1}, (_, index) => index);
211+
const exists = foo.includes(1);
212+
`,
213+
outdent`
214+
const foo = Array[of](1, 2);
215+
const exists = foo.includes(1);
216+
`,
217+
// Not Identifier
218+
outdent`
219+
const foo = 'Array'.from({length: 1}, (_, index) => index);
220+
const exists = foo.includes(1);
221+
`,
222+
outdent`
223+
const foo = 'Array'.of(1, 2);
224+
const exists = foo.includes(1);
225+
`,
226+
outdent`
227+
const foo = Array['from']({length: 1}, (_, index) => index);
228+
const exists = foo.includes(1);
229+
`,
230+
outdent`
231+
const foo = Array['of'](1, 2);
232+
const exists = foo.includes(1);
233+
`,
234+
235+
outdent`
236+
const foo = of(1, 2);
237+
const exists = foo.includes(1);
238+
`,
239+
outdent`
240+
const foo = from({length: 1}, (_, index) => index);
241+
const exists = foo.includes(1);
242+
`,
243+
244+
// Methods
245+
// Not call
246+
...methodsReturnsArray.map(method => outdent`
247+
const foo = bar.${method};
248+
const exists = foo.includes(1);
249+
`),
250+
...methodsReturnsArray.map(method => outdent`
251+
const foo = new bar.${method}();
252+
const exists = foo.includes(1);
253+
`),
254+
// Not MemberExpression
255+
...methodsReturnsArray.map(method => outdent`
256+
const foo = ${method}();
257+
const exists = foo.includes(1);
258+
`),
259+
// Computed
260+
...methodsReturnsArray.map(method => outdent`
261+
const foo = bar[${method}]();
262+
const exists = foo.includes(1);
263+
`),
264+
// Not `Identifier`
265+
...methodsReturnsArray.map(method => outdent`
266+
const foo = bar["${method}"]();
267+
const exists = foo.includes(1);
268+
`),
269+
// Not listed method
270+
outdent`
271+
const foo = bar.notListed();
272+
const exists = foo.includes(1);
174273
`
175274
],
176275
invalid: [
@@ -238,6 +337,71 @@ ruleTester.run(ruleId, rule, {
238337
...createError('foo'),
239338
...createError('bar')
240339
]
241-
}
340+
},
341+
342+
// `Array()`
343+
{
344+
code: outdent`
345+
const foo = Array(1, 2);
346+
const exists = foo.includes(1);
347+
`,
348+
output: outdent`
349+
const foo = new Set(Array(1, 2));
350+
const exists = foo.has(1);
351+
`,
352+
errors: createError('foo')
353+
},
354+
355+
// `new Array()`
356+
{
357+
code: outdent`
358+
const foo = new Array(1, 2);
359+
const exists = foo.includes(1);
360+
`,
361+
output: outdent`
362+
const foo = new Set(new Array(1, 2));
363+
const exists = foo.has(1);
364+
`,
365+
errors: createError('foo')
366+
},
367+
368+
// `Array.from()`
369+
{
370+
code: outdent`
371+
const foo = Array.from({length: 1}, (_, index) => index);
372+
const exists = foo.includes(1);
373+
`,
374+
output: outdent`
375+
const foo = new Set(Array.from({length: 1}, (_, index) => index));
376+
const exists = foo.has(1);
377+
`,
378+
errors: createError('foo')
379+
},
380+
381+
// `Array.of()`
382+
{
383+
code: outdent`
384+
const foo = Array.of(1, 2);
385+
const exists = foo.includes(1);
386+
`,
387+
output: outdent`
388+
const foo = new Set(Array.of(1, 2));
389+
const exists = foo.has(1);
390+
`,
391+
errors: createError('foo')
392+
},
393+
394+
// Methods
395+
...methodsReturnsArray.map(method => ({
396+
code: outdent`
397+
const foo = bar.${method}();
398+
const exists = foo.includes(1);
399+
`,
400+
output: outdent`
401+
const foo = new Set(bar.${method}());
402+
const exists = foo.has(1);
403+
`,
404+
errors: createError('foo')
405+
}))
242406
]
243407
});

0 commit comments

Comments
 (0)