From 81158731b05112a5996d2e86df441094039328a6 Mon Sep 17 00:00:00 2001 From: unional Date: Sun, 2 Jul 2023 11:45:01 -0700 Subject: [PATCH] fix: separate PadStart and Filter --- .changeset/cuddly-students-listen.md | 5 ++ type-plus/ts/array/array_plus.filter.ts | 5 ++ type-plus/ts/array/array_plus.pad_start.ts | 13 ++++ type-plus/ts/array/array_plus.ts | 3 +- type-plus/ts/array/filter.spec.ts | 69 +++++++++---------- type-plus/ts/array/filter.ts | 28 +++----- type-plus/ts/array/pad_start.ts | 16 ++--- type-plus/ts/assertion/readme.md | 6 +- type-plus/ts/tuple/readme.md | 40 +++++++++++ type-plus/ts/tuple/tuple_plus.filter.ts | 29 ++++++++ .../ts/tuple/tuple_plus.pad_start.spec.ts | 9 ++- type-plus/ts/tuple/tuple_plus.pad_start.ts | 39 +++++++---- type-plus/ts/tuple/tuple_plus.ts | 3 +- 13 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 .changeset/cuddly-students-listen.md create mode 100644 type-plus/ts/array/array_plus.filter.ts create mode 100644 type-plus/ts/array/array_plus.pad_start.ts create mode 100644 type-plus/ts/tuple/tuple_plus.filter.ts diff --git a/.changeset/cuddly-students-listen.md b/.changeset/cuddly-students-listen.md new file mode 100644 index 0000000000..82ccf9819f --- /dev/null +++ b/.changeset/cuddly-students-listen.md @@ -0,0 +1,5 @@ +--- +"type-plus": patch +--- + +Separate `Filter` and `PadStart` for array and tuple diff --git a/type-plus/ts/array/array_plus.filter.ts b/type-plus/ts/array/array_plus.filter.ts new file mode 100644 index 0000000000..da8a3ec157 --- /dev/null +++ b/type-plus/ts/array/array_plus.filter.ts @@ -0,0 +1,5 @@ +export type Filter = A[0] extends Criteria + ? A + : Criteria extends A[0] + ? Array + : never[] diff --git a/type-plus/ts/array/array_plus.pad_start.ts b/type-plus/ts/array/array_plus.pad_start.ts new file mode 100644 index 0000000000..0b134fcaa4 --- /dev/null +++ b/type-plus/ts/array/array_plus.pad_start.ts @@ -0,0 +1,13 @@ +import type { CanAssign } from '../index.js' +import type { CreateTuple } from '../tuple/create_tuple.js' +import type { UnionOfValues } from './union_of_values.js' + +export type PadStart< + A extends unknown[], + MaxLength extends number, + PadWith = unknown +> = MaxLength extends 0 + ? A + : CanAssign> extends true + ? A + : PadStart<[...CreateTuple, ...A], MaxLength, PadWith> diff --git a/type-plus/ts/array/array_plus.ts b/type-plus/ts/array/array_plus.ts index a865a78562..1f7fcac90f 100644 --- a/type-plus/ts/array/array_plus.ts +++ b/type-plus/ts/array/array_plus.ts @@ -6,5 +6,6 @@ export type { FindLast } from './array.find_last.js' export type { Reverse } from './array.reverse.js' export type { Some } from './array.some.js' export type { IndexAt, IsIndexOutOfBound } from './array_index.js' +export type { Filter } from './array_plus.filter.js' export type { SplitAt } from './array_plus.split_at.js' -export type { PadStart } from './pad_start.js' +export type { PadStart } from './array_plus.pad_start.js' diff --git a/type-plus/ts/array/filter.spec.ts b/type-plus/ts/array/filter.spec.ts index 23b421b48e..3d914530e9 100644 --- a/type-plus/ts/array/filter.spec.ts +++ b/type-plus/ts/array/filter.spec.ts @@ -1,53 +1,50 @@ -import { describe, test } from '@jest/globals' +import { describe, it } from '@jest/globals' import { testType, type Filter, type KeepMatch } from '../index.js' describe('Filter', () => { describe('A is array', () => { - test('array matching criteria gets itself', () => { - type Actual = Filter - testType.equal(true) + it('returns A when criteria matches the type of the entries', () => { + testType.equal, string[]>(true) }) - test('array not matching criteria gets never[]', () => { - type Actual = Filter - testType.equal(true) + it('returns never[] when criteria does not match the type of the entries', () => { + testType.equal, never[]>(true) }) - test('remove unmatched type form array', () => { - type Actual = Filter, string> - - testType.equal(true) + it('removes unmatched type form array', () => { + testType.equal, string>, string[]>(true) }) - test('remove undefined and null', () => { - type Actual = Filter, string> - testType.equal(true) + it('removes unmatched undefined and null as expected', () => { + testType.equal, string>, string[]>(true) }) - test('can filter with undefined and null', () => { - type Actual = Filter, undefined | null> + it('can filter with undefined and null', () => { // Array is destructured to undefined[] | null[] by TypeScript - testType.equal(true) + testType.equal, undefined | null>, undefined[] | null[]>(true) }) - test('work with never[]', () => { - type Actual = Filter - testType.equal(true) + it('work with never[]', () => { + testType.equal, never[]>(true) }) }) describe(`A is Tuple`, () => { - test('matching criteria', () => { - type Actual = Filter<[1, 2, 3, 4], 2 | 4> - testType.equal<[2, 4], Actual>(true) + it('matching criteria', () => { + testType.equal, [2, 4]>(true) + testType.equal, [1, 2]>(true) }) - test('no match gets never[]', () => { + it('no match gets []', () => { type Actual = Filter<[1, 2, 3, 4], 5> - testType.equal(true) + testType.equal<[], Actual>(true) }) - test('matching undefined and null', () => { + it('empty tuple gets empty tuple', () => { + testType.equal, []>(true) + }) + + it('matching undefined and null', () => { type Actual = Filter<[1, undefined, 3, null], undefined | null> testType.equal<[undefined, null], Actual>(true) }) @@ -56,51 +53,51 @@ describe('Filter', () => { describe('KeepMatch', () => { describe('A is array', () => { - test('array matching criteria gets itself', () => { + it('array matching criteria gets itself', () => { type Actual = KeepMatch testType.equal(true) }) - test('array not matching criteria gets never[]', () => { + it('array not matching criteria gets never[]', () => { type Actual = KeepMatch testType.equal(true) }) - test('remove unmatched type form array', () => { + it('remove unmatched type form array', () => { type Actual = KeepMatch, string> testType.equal(true) }) - test('remove undefined and null', () => { + it('remove undefined and null', () => { type Actual = KeepMatch, string> testType.equal(true) }) - test('can filter with undefined and null', () => { + it('can filter with undefined and null', () => { type Actual = KeepMatch, undefined | null> // Array is destructured to undefined[] | null[] by TypeScript testType.equal(true) }) - test('work with never[]', () => { + it('work with never[]', () => { type Actual = KeepMatch testType.equal(true) }) }) describe(`A is Tuple`, () => { - test('matching criteria', () => { + it('matching criteria', () => { type Actual = KeepMatch<[1, 2, 3, 4], 2 | 4> testType.equal<[2, 4], Actual>(true) }) - test('no match gets never[]', () => { + it('no match gets []', () => { type Actual = KeepMatch<[1, 2, 3, 4], 5> - testType.equal(true) + testType.equal<[], Actual>(true) }) - test('matching undefined and null', () => { + it('matching undefined and null', () => { type Actual = KeepMatch<[1, undefined, 3, null], undefined | null> testType.equal<[undefined, null], Actual>(true) }) diff --git a/type-plus/ts/array/filter.ts b/type-plus/ts/array/filter.ts index 594b5ce0f8..f5ef392cb9 100644 --- a/type-plus/ts/array/filter.ts +++ b/type-plus/ts/array/filter.ts @@ -1,27 +1,15 @@ +import type { Filter as FilterTuple } from '../tuple/tuple_plus.filter.js' +import type { Filter as FilterArray } from './array_plus.filter.js' + /** * filter the array or tuple `A`, keeping entries satisfying `Criteria`. */ -export type Filter, Criteria> = number extends A['length'] - ? // array - A[0] extends Criteria - ? A - : Criteria extends A[0] - ? Array - : never[] - : // tuple - A['length'] extends 0 - ? never - : A extends [infer Head, ...infer Tail] - ? Tail['length'] extends 0 - ? Head extends Criteria - ? [Head] - : never[] - : Head extends Criteria - ? [Head, ...Filter] - : Filter - : never +export type Filter = number extends A['length'] + ? FilterArray + : FilterTuple + /** * keeps entries satisfying `Criteria` in array or tuple `A`. */ -export type KeepMatch, Criteria> = Filter +export type KeepMatch = Filter diff --git a/type-plus/ts/array/pad_start.ts b/type-plus/ts/array/pad_start.ts index 33d6269de3..22887cd607 100644 --- a/type-plus/ts/array/pad_start.ts +++ b/type-plus/ts/array/pad_start.ts @@ -1,11 +1,11 @@ -import type { CanAssign } from '../index.js' -import type { CreateTuple } from '../tuple/create_tuple.js' -import type { PadStart as TuplePadStart } from '../tuple/tuple_plus.pad_start.js' -import type { UnionOfValues } from './union_of_values.js' +import type { PadStart as PadStartTuple } from '../tuple/tuple_plus.pad_start.js' +import type { PadStart as PadStartArray } from './array_plus.pad_start.js' /** * Pads the start of an array or tuple with `PadWith`. * + * ⚗️ *transform* + * * @example * ```ts * // Padding array @@ -30,12 +30,8 @@ export type PadStart< MaxLength extends number, PadWith = unknown > = number extends A['length'] - ? MaxLength extends 0 - ? A - : CanAssign> extends true - ? A - : PadStart<[...CreateTuple, ...A], MaxLength, PadWith> - : TuplePadStart + ? PadStartArray + : PadStartTuple /** * @deprecated use PadStart instead diff --git a/type-plus/ts/assertion/readme.md b/type-plus/ts/assertion/readme.md index a4381e1ed0..034e34dd10 100644 --- a/type-plus/ts/assertion/readme.md +++ b/type-plus/ts/assertion/readme.md @@ -9,11 +9,11 @@ They throw an error if the condition is not met, and return nothing otherwise. These assertion functions are typically used in runtime, so that that type of the value can be narrowed down. -## assertType +## [assertType](./assert_type.ts) -`assertType(subject)`: +`assertType(subject)` -✔️ `immediate` +💥 `immediate` It ensures `subject` satisfies `T`. It is similar to `const x: T = subject` without introducing an unused variable. diff --git a/type-plus/ts/tuple/readme.md b/type-plus/ts/tuple/readme.md index 68de0a0c62..40ba92ba1c 100644 --- a/type-plus/ts/tuple/readme.md +++ b/type-plus/ts/tuple/readme.md @@ -96,6 +96,46 @@ type R = DropMatch, string> // never[] type R = DropMatch, number> // never[] ``` +## [TuplePlus](./tuple_plus.ts) + +`TuplePlus` contains type utilities specific for *tuple*. +The input type are not checked and assumed to be *tuple*. + +### [TuplePlus.Filter](./tuple_plus.filter.ts) + +`TuplePlus.Filter` + +Filter entries matching `Criteria` in tuple `T`. + +⚗️ *transform* + +```ts +import { TuplePlus } from 'type-plus' + +type R = TuplePlus.Filter<[1, 2, '3'], number> // [1, 2] +``` + +### [TuplePlus.PadStart](./tuple_plus.pad_start.ts) + +`TuplePlus.PadStart` + +Pad `T` with `PadWith` at the start of the tuple. + +If the `MaxLength` is less than the length of the tuple, +the `Tuple` will be returned unchanged. + +⚗️ *transform* + +```ts +PadStart<[1, 2, 3], 5, 0> // [0, 0, 1, 2, 3] + +// Ignore if MaxLength is less than the length of the tuple +PadStart<[1, 2, 3], 2> // [1, 2, 3] + +// Default to unknown +PadStart<[1, 2, 3], 5> // [unknown, unknown, 1, 2, 3] +``` + ## References - [Handbook] diff --git a/type-plus/ts/tuple/tuple_plus.filter.ts b/type-plus/ts/tuple/tuple_plus.filter.ts new file mode 100644 index 0000000000..d304078387 --- /dev/null +++ b/type-plus/ts/tuple/tuple_plus.filter.ts @@ -0,0 +1,29 @@ +/** + * Filter entries matching `Criteria` in tuple `T`. + * + * ⚗️ *transform* + * + * @example + * ```ts + * type R = Filter<[1, 2, '3'], number> // [1, 2] + * type R = Filter<[1, 2, '3'], true> // [] + * ``` + */ +export type Filter = T['length'] extends 0 + ? [] + : ( + T extends [infer Head, ...infer Tail] + ? ( + Tail['length'] extends 0 + ? ( + Head extends Criteria + ? [Head] + : []) + : ( + Head extends Criteria + ? [Head, ...Filter] + : Filter + ) + ) + : never + ) diff --git a/type-plus/ts/tuple/tuple_plus.pad_start.spec.ts b/type-plus/ts/tuple/tuple_plus.pad_start.spec.ts index 211d17d626..9eba921e0e 100644 --- a/type-plus/ts/tuple/tuple_plus.pad_start.spec.ts +++ b/type-plus/ts/tuple/tuple_plus.pad_start.spec.ts @@ -1,15 +1,14 @@ import { it } from '@jest/globals' -import { testType } from '../index.js' -import type { PadStart } from './tuple_plus.js' +import { testType, type TuplePlus } from '../index.js' it('pads with unknown', () => { - testType.equal, [unknown, unknown, 1, 2, 3]>(true) + testType.equal, [unknown, unknown, 1, 2, 3]>(true) }) it('returns source type if total length is less than source length', () => { - testType.equal, [1, 2, 3]>(true) + testType.equal, [1, 2, 3]>(true) }) it('can specify what to pad with', () => { - testType.equal, [0, 0, 1, 2, 3]>(true) + testType.equal, [0, 0, 1, 2, 3]>(true) }) diff --git a/type-plus/ts/tuple/tuple_plus.pad_start.ts b/type-plus/ts/tuple/tuple_plus.pad_start.ts index 8a306d6f2c..a9a7f0c5e8 100644 --- a/type-plus/ts/tuple/tuple_plus.pad_start.ts +++ b/type-plus/ts/tuple/tuple_plus.pad_start.ts @@ -1,15 +1,17 @@ /** - * Pads the start of a tuple with `PadWith`. + * Pad `T` with `PadWith` at the start of the tuple. * * If the `MaxLength` is less than the length of the tuple, * the `Tuple` will be returned unchanged. * + * ⚗️ *transform* + * * @example * ```ts * PadStart<[1, 2, 3], 5, 0> // [0, 0, 1, 2, 3] * * // Ignore if MaxLength is less than the length of the tuple - * PadStart<[1, 2, 3], 5, 0> // [0, 0, 1, 2, 3] + * PadStart<[1, 2, 3], 2> // [1, 2, 3] * * // Default to unknown * PadStart<[1, 2, 3], 5> // [unknown, unknown, 1, 2, 3] @@ -27,16 +29,27 @@ type PadStartDevice< MaxLength extends number, PadWith, Result extends unknown[] -> = Result['length'] extends MaxLength - ? Source extends [] +> = + Result['length'] extends MaxLength + ? ( + Source extends [] ? Result - : Source extends [...infer Head, infer Tail] - ? [Tail, ...Result] extends infer R extends unknown[] - ? PadStartDevice + : ( + Source extends [...infer Head, infer Tail] + ? ( + [Tail, ...Result] extends infer R extends unknown[] + ? PadStartDevice + : never + ) : never - : never - : Source extends [] - ? PadStartDevice - : Source extends [...infer Head, infer Tail] - ? PadStartDevice - : Source + ) + ) + : ( + Source extends [] + ? PadStartDevice + : ( + Source extends [...infer Head, infer Tail] + ? PadStartDevice + : Source + ) + ) diff --git a/type-plus/ts/tuple/tuple_plus.ts b/type-plus/ts/tuple/tuple_plus.ts index 29d2c9de1e..51534d7d77 100644 --- a/type-plus/ts/tuple/tuple_plus.ts +++ b/type-plus/ts/tuple/tuple_plus.ts @@ -1 +1,2 @@ -export * from './tuple_plus.pad_start.js' +export type { Filter } from './tuple_plus.filter.js' +export type { PadStart } from './tuple_plus.pad_start.js'