Skip to content

Commit 615dfee

Browse files
committed
add array-map for keeping tuple length when using .map
1 parent 4d44ea0 commit 615dfee

File tree

4 files changed

+125
-0
lines changed

4 files changed

+125
-0
lines changed

readme.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ TypeScript's built-in typings are not perfect. `ts-reset` makes them better.
77
- 🚨 `.json` (in `fetch`) and `JSON.parse` both return `any`
88
- 🤦 `.filter(Boolean)` doesn't behave how you expect
99
- 😡 `array.includes` often breaks on readonly arrays
10+
- 😭 `array.map` on a tuple looses the tuple length
1011

1112
`ts-reset` smooths over these hard edges, just like a CSS reset does in the browser.
1213

@@ -293,6 +294,37 @@ const validate = (input: unknown) => {
293294
};
294295
```
295296

297+
### Keeping the tuple length in resulting tuple from `Array.map`
298+
299+
```ts
300+
import "@total-typescript/ts-reset/array-map";
301+
```
302+
303+
When you're using `Array.map` with a tuple, the length is lost. This means you loose the guard against accessing an item out of bounds.
304+
305+
```ts
306+
// BEFORE
307+
308+
const tuple = [1, 2, 3] as const;
309+
const mapped = tuple.map((a) => a + 1);
310+
311+
// oops. There's no 4th element, but no error
312+
console.log(tuple[3]);
313+
```
314+
315+
With `array-map` enabled, this code will now error:
316+
317+
```ts
318+
// AFTER
319+
import "@total-typescript/ts-reset/array-map";
320+
321+
const tuple = [1, 2, 3] as const;
322+
const mapped = tuple.map((a) => a + 1);
323+
324+
// Tuple type 'readonly [number, number, number]' of length '3' has no element at index '3'.
325+
console.log(tuple[3]);
326+
```
327+
296328
## Rules we won't add
297329

298330
### `Object.keys`/`Object.entries`

src/entrypoints/array-map.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path="utils.d.ts" />
2+
3+
interface ReadonlyArray<T> {
4+
map<U>(
5+
callbackfn: (value: T, index: number, array: readonly T[]) => U,
6+
thisArg?: any,
7+
): { [K in keyof this]: U };
8+
}
9+
10+
interface Array<T> {
11+
map<U>(
12+
callbackfn: (value: T, index: number, array: T[]) => U,
13+
thisArg?: any,
14+
): { [K in keyof this]: U };
15+
}

src/entrypoints/recommended.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
/// <reference path="set-has.d.ts" />
77
/// <reference path="map-has.d.ts" />
88
/// <reference path="array-index-of.d.ts" />
9+
/// <reference path="array-map.d.ts" />

src/tests/array-map.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { doNotExecute, Equal, Expect } from "./utils";
2+
3+
doNotExecute(async () => {
4+
const tuple = [0, 1] as const;
5+
const mapped = tuple.map(
6+
(
7+
value: (typeof tuple)[number],
8+
index: number,
9+
source: readonly (typeof tuple)[number][],
10+
) => 1,
11+
);
12+
13+
tuple.map(() => 1, {});
14+
15+
type tests = [
16+
Expect<Equal<(typeof mapped)["length"], 2>>,
17+
Expect<Equal<(typeof mapped)[0], number>>,
18+
];
19+
20+
mapped[0];
21+
mapped[1];
22+
// @ts-expect-error
23+
mapped[2];
24+
});
25+
26+
doNotExecute(async () => {
27+
const tuple = [0, 1] as [0, 1];
28+
const mapped = tuple.map(
29+
(
30+
value: (typeof tuple)[number],
31+
index: number,
32+
source: (typeof tuple)[number][],
33+
) => 1,
34+
);
35+
36+
tuple.map(() => 1, {});
37+
38+
type tests = [
39+
Expect<Equal<(typeof mapped)["length"], 2>>,
40+
Expect<Equal<(typeof mapped)[0], number>>,
41+
];
42+
43+
mapped[0];
44+
mapped[1];
45+
// @ts-expect-error
46+
mapped[2];
47+
});
48+
49+
doNotExecute(async () => {
50+
const arr: readonly number[] = [0, 1];
51+
const mapped = arr.map(
52+
(
53+
value: (typeof arr)[number],
54+
index: number,
55+
source: readonly (typeof arr)[number][],
56+
) => 1,
57+
);
58+
59+
arr.map(() => 1, {});
60+
61+
type tests = [Expect<Equal<(typeof mapped)["length"], number>>];
62+
});
63+
64+
doNotExecute(async () => {
65+
const arr: number[] = [0, 1];
66+
const mapped = arr.map(
67+
(
68+
value: (typeof arr)[number],
69+
index: number,
70+
source: (typeof arr)[number][],
71+
) => 1,
72+
);
73+
74+
arr.map(() => 1, {});
75+
76+
type tests = [Expect<Equal<(typeof mapped)["length"], number>>];
77+
});

0 commit comments

Comments
 (0)