From 91211c9b596cc6c04ceb2c3a7230d2cef784245a Mon Sep 17 00:00:00 2001 From: unional Date: Wed, 5 Jul 2023 00:33:00 -0700 Subject: [PATCH] fix: notMatch and unionNotMatch --- .changeset/odd-kings-turn.md | 9 ++++ .../ts/array/array_plus.element_match.ts | 31 ++++++------ type-plus/ts/array/array_plus.find.spec.ts | 27 +++++----- type-plus/ts/array/array_plus.find.ts | 12 +++-- type-plus/ts/array/readme.md | 49 ++++++++++++++----- type-plus/ts/assertion/assert_type.ts | 5 +- type-plus/ts/assertion/readme.md | 5 +- type-plus/ts/tuple/readme.md | 12 ++--- type-plus/ts/tuple/tuple_plus.find.spec.ts | 13 +++-- type-plus/ts/tuple/tuple_plus.find.ts | 16 +++--- 10 files changed, 118 insertions(+), 61 deletions(-) create mode 100644 .changeset/odd-kings-turn.md diff --git a/.changeset/odd-kings-turn.md b/.changeset/odd-kings-turn.md new file mode 100644 index 0000000000..50b43e895a --- /dev/null +++ b/.changeset/odd-kings-turn.md @@ -0,0 +1,9 @@ +--- +"type-plus": patch +--- + +Rename `caseNoMatch` to `caseNotMatch`. +Rename `caseUnionMiss` to `caseUnionNotMatch`. + +Change `caseUnionNotMatch` default from `undefined` to `never`, +making it defaults to the type behavior instead of JavaScript behavior. diff --git a/type-plus/ts/array/array_plus.element_match.ts b/type-plus/ts/array/array_plus.element_match.ts index 9fde14a37c..63771f02a4 100644 --- a/type-plus/ts/array/array_plus.element_match.ts +++ b/type-plus/ts/array/array_plus.element_match.ts @@ -4,24 +4,27 @@ import type { MergeOptions } from '../utils/options.js' /** * 🦴 *utilities* - * ㊙️ *internal* * 🔢 *customizable* * - * Match element in an array or tuple. + * Filter the element `T` in an array or tuple to match `Criteria`. * * @typeParam Options['widen'] Allow using narrow type to match widen type. * e.g. `number, 1` -> `1 | undefined`. * Default to `true`. * - * @typeParam Options['caseNoMatch'] Return value when `T` does not match `Criteria`. + * @typeParam Options['caseNotMatch'] Return value when `T` does not match `Criteria`. * Default to `never`. * * @typeParam Options['caseWiden'] Return value when `widen` is true. * Default to `Criteria | undefined`. * - * @typeParam Options['caseUnionMiss'] Return value when a branch of the union `T` does not match `Criteria`. - * Default to `undefined`. - * Since it is a union, the result will be join to the matched branch as union. + * @typeParam Options['caseUnionNotMatch'] Return value when a branch of the union `T` does not match `Criteria`. + * Default to `never`. + * + * If you want the type to behave more like JavaScript, + * you can override it to return `undefined`. + * + * Since it is a union, the result will be joined to the matched branch as union. * e.g. `ElementMatch<1 | 2, 1>` -> `1 | undefined` */ export type ElementMatch< @@ -36,23 +39,23 @@ export type ElementMatch< : (C['widen'] extends true ? (Criteria extends T ? C['caseWiden'] - : C['caseNoMatch']) - : C['caseNoMatch'])) extends infer R - ? IsUnion, R> - : C['caseNoMatch']) + : C['caseNotMatch']) + : C['caseNotMatch'])) extends infer R + ? IsUnion, R> + : C['caseNotMatch']) : never) export namespace ElementMatch { export interface Options { widen?: boolean | undefined, - caseNoMatch?: unknown, + caseNotMatch?: unknown, caseWiden?: unknown, - caseUnionMiss?: unknown + caseUnionNotMatch?: unknown } export interface DefaultOptions { widen: true, - caseNoMatch: never, + caseNotMatch: never, caseWiden: Criteria | undefined, - caseUnionMiss: undefined + caseUnionNotMatch: never } } diff --git a/type-plus/ts/array/array_plus.find.spec.ts b/type-plus/ts/array/array_plus.find.spec.ts index 8307317eb7..9373880035 100644 --- a/type-plus/ts/array/array_plus.find.spec.ts +++ b/type-plus/ts/array/array_plus.find.spec.ts @@ -20,7 +20,7 @@ it('returns never if the type in the array does not satisfy the criteria', () => }) it('can override no_match case', () => { - testType.equal, 'a'>(true) + testType.equal, 'a'>(true) }) it('returns T if T satisfies the Criteria', () => { @@ -48,29 +48,34 @@ it('returns never if the union type does not satisfy the Criteria', () => { testType.equal, boolean>, never>(true) }) -it('returns T | undefined for T[] if T is a union satisfies the Criteria', () => { +it('returns Criteria if T is a union partially satisfies the Criteria', () => { + testType.equal, number>, number>(true) + testType.equal, number>, 1 | 2>(true) +}) + +it('can override unionNotMach to `undefined`', () => { // adding `undefined` to the result better match the behavior in JavaScript, // as an array of `Array` can contains only `string` or `number`. // so `Find, string>` returns `string | undefined`. - testType.equal, number>, number | undefined>(true) - testType.equal, number>, 1 | 2 | undefined>(true) + testType.equal, number, { caseUnionNotMatch: undefined }>, number | undefined>(true) + testType.equal, number, { caseUnionNotMatch: undefined }>, 1 | 2 | undefined>(true) }) -it('handles union_miss and widen cases separately', () => { +it('handles union not match and widen cases separately', () => { testType.equal, 1, { caseWiden: 234, - caseUnionMiss: 123 + caseUnionNotMatch: 123 }>, 123 | 234>(true) }) it('can override the union_miss case', () => { - testType.equal, number, { caseUnionMiss: never }>, number>(true) + testType.equal, number, { caseUnionNotMatch: never }>, number>(true) }) it('will not affect other cases', () => { - testType.equal, number, { caseNever: 123 }>, number | ArrayPlus.Find.DefaultOptions['caseUnionMiss']>(true) - testType.equal, ArrayPlus.Find.DefaultOptions['caseNever']>(true) - testType.equal, ArrayPlus.Find.DefaultOptions['caseNoMatch']>(true) + testType.equal, number, { caseNever: 123 }>, number | ArrayPlus.Find.DefaultOptions['caseUnionNotMatch']>(true) + testType.equal, ArrayPlus.Find.DefaultOptions['caseNever']>(true) + testType.equal, ArrayPlus.Find.DefaultOptions['caseNotMatch']>(true) testType.equal, ArrayPlus.Find.DefaultOptions['caseTuple']>(true) - testType.equal, ArrayPlus.Find.DefaultOptions<1>['caseWiden']>(true) + testType.equal, ArrayPlus.Find.DefaultOptions<1>['caseWiden']>(true) }) diff --git a/type-plus/ts/array/array_plus.find.ts b/type-plus/ts/array/array_plus.find.ts index 0fa0faebee..e48171c8f1 100644 --- a/type-plus/ts/array/array_plus.find.ts +++ b/type-plus/ts/array/array_plus.find.ts @@ -29,7 +29,7 @@ import type { ElementMatch } from './array_plus.element_match.js' * * @typeParam Options['caseNever'] return type when `A` is `never`. Default to `never`. * - * @typeParam Options['caseNoMatch'] Return value when `T` does not match `Criteria`. + * @typeParam Options['caseNotMatch'] Return value when `T` does not match `Criteria`. * Default to `never`. * * @typeParam Options['caseTuple'] return type when `A` is a tuple. Default to `not supported` message. @@ -38,9 +38,13 @@ import type { ElementMatch } from './array_plus.element_match.js' * Default to `Criteria | undefined`. * Set it to `never` for a more type-centric behavior * - * @typeParam Options['caseUnionMiss'] Return value when a branch of the union `T` does not match `Criteria`. - * Default to `undefined`. - * Since it is a union, the result will be join to the matched branch as union. + * @typeParam Options['caseUnionNotMatch'] Return value when a branch of the union `T` does not match `Criteria`. + * Default to `never`. + * + * If you want the type to behave more like JavaScript, + * you can override it to return `undefined`. + * + * Since it is a union, the result will be joined to the matched branch as union. */ export type Find< A extends unknown[], diff --git a/type-plus/ts/array/readme.md b/type-plus/ts/array/readme.md index 1c13b467c1..455493d7a1 100644 --- a/type-plus/ts/array/readme.md +++ b/type-plus/ts/array/readme.md @@ -142,23 +142,23 @@ import type { FindFirst } from 'type-plus' type R = FindFirst<[true, 1, 'x', 3], string> // 'x' type R = FindFirst<[true, 1, 'x', 3], number> // 1 type R = FindFirst<[string, number, 1], 1> // widen: 1 | undefined -type R = FindFirst<[true, number | string], string> // unionMiss: string | undefined +type R = FindFirst<[true, number | string], string> // unionNotMatch: string type R = FindFirst, string> // string type R = FindFirst, number> // 1 | 2 | undefined type R = FindFirst, number | string> // string | number type R = FindFirst, 1> // widen: 1 | undefined -type R = FindFirst, number> // unionMiss: number | undefined +type R = FindFirst, number> // unionNotMatch: number type R = FindFirst<[true, 1, 'x'], 2> // never type R = FindFirst // never // customization type R = FindFirst<[number], 1, { widen: false }> // never +type R = FindFirst<[number], 1, { caseWiden: never }> // never type R = FindFirst<[], 1, { caseEmptyTuple: 2 }> // 2 type R = FindFirst // 2 -type R = FindFirst<[string], number, { caseNoMatch: 2 }> // 2 -type R = FindFirst<[number], 1, { caseWiden: never }> // never -type R = FindFirst<[string | number], number, { caseUnionMiss: never }> // number +type R = FindFirst<[string], number, { caseNotMatch: 2 }> // 2 +type R = FindFirst<[string | number], number, { caseUnionNotMatch: undefined }> // number | undefined ``` ## [`FindLast`](./array.find_last.ts) @@ -292,12 +292,37 @@ type R = ArrayPlus.CommonPropKeys> // 'a' type R = ArrayPlus.CommonPropKeys // 1 ``` -### [`ArrayPlus.Concat`](./array.concat.ts#L12) +### [`ArrayPlus.Concat`](./array.concat.ts#l12) `ArrayPlus.Concat` Alias of [Concat](#concat). +### [`ArrayPlus.ElementMatch`](./array_plus.element_match.ts#l30) + +`ArrayPlus.ElementMatch` + +🌪️ *filter* +🔢 *customizable* + +Filter the element `T` in an array or tuple to match `Criteria`. + +```ts +import type { ArrayPlus } from 'type-plus' + +type R = ArrayPlus.ElementMatch // number +type R = ArrayPlus.ElementMatch<1, number> // 1 +type R = ArrayPlus.ElementMatch // notMatch: never +type R = ArrayPlus.ElementMatch // widen: 1 +type R = ArrayPlus.ElementMatch // unionNotMatch: number + +// customization +type R = ArrayPlus.ElementMatch // 1 +type R = ArrayPlus.ElementMatch // never +type R = ArrayPlus.ElementMatch // never +type R = ArrayPlus.ElementMatch // number | undefined +``` + ### [`ArrayPlus.Entries`](./array.entries.ts#L14) > `ArrayPlus.Entries` @@ -312,9 +337,9 @@ type R = ArrayPlus.Entries> // Array<[number, string | nu type R = ArrayPlus.Entries<[1, 2, 3]> // [[0, 1], [1, 2], [2, 3]] ``` -### [`ArrayPlus.Find`](./array_plus.find.ts#l45) +### [`ArrayPlus.Find`](./array_plus.find.ts#l49) -`ArrayPlus.Find` +`ArrayPlus.Find` 🦴 *utilities* 🔢 *customizable* @@ -328,17 +353,17 @@ type R = ArrayPlus.Find, string> // string type R = ArrayPlus.Find, number> // 1 | 2 | undefined type R = ArrayPlus.Find, number | string> // string | number type R = ArrayPlus.Find // widen: 1 | undefined -type R = ArrayPlus.Find, number> // union_miss: number | undefined +type R = ArrayPlus.Find, number> // unionNotMatch: number type R = ArrayPlus.Find // never // customization type R = ArrayPlus.Find // never +type R = ArrayPlus.Find // never type R = ArrayPlus.Find // 2 -type R = ArrayPlus.Find // 2 +type R = ArrayPlus.Find // 2 type R = ArrayPlus.Find<[], 1, { caseTuple: 2 }> // 2 -type R = ArrayPlus.Find // never -type R = ArrayPlus.Find, number, { caseUnionMiss: never }> // number +type R = ArrayPlus.Find, number, { caseUnionNotMatch: undefined }> // number | undefined ``` ### [`ArrayPlus.FindLast`](./array.find_last.ts#L17) diff --git a/type-plus/ts/assertion/assert_type.ts b/type-plus/ts/assertion/assert_type.ts index 8d0dae3280..578b21d4d7 100644 --- a/type-plus/ts/assertion/assert_type.ts +++ b/type-plus/ts/assertion/assert_type.ts @@ -3,7 +3,10 @@ import { isConstructor, type AnyConstructor } from '../class/index.js' import { type AnyFunction } from '../function/any_function.js' /** - * assert the subject satisfies the specified type T + * 💥 *immediate* + * 🚦 *assertion* + * + * Assert the subject satisfies the specified type T * @type T the type to check against. */ export function assertType(subject: T): asserts subject is T diff --git a/type-plus/ts/assertion/readme.md b/type-plus/ts/assertion/readme.md index 034e34dd10..3a0065c477 100644 --- a/type-plus/ts/assertion/readme.md +++ b/type-plus/ts/assertion/readme.md @@ -9,11 +9,12 @@ 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](./assert_type.ts) +## [assertType](./assert_type.ts#l10) `assertType(subject)` -💥 `immediate` +💥 *immediate* +🚦 *assertion* 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 dd6684d113..149f59a393 100644 --- a/type-plus/ts/tuple/readme.md +++ b/type-plus/ts/tuple/readme.md @@ -225,9 +225,9 @@ import { TuplePlus } from 'type-plus' type R = TuplePlus.Filter<[1, 2, '3'], number> // [1, 2] ``` -### [`TuplePlus.Find`](./tuple_plus.find.ts#l47) +### [`TuplePlus.Find`](./tuple_plus.find.ts#l51) -`TuplePlus.Find` +`TuplePlus.Find` 🦴 *utilities* 🔢 *customizable* @@ -240,18 +240,18 @@ import type { TuplePlus } from 'type-plus' type R = TuplePlus.Find<[true, 1, 'x', 3], string> // 'x' type R = TuplePlus.Find<[true, 1, 'x', 3], number> // 1 type R = TuplePlus.Find<[string, number, 1], 1> // widen: 1 | undefined -type R = TuplePlus.Find<[true, number | string], string> // unionMiss: string | undefined +type R = TuplePlus.Find<[true, number | string], string> // unionNotMatch: string type R = TuplePlus.Find<[true, 1, 'x'], 2> // never // customization type R = TuplePlus.Find<[number], 1, { widen: false }> // never +type R = TuplePlus.Find<[number], 1, { caseWiden: never }> // never type R = TuplePlus.Find // 2 type R = TuplePlus.Find<[], 1, { caseEmptyTuple: 2 }> // 2 type R = TuplePlus.Find // 2 -type R = TuplePlus.Find<[string], number, { caseNoMatch: 2 }> // 2 -type R = TuplePlus.Find<[number], 1, { caseWiden: never }> // never -type R = TuplePlus.Find<[string | number], number, { caseUnionMiss: never }> // number +type R = TuplePlus.Find<[string], number, { caseNotMatch: 2 }> // 2 +type R = TuplePlus.Find<[string | number], number, { caseUnionNotMatch: undefined }> // number | undefined ``` ### [TuplePlus.PadStart](./tuple_plus.pad_start.ts) diff --git a/type-plus/ts/tuple/tuple_plus.find.spec.ts b/type-plus/ts/tuple/tuple_plus.find.spec.ts index 862ac07d33..61e0620c7c 100644 --- a/type-plus/ts/tuple/tuple_plus.find.spec.ts +++ b/type-plus/ts/tuple/tuple_plus.find.spec.ts @@ -36,7 +36,7 @@ it('no match gets never', () => { }) it('can override no_match case', () => { - testType.equal, 1>(true) + testType.equal, 1>(true) }) it('pick first type matching criteria', () => { @@ -66,12 +66,15 @@ it('can disable widen', () => { testType.equal, never>(true) }) -it('returns T | undefined for element T if T is a union satisfies the Criteria', () => { - testType.equal, number | undefined>(true) +it('returns Criteria if T is a union partially satisfies the Criteria', () => { + testType.equal, number>(true) }) -it('can override the union_miss case', () => { - testType.equal, number>(true) +it('can return T | undefined by overriding unionNotMach to `undefined`', () => { + // adding `undefined` to the result better match the behavior in JavaScript, + // as an array of `Array` can contains only `string` or `number`. + // so `Find, string>` returns `string | undefined`. + testType.equal, number | undefined>(true) }) it('pick object', () => { diff --git a/type-plus/ts/tuple/tuple_plus.find.ts b/type-plus/ts/tuple/tuple_plus.find.ts index f01bb759a6..8aa570ec59 100644 --- a/type-plus/ts/tuple/tuple_plus.find.ts +++ b/type-plus/ts/tuple/tuple_plus.find.ts @@ -33,16 +33,20 @@ import type { TupleType } from './tuple_type.js' * * @typeParam Options['caseNever'] return type when `A` is `never`. Default to `never`. * - * @typeParam Options['caseNoMatch'] Return value when `T` does not match `Criteria`. + * @typeParam Options['caseNotMatch'] Return value when `T` does not match `Criteria`. * Default to `never`. * * @typeParam Options['caseWiden'] return type when `T` in `A` is a widen type of `Criteria`. * Default to `Criteria | undefined`. * Set it to `never` for a more type-centric behavior * - * @typeParam Options['caseUnionMiss'] Return value when a branch of the union `T` does not match `Criteria`. - * Default to `undefined`. - * Since it is a union, the result will be join to the matched branch as union. + * @typeParam Options['caseUnionNotMatch'] Return value when a branch of the union `T` does not match `Criteria`. + * Default to `never`. + * + * If you want the type to behave more like JavaScript, + * you can override it to return `undefined`. + * + * Since it is a union, the result will be joined to the matched branch as union. */ export type Find< A extends unknown[], @@ -64,12 +68,12 @@ export namespace Find { Criteria, Options extends Find.Options > = A['length'] extends 0 - ? Options['caseNoMatch'] + ? Options['caseNotMatch'] : (A extends [infer Head, ...infer Tail] ? ElementMatch< Head, Criteria, - MergeOptions<{ caseNoMatch: Device }, Options> + MergeOptions<{ caseNotMatch: Device }, Options> > : never) export interface Options extends ElementMatch.Options, NeverType.Options {