Skip to content

Commit 119d23a

Browse files
committed
fix(linter/prefer-array-flat-map): error for .flat(1.0) (#12360)
Previously this rule did not produce an error for `.map(f).flat(1.0)`. This PR fixes that. Also round down the argument and produce errors for e.g. `.map(f).flat(1.9)`. This follows ECMA spec (see comments in code), and I've also verified that all the weird test cases match behavior of `.flat(...)` in NodeJS.
1 parent 8777839 commit 119d23a

File tree

2 files changed

+55
-6
lines changed

2 files changed

+55
-6
lines changed

crates/oxc_linter/src/rules/unicorn/prefer_array_flat_map.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,13 @@ impl Rule for PreferArrayFlatMap {
6868
}
6969

7070
if let Some(first_arg) = flat_call_expr.arguments.first() {
71-
if let Argument::NumericLiteral(number_lit) = first_arg {
72-
if number_lit.raw.as_ref().unwrap() != "1" {
73-
return;
74-
}
75-
} else {
71+
// `Array.prototype.flat` rounds down the argument.
72+
// So `.flat(1.5)` is equivalent to `.flat(1)`.
73+
// https://tc39.es/ecma262/#sec-array.prototype.flat
74+
// https://tc39.es/ecma262/#sec-tointegerorinfinity
75+
// https://tc39.es/ecma262/#eqn-truncate
76+
#[expect(clippy::float_cmp)]
77+
if !matches!(first_arg, Argument::NumericLiteral(lit) if lit.value.floor() == 1.0) {
7678
return;
7779
}
7880
}
@@ -107,7 +109,13 @@ fn test() {
107109
("const bar = [[1],[2],[3]].flat()", None),
108110
("const bar = [1,2,3].map(i => [i]).sort().flat()", None),
109111
("const bar = [[1],[2],[3]].map(i => [i]).flat(2)", None),
112+
("const bar = [[1],[2],[3]].map(i => [i]).flat(2.0)", None),
113+
// Parsed as 0.9999999999999999. Rounds down to 0.
114+
("const bar = [[1],[2],[3]].map(i => [i]).flat(0.99999999999999994)", None),
115+
// Parsed as 2.0.
116+
("const bar = [[1],[2],[3]].map(i => [i]).flat(1.99999999999999989)", None),
110117
("const bar = [[1],[2],[3]].map(i => [i]).flat(1, null)", None),
118+
("const bar = [[1],[2],[3]].map(i => [i]).flat(-1)", None),
111119
("const bar = [[1],[2],[3]].map(i => [i]).flat(Infinity)", None),
112120
("const bar = [[1],[2],[3]].map(i => [i]).flat(Number.POSITIVE_INFINITY)", None),
113121
("const bar = [[1],[2],[3]].map(i => [i]).flat(Number.MAX_VALUE)", None),
@@ -117,11 +125,17 @@ fn test() {
117125
("const bar = [[1],[2],[3]].map(i => [i]).flat(+1)", None),
118126
("const bar = [[1],[2],[3]].map(i => [i]).flat(foo)", None),
119127
("const bar = [[1],[2],[3]].map(i => [i]).flat(foo.bar)", None),
120-
("const bar = [[1],[2],[3]].map(i => [i]).flat(1.00)", None),
121128
];
122129

123130
let fail = vec![
124131
("const bar = [[1],[2],[3]].map(i => [i]).flat()", None),
132+
("const bar = [[1],[2],[3]].map(i => [i]).flat(1)", None),
133+
("const bar = [[1],[2],[3]].map(i => [i]).flat(1.0)", None),
134+
("const bar = [[1],[2],[3]].map(i => [i]).flat(1.00)", None),
135+
// Parsed as 1.0.
136+
("const bar = [[1],[2],[3]].map(i => [i]).flat(0.99999999999999995)", None),
137+
// Parsed as 1.9999999999999998. Rounds down to 1.
138+
("const bar = [[1],[2],[3]].map(i => [i]).flat(1.99999999999999988)", None),
125139
("const bar = [[1],[2],[3]].map(i => [i]).flat(1,)", None),
126140
("const bar = [1,2,3].map(i => [i]).flat()", None),
127141
("const bar = [1,2,3].map((i) => [i]).flat()", None),

crates/oxc_linter/src/snapshots/unicorn_prefer_array_flat_map.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,41 @@ source: crates/oxc_linter/src/tester.rs
88
╰────
99
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
1010

11+
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
12+
╭─[prefer_array_flat_map.tsx:1:13]
13+
1const bar = [[1],[2],[3]].map(i => [i]).flat(1)
14+
· ───────────────────────────────────
15+
╰────
16+
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
17+
18+
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
19+
╭─[prefer_array_flat_map.tsx:1:13]
20+
1const bar = [[1],[2],[3]].map(i => [i]).flat(1.0)
21+
· ─────────────────────────────────────
22+
╰────
23+
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
24+
25+
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
26+
╭─[prefer_array_flat_map.tsx:1:13]
27+
1const bar = [[1],[2],[3]].map(i => [i]).flat(1.00)
28+
· ──────────────────────────────────────
29+
╰────
30+
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
31+
32+
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
33+
╭─[prefer_array_flat_map.tsx:1:13]
34+
1const bar = [[1],[2],[3]].map(i => [i]).flat(0.99999999999999995)
35+
· ─────────────────────────────────────────────────────
36+
╰────
37+
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
38+
39+
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
40+
╭─[prefer_array_flat_map.tsx:1:13]
41+
1const bar = [[1],[2],[3]].map(i => [i]).flat(1.99999999999999988)
42+
· ─────────────────────────────────────────────────────
43+
╰────
44+
help: Prefer `.flatMap(…)` over `.map(…).flat()`.
45+
1146
eslint-plugin-unicorn(prefer-array-flat-map): `Array.flatMap` performs `Array.map` and `Array.flat` in one step.
1247
╭─[prefer_array_flat_map.tsx:1:13]
1348
1const bar = [[1],[2],[3]].map(i => [i]).flat(1,)

0 commit comments

Comments
 (0)