Skip to content

Commit b4e5ce2

Browse files
authored
prefer-array-flat: Stop reporting plain concat normalization (#3106)
1 parent 2e6696d commit b4e5ce2

6 files changed

Lines changed: 106 additions & 590 deletions

File tree

docs/rules/prefer-array-flat.md

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<!-- end auto-generated rule header -->
1010
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
1111

12-
ES2019 introduced a new method [`Array#flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) that flatten arrays.
12+
ES2019 introduced a new method [`Array#flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) that flattens arrays.
1313

1414
## Examples
1515

@@ -48,16 +48,7 @@ const foo = underscore.flatten(array);
4848
const foo = array.flat();
4949
```
5050

51-
```js
52-
//
53-
const foo = [].concat(maybeArray);
54-
55-
//
56-
const foo = Array.prototype.concat.call([], maybeArray);
57-
58-
//
59-
const foo = [maybeArray].flat();
60-
```
51+
This rule intentionally does not report plain concat normalization like `[].concat(value)` or `Array.prototype.concat.call([], value)`. Those patterns do not consume an array of concat arguments, so they are better handled by [`prefer-spread`](./prefer-spread.md), which can replace ordinary `Array#concat()` usage.
6152

6253
## Options
6354

rules/prefer-array-flat.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ const arrayReduce = {
9696
description: 'Array#reduce()',
9797
};
9898

99-
// `[].concat(maybeArray)`
10099
// `[].concat(...array)`
101100
const emptyArrayConcat = {
102101
testFunction(node) {
@@ -107,18 +106,16 @@ const emptyArrayConcat = {
107106
optionalCall: false,
108107
optionalMember: false,
109108
})
110-
&& isEmptyArrayExpression(node.callee.object);
109+
&& isEmptyArrayExpression(node.callee.object)
110+
&& node.arguments[0].type === 'SpreadElement';
111111
},
112112
getArrayNode(node) {
113-
const argumentNode = node.arguments[0];
114-
return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode;
113+
return node.arguments[0].argument;
115114
},
116115
description: '[].concat()',
117-
shouldSwitchToArray: node => node.arguments[0].type !== 'SpreadElement',
118116
};
119117

120118
// - `[].concat.apply([], array)` and `Array.prototype.concat.apply([], array)`
121-
// - `[].concat.call([], maybeArray)` and `Array.prototype.concat.call([], maybeArray)`
122119
// - `[].concat.call([], ...array)` and `Array.prototype.concat.call([], ...array)`
123120
const arrayPrototypeConcat = {
124121
testFunction(node) {
@@ -140,16 +137,21 @@ const arrayPrototypeConcat = {
140137
const [firstArgument, secondArgument] = node.arguments;
141138
return isEmptyArrayExpression(firstArgument)
142139
&& (
143-
node.callee.property.name === 'call'
144-
|| secondArgument.type !== 'SpreadElement'
140+
(
141+
node.callee.property.name === 'apply'
142+
&& secondArgument.type !== 'SpreadElement'
143+
)
144+
|| (
145+
node.callee.property.name === 'call'
146+
&& secondArgument.type === 'SpreadElement'
147+
)
145148
);
146149
},
147150
getArrayNode(node) {
148151
const argumentNode = node.arguments[1];
149152
return argumentNode.type === 'SpreadElement' ? argumentNode.argument : argumentNode;
150153
},
151154
description: 'Array.prototype.concat()',
152-
shouldSwitchToArray: node => node.arguments[1].type !== 'SpreadElement' && node.callee.property.name === 'call',
153155
};
154156

155157
const lodashFlattenFunctions = [

test/prefer-array-flat.js

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ test.snapshot({
159159
],
160160
});
161161

162-
// `[].concat(array)`
162+
// Plain `[].concat(value)` normalization
163163
test.snapshot({
164164
valid: [
165165
'[].concat',
@@ -172,15 +172,14 @@ test.snapshot({
172172
'[].concat(array, EXTRA_ARGUMENT)',
173173
'[]?.concat(array)',
174174
'[].concat?.(array)',
175-
],
176-
invalid: [
177175
'[].concat(maybeArray)',
178176
'[].concat( ((0, maybeArray)) )',
179177
'[].concat( ((maybeArray)) )',
180178
'[].concat( [foo] )',
181179
'[].concat( [[foo]] )',
182180
'function foo(){return[].concat(maybeArray)}',
183181
],
182+
invalid: [],
184183
});
185184

186185
// `[].concat(...array)`
@@ -208,7 +207,6 @@ test.snapshot({
208207
});
209208

210209
// - `[].concat.apply([], array)`
211-
// - `[].concat.call([], maybeArray)`
212210
// - `[].concat.call([], ...array)`
213211
test.snapshot({
214212
valid: [
@@ -227,6 +225,11 @@ test.snapshot({
227225
'[].concat.apply?.([], array)',
228226
'[].concat?.apply([], array)',
229227
'[]?.concat.apply([], array)',
228+
'[].concat.call([], maybeArray)',
229+
'[].concat.call([], ((0, maybeArray)))',
230+
'[].concat.call([], ((maybeArray)))',
231+
'[].concat.call([], [foo])',
232+
'[].concat.call([], [[foo]])',
230233
],
231234
invalid: [
232235
'[].concat.apply([], array)',
@@ -235,12 +238,6 @@ test.snapshot({
235238
'[].concat.apply([], [foo])',
236239
'[].concat.apply([], [[foo]])',
237240

238-
'[].concat.call([], maybeArray)',
239-
'[].concat.call([], ((0, maybeArray)))',
240-
'[].concat.call([], ((maybeArray)))',
241-
'[].concat.call([], [foo])',
242-
'[].concat.call([], [[foo]])',
243-
244241
'[].concat.call([], ...array)',
245242
'[].concat.call([], ...((0, array)))',
246243
'[].concat.call([], ...((array)))',
@@ -252,7 +249,6 @@ test.snapshot({
252249
});
253250

254251
// - `Array.prototype.concat.apply([], array)`
255-
// - `Array.prototype.concat.call([], maybeArray)`
256252
// - `Array.prototype.concat.call([], ...array)`
257253
test.snapshot({
258254
valid: [
@@ -275,6 +271,11 @@ test.snapshot({
275271
'Array.prototype?.concat.apply([], array)',
276272
'Array?.prototype.concat.apply([], array)',
277273
'object.Array.prototype.concat.apply([], array)',
274+
'Array.prototype.concat.call([], maybeArray)',
275+
'Array.prototype.concat.call([], ((0, maybeArray)))',
276+
'Array.prototype.concat.call([], ((maybeArray)))',
277+
'Array.prototype.concat.call([], [foo])',
278+
'Array.prototype.concat.call([], [[foo]])',
278279
],
279280
invalid: [
280281
'Array.prototype.concat.apply([], array)',
@@ -283,12 +284,6 @@ test.snapshot({
283284
'Array.prototype.concat.apply([], [foo])',
284285
'Array.prototype.concat.apply([], [[foo]])',
285286

286-
'Array.prototype.concat.call([], maybeArray)',
287-
'Array.prototype.concat.call([], ((0, maybeArray)))',
288-
'Array.prototype.concat.call([], ((maybeArray)))',
289-
'Array.prototype.concat.call([], [foo])',
290-
'Array.prototype.concat.call([], [[foo]])',
291-
292287
'Array.prototype.concat.call([], ...array)',
293288
'Array.prototype.concat.call([], ...((0, array)))',
294289
'Array.prototype.concat.call([], ...((array)))',
@@ -413,63 +408,62 @@ test.snapshot({
413408
valid: [
414409
'array.flat()',
415410
'array.flat(1)',
416-
],
417-
invalid: [
418-
// ASI
419411
outdent`
420412
before()
421-
Array.prototype.concat.apply([], [array].concat(array))
413+
Array.prototype.concat.call([], +1)
422414
`,
415+
'Array.prototype.concat.call([], (0, array))',
416+
'async function a() { return [].concat(await getArray()); }',
423417
outdent`
424418
before()
425-
Array.prototype.concat.apply([], +1)
419+
Array.prototype.concat.call([], 1)
426420
`,
427421
outdent`
428422
before()
429-
Array.prototype.concat.call([], +1)
423+
Array.prototype.concat.call([], 1.)
430424
`,
431-
// Parentheses
432-
'Array.prototype.concat.apply([], (0, array))',
433-
'Array.prototype.concat.call([], (0, array))',
434-
'async function a() { return [].concat(await getArray()); }',
435-
'_.flatten((0, array))',
436-
'async function a() { return _.flatten(await getArray()); }',
437-
'async function a() { return _.flatten((await getArray())); }',
438425
outdent`
439426
before()
440-
Array.prototype.concat.apply([], 1)
427+
Array.prototype.concat.call([], .1)
441428
`,
442429
outdent`
443430
before()
444-
Array.prototype.concat.call([], 1)
431+
Array.prototype.concat.call([], 1.0)
445432
`,
433+
'[].concat(some./**/array)',
434+
'[/**/].concat(some./**/array)',
435+
'[/**/].concat(some.array)',
436+
],
437+
invalid: [
438+
// ASI
446439
outdent`
447440
before()
448-
Array.prototype.concat.apply([], 1.)
441+
Array.prototype.concat.apply([], [array].concat(array))
449442
`,
450443
outdent`
451444
before()
452-
Array.prototype.concat.call([], 1.)
445+
Array.prototype.concat.apply([], +1)
453446
`,
447+
// Parentheses
448+
'Array.prototype.concat.apply([], (0, array))',
449+
'_.flatten((0, array))',
450+
'async function a() { return _.flatten(await getArray()); }',
451+
'async function a() { return _.flatten((await getArray())); }',
454452
outdent`
455453
before()
456-
Array.prototype.concat.apply([], .1)
454+
Array.prototype.concat.apply([], 1)
457455
`,
458456
outdent`
459457
before()
460-
Array.prototype.concat.call([], .1)
458+
Array.prototype.concat.apply([], 1.)
461459
`,
462460
outdent`
463461
before()
464-
Array.prototype.concat.apply([], 1.0)
462+
Array.prototype.concat.apply([], .1)
465463
`,
466464
outdent`
467465
before()
468-
Array.prototype.concat.call([], 1.0)
466+
Array.prototype.concat.apply([], 1.0)
469467
`,
470-
// Comment
471-
'[].concat(some./**/array)',
472-
'[/**/].concat(some./**/array)',
473-
'[/**/].concat(some.array)',
474468
],
475469
});

test/prefer-spread.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import test from 'ava';
2+
import {Linter} from 'eslint';
13
import outdent from 'outdent';
4+
import plugin from '../index.js';
25
import {getTester} from './utils/test.js';
6+
import {DEFAULT_LANGUAGE_OPTIONS} from './utils/language-options.js';
37

4-
const {test} = getTester(import.meta);
8+
const {test: ruleTest, rule} = getTester(import.meta);
59

610
// `Array.from`
7-
test.snapshot({
11+
ruleTest.snapshot({
812
valid: [
913
'[...set].map(() => {});',
1014
// TypedArray.from
@@ -157,7 +161,7 @@ test.snapshot({
157161
});
158162

159163
// `Array#concat`
160-
test.snapshot({
164+
ruleTest.snapshot({
161165
valid: [
162166
'new Array.concat(1)',
163167
'concat(1)',
@@ -346,8 +350,36 @@ test.snapshot({
346350
],
347351
});
348352

353+
test('fixes chained empty concat calls with no-useless-spread', t => {
354+
const linter = new Linter();
355+
const result = linter.verifyAndFix(
356+
'const a = [].concat([1]).concat([2]);',
357+
[
358+
{
359+
files: ['**'],
360+
languageOptions: DEFAULT_LANGUAGE_OPTIONS,
361+
plugins: {
362+
'rule-to-test': {
363+
rules: {
364+
'prefer-spread': rule,
365+
'no-useless-spread': plugin.rules['no-useless-spread'],
366+
},
367+
},
368+
},
369+
rules: {
370+
'rule-to-test/prefer-spread': 'error',
371+
'rule-to-test/no-useless-spread': 'error',
372+
},
373+
},
374+
],
375+
{filename: 'index.js'},
376+
);
377+
378+
t.is(result.output, 'const a = [1, 2];');
379+
});
380+
349381
// `Array#slice`
350-
test.snapshot({
382+
ruleTest.snapshot({
351383
valid: [
352384
'new Array.slice()',
353385
'slice()',
@@ -426,7 +458,7 @@ test.snapshot({
426458
});
427459

428460
// `Array#toSpliced`
429-
test.snapshot({
461+
ruleTest.snapshot({
430462
valid: [
431463
'new Array.toSpliced()',
432464
'toSpliced()',
@@ -467,7 +499,7 @@ test.snapshot({
467499
});
468500

469501
// `String#slice('')`
470-
test.snapshot({
502+
ruleTest.snapshot({
471503
valid: [
472504
'new foo.split("")',
473505
'split("")',

0 commit comments

Comments
 (0)