diff --git a/readme.md b/readme.md
index 68f6a2b..a8bad0c 100644
--- a/readme.md
+++ b/readme.md
@@ -7,6 +7,7 @@ TypeScript's built-in typings are not perfect. `ts-reset` makes them better.
- 🚨 `.json` (in `fetch`) and `JSON.parse` both return `any`
- 🤦 `.filter(Boolean)` doesn't behave how you expect
- 😡 `array.includes` often breaks on readonly arrays
+- 😠`array.map` on a tuple looses the tuple length
`ts-reset` smooths over these hard edges, just like a CSS reset does in the browser.
@@ -293,6 +294,37 @@ const validate = (input: unknown) => {
};
```
+### Keeping the tuple length in resulting tuple from `Array.map`
+
+```ts
+import "@total-typescript/ts-reset/array-map";
+```
+
+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.
+
+```ts
+// BEFORE
+
+const tuple = [1, 2, 3] as const;
+const mapped = tuple.map((a) => a + 1);
+
+// oops. There's no 3rd element, but no error
+console.log(tuple[3]);
+```
+
+With `array-map` enabled, this code will now error:
+
+```ts
+// AFTER
+import "@total-typescript/ts-reset/array-map";
+
+const tuple = [1, 2, 3] as const;
+const mapped = tuple.map((a) => a + 1);
+
+// Tuple type 'readonly [number, number, number]' of length '3' has no element at index '3'.
+console.log(tuple[3]);
+```
+
## Rules we won't add
### `Object.keys`/`Object.entries`
diff --git a/src/entrypoints/array-map.d.ts b/src/entrypoints/array-map.d.ts
new file mode 100644
index 0000000..5935bde
--- /dev/null
+++ b/src/entrypoints/array-map.d.ts
@@ -0,0 +1,15 @@
+///
+
+interface ReadonlyArray {
+ map(
+ callbackfn: (value: T, index: number, array: readonly T[]) => U,
+ thisArg?: any,
+ ): { [K in keyof this]: U };
+}
+
+interface Array {
+ map(
+ callbackfn: (value: T, index: number, array: T[]) => U,
+ thisArg?: any,
+ ): { [K in keyof this]: U };
+}
diff --git a/src/entrypoints/recommended.d.ts b/src/entrypoints/recommended.d.ts
index 7e6b2b4..22678d0 100644
--- a/src/entrypoints/recommended.d.ts
+++ b/src/entrypoints/recommended.d.ts
@@ -6,3 +6,4 @@
///
///
///
+///
diff --git a/src/tests/array-map.ts b/src/tests/array-map.ts
new file mode 100644
index 0000000..788289c
--- /dev/null
+++ b/src/tests/array-map.ts
@@ -0,0 +1,77 @@
+import { doNotExecute, Equal, Expect } from "./utils";
+
+doNotExecute(async () => {
+ const tuple = [0, 1] as const;
+ const mapped = tuple.map(
+ (
+ value: (typeof tuple)[number],
+ index: number,
+ source: readonly (typeof tuple)[number][],
+ ) => 1,
+ );
+
+ tuple.map(() => 1, {});
+
+ type tests = [
+ Expect>,
+ Expect>,
+ ];
+
+ mapped[0];
+ mapped[1];
+ // @ts-expect-error
+ mapped[2];
+});
+
+doNotExecute(async () => {
+ const tuple = [0, 1] as [0, 1];
+ const mapped = tuple.map(
+ (
+ value: (typeof tuple)[number],
+ index: number,
+ source: (typeof tuple)[number][],
+ ) => 1,
+ );
+
+ tuple.map(() => 1, {});
+
+ type tests = [
+ Expect>,
+ Expect>,
+ ];
+
+ mapped[0];
+ mapped[1];
+ // @ts-expect-error
+ mapped[2];
+});
+
+doNotExecute(async () => {
+ const arr: readonly number[] = [0, 1];
+ const mapped = arr.map(
+ (
+ value: (typeof arr)[number],
+ index: number,
+ source: readonly (typeof arr)[number][],
+ ) => 1,
+ );
+
+ arr.map(() => 1, {});
+
+ type tests = [Expect>];
+});
+
+doNotExecute(async () => {
+ const arr: number[] = [0, 1];
+ const mapped = arr.map(
+ (
+ value: (typeof arr)[number],
+ index: number,
+ source: (typeof arr)[number][],
+ ) => 1,
+ );
+
+ arr.map(() => 1, {});
+
+ type tests = [Expect>];
+});