Skip to content

Commit f37cf1a

Browse files
committed
Support recursive ReadonlyArray
1 parent 8de93a1 commit f37cf1a

File tree

2 files changed

+59
-13
lines changed

2 files changed

+59
-13
lines changed

src/index.spec.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,59 @@
11
import { expectType } from "ts-expect";
22
import { flatten } from "./index";
33

4-
describe("flatten", function() {
5-
it("should flatten an array", function() {
4+
describe("flatten", () => {
5+
it("should flatten an array", () => {
66
const result = flatten([1, [2, [3, [4, [5]]], 6, [[7], 8], 9], 10]);
77

88
expectType<number[]>(result);
99

1010
expect(result).toStrictEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1111
});
1212

13-
it("should work with array-like", function() {
13+
it("should work with array-like", () => {
1414
const result = flatten("test");
1515

1616
expectType<string[]>(result);
1717

1818
expect(result).toStrictEqual(["t", "e", "s", "t"]);
1919
});
2020

21-
it("should work with arguments", function() {
22-
const result = flatten(arguments);
21+
it("should work with readonly array", () => {
22+
const input = [1, [2, [3, [4]]]] as const;
23+
const result = flatten(input);
24+
25+
expectType<(1 | 2 | 3 | 4)[]>(result);
26+
27+
expect(result).toStrictEqual([1, 2, 3, 4]);
28+
});
29+
30+
it("should work with arguments", () => {
31+
const input = (function() {
32+
return arguments;
33+
})();
34+
const result = flatten(input);
2335

2436
expectType<any[]>(result);
2537

2638
expect(result).toStrictEqual([]);
2739
});
40+
41+
it("should work with mixed types", () => {
42+
const fn = (x: string) => x;
43+
const input = [1, ["test", [fn, [true]]]];
44+
const result = flatten(input);
45+
46+
expectType<(number | string | boolean | typeof fn)[]>(result);
47+
48+
expect(result).toStrictEqual([1, "test", fn, true]);
49+
});
50+
51+
it("should work with tuples", () => {
52+
const input: [number, [number, number], [number]] = [1, [1, 2], [3]];
53+
const result = flatten(input);
54+
55+
expectType<number[]>(result);
56+
57+
expect(result).toStrictEqual([1, 1, 2, 3]);
58+
});
2859
});

src/index.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
/**
2-
* Recursive value or array type.
2+
* Pick the value from an array.
33
*/
4-
export type ValueOrArray<T> = T | Array<ValueOrArray<T>>;
4+
export type PickValue<T> = T extends ReadonlyArray<any>
5+
? {
6+
[K in Extract<keyof T, number>]: PickValue<T[K]>;
7+
}[number]
8+
: T;
9+
10+
/**
11+
* Flatten an `ArrayLike` object in TypeScript.
12+
*/
13+
export type FlatArray<T extends ArrayLike<any>> = Array<PickValue<T[number]>>;
514

615
/**
716
* Flatten an array indefinitely.
817
*/
9-
export function flatten<T>(array: ArrayLike<ValueOrArray<T>>): T[] {
10-
return $flatten(array, []);
18+
export function flatten<T extends ArrayLike<any>>(array: T): FlatArray<T> {
19+
const result: FlatArray<T> = [];
20+
$flatten<T>(array, result);
21+
return result;
1122
}
1223

13-
function $flatten<T>(array: ArrayLike<ValueOrArray<T>>, result: T[]): T[] {
24+
/**
25+
* Internal flatten function recursively passes `result`.
26+
*/
27+
function $flatten<T extends ArrayLike<any>>(
28+
array: T,
29+
result: FlatArray<T>
30+
): void {
1431
for (let i = 0; i < array.length; i++) {
1532
const value = array[i];
1633

1734
if (Array.isArray(value)) {
18-
$flatten(value, result);
35+
$flatten(value as any, result);
1936
} else {
2037
result.push(value);
2138
}
2239
}
23-
24-
return result;
2540
}

0 commit comments

Comments
 (0)