Skip to content

Commit

Permalink
feat: rename case* to $*
Browse files Browse the repository at this point in the history
  • Loading branch information
unional committed Sep 11, 2023
1 parent ae6ab5f commit 2a0d791
Show file tree
Hide file tree
Showing 37 changed files with 431 additions and 154 deletions.
5 changes: 5 additions & 0 deletions .changeset/lucky-socks-walk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"type-plus": major
---

Rename `case*` to `$*` to make them easier to use.
2 changes: 1 addition & 1 deletion type-plus/ts/array/array_plus.common_prop_keys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ it('never returns never', () => {
})

it('can override never case', () => {
testType.equal<ArrayPlus.CommonPropKeys<never, { caseNever: 1 }>, 1>(true)
testType.equal<ArrayPlus.CommonPropKeys<never, { $never: 1 }>, 1>(true)
})

it('accepts readonly array', () => {
Expand Down
4 changes: 2 additions & 2 deletions type-plus/ts/array/array_plus.common_prop_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import type { KeyTypes } from '../object/KeyTypes.js'
* type R = ArrayPlus.CommonPropKeys<Array<{ a: 1, b: 1 } | { a: 1, c: 1 }>> // 'a'
* ```
*
* @typeParam Options['caseNever'] Return type when `T` is `never`.
* @typeParam Options['$never'] Return type when `T` is `never`.
* Default to `never`.
*/
export type CommonPropKeys<
A extends readonly Record<KeyTypes, unknown>[],
Options extends CommonPropKeys.Options = CommonPropKeys.DefaultOptions
> = NeverType<A,
Options['caseNever'],
Options['$never'],
A extends Readonly<Array<infer R extends Record<KeyTypes, unknown>>> ? keyof R : never
>

Expand Down
28 changes: 14 additions & 14 deletions type-plus/ts/array/array_plus.element_match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import type { TypePlusOptions } from '../utils/options.js'
* e.g. `number, 1` -> `1 | undefined`.
* Default to `true`.
*
* @typeParam Options['caseNotMatch'] Return value when `T` does not match `Criteria`.
* @typeParam Options['$notMatch'] Return value when `T` does not match `Criteria`.
* Default to `never`.
*
* @typeParam Options['caseWiden'] Return value when `widen` is true.
* @typeParam Options['$widen'] Return value when `widen` is true.
* Default to `Criteria | undefined`.
*
* @typeParam Options['caseUnionNotMatch'] Return value when a branch of the union `T` does not match `Criteria`.
* @typeParam Options['$unionNotMatch'] 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,
Expand All @@ -38,24 +38,24 @@ export type ElementMatch<
? T
: (C['widen'] extends true
? (Criteria extends T
? C['caseWiden']
: C['caseNotMatch'])
: C['caseNotMatch'])) extends infer R
? IsUnion<T, IsNever<R, R, R | C['caseUnionNotMatch']>, R>
: C['caseNotMatch'])
? C['$widen']
: C['$notMatch'])
: C['$notMatch'])) extends infer R
? IsUnion<T, IsNever<R, R, R | C['$unionNotMatch']>, R>
: C['$notMatch'])
: never)

export namespace ElementMatch {
export interface Options {
widen?: boolean | undefined,
caseNotMatch?: unknown,
caseWiden?: unknown,
caseUnionNotMatch?: unknown
$notMatch?: unknown,
$widen?: unknown,
$unionNotMatch?: unknown
}
export interface DefaultOptions<Criteria> {
widen: true,
caseNotMatch: never,
caseWiden: Criteria | undefined,
caseUnionNotMatch: never
$notMatch: never,
$widen: Criteria | undefined,
$unionNotMatch: never
}
}
10 changes: 10 additions & 0 deletions type-plus/ts/array/array_plus.filter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { it } from '@jest/globals'
import { testType, type ArrayPlus } from '../index.js'

it('filters empty tuple -> empty tuple', () => {
testType.equal<ArrayPlus.Filter<[]>, []>(true)
})

it('filters for true elements by default', () => {
testType.equal<ArrayPlus.Filter<[true, false, true]>, [true, true]>(true)
})
17 changes: 14 additions & 3 deletions type-plus/ts/array/array_plus.filter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
export type Filter<A extends readonly unknown[], Criteria> = A[0] extends Criteria
? A
: Criteria extends A[0] ? Array<Criteria> : never[]
import type { IsEqual } from '../equal/equal.js'

/**
* Filters an array or tuple based on criteria
*/
export type Filter<A extends readonly unknown[], Criteria = true> = Filter._<A, Criteria, []>

export namespace Filter {
export type _<A extends readonly unknown[], Criteria, Result extends unknown[]> = A['length'] extends 0
? Result
: (A extends [infer H, ...infer Rest]
? IsEqual<H, Criteria, _<Rest, Criteria, [...Result, H]>, _<Rest, Criteria, Result>>
: never)
}
26 changes: 13 additions & 13 deletions type-plus/ts/array/array_plus.find.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ it('returns never if input is never', () => {
})

it('can override the never case', () => {
testType.equal<ArrayPlus.Find<never, 1, { caseNever: 2 }>, 2>(true)
testType.equal<ArrayPlus.Find<never, 1, { $never: 2 }>, 2>(true)
})

it('returns never if the type in the array does not satisfy the criteria', () => {
testType.equal<ArrayPlus.Find<string[], number>, never>(true)
})

it('can override no_match case', () => {
testType.equal<ArrayPlus.Find<number[], string, { caseNotMatch: 'a' }>, 'a'>(true)
testType.equal<ArrayPlus.Find<number[], string, { $notMatch: 'a' }>, 'a'>(true)
})

it('returns T if T satisfies the Criteria', () => {
Expand All @@ -35,7 +35,7 @@ it('returns Criteria | undefined if T is a widen type of Criteria', () => {
})

it('can override widen case', () => {
testType.equal<ArrayPlus.Find<number[], 1, { caseWiden: never }>, never>(true)
testType.equal<ArrayPlus.Find<number[], 1, { $widen: never }>, never>(true)
})

it('does not support tuple', () => {
Expand All @@ -57,27 +57,27 @@ it('can override unionNotMach to `undefined`', () => {
// adding `undefined` to the result better match the behavior in JavaScript,
// as an array of `Array<string | number>` can contains only `string` or `number`.
// so `Find<Array<string | number>, string>` returns `string | undefined`.
testType.equal<ArrayPlus.Find<Array<string | number>, number, { caseUnionNotMatch: undefined }>, number | undefined>(true)
testType.equal<ArrayPlus.Find<Array<1 | 2 | 'x'>, number, { caseUnionNotMatch: undefined }>, 1 | 2 | undefined>(true)
testType.equal<ArrayPlus.Find<Array<string | number>, number, { $unionNotMatch: undefined }>, number | undefined>(true)
testType.equal<ArrayPlus.Find<Array<1 | 2 | 'x'>, number, { $unionNotMatch: undefined }>, 1 | 2 | undefined>(true)
})

it('handles union not match and widen cases separately', () => {
testType.equal<ArrayPlus.Find<Array<string | number>, 1, {
caseWiden: 234,
caseUnionNotMatch: 123
$widen: 234,
$unionNotMatch: 123
}>, 123 | 234>(true)
})

it('can override the union_miss case', () => {
testType.equal<ArrayPlus.Find<Array<string | number>, number, { caseUnionNotMatch: never }>, number>(true)
testType.equal<ArrayPlus.Find<Array<string | number>, number, { $unionNotMatch: never }>, number>(true)
})

it('will not affect other cases', () => {
testType.equal<ArrayPlus.Find<Array<string | number>, number, { caseNever: 123 }>, number | ArrayPlus.Find.DefaultOptions<unknown>['caseUnionNotMatch']>(true)
testType.equal<ArrayPlus.Find<never, 1, { caseNotMatch: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['caseNever']>(true)
testType.equal<ArrayPlus.Find<number[], string, { caseTuple: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['caseNotMatch']>(true)
testType.equal<ArrayPlus.Find<[], 1, { caseWiden: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['caseTuple']>(true)
testType.equal<ArrayPlus.Find<number[], 1, { caseUnionNotMatch: 123 }>, ArrayPlus.Find.DefaultOptions<1>['caseWiden']>(true)
testType.equal<ArrayPlus.Find<Array<string | number>, number, { $never: 123 }>, number | ArrayPlus.Find.DefaultOptions<unknown>['$unionNotMatch']>(true)
testType.equal<ArrayPlus.Find<never, 1, { $notMatch: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['$never']>(true)
testType.equal<ArrayPlus.Find<number[], string, { $tuple: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['$notMatch']>(true)
testType.equal<ArrayPlus.Find<[], 1, { $widen: 123 }>, ArrayPlus.Find.DefaultOptions<unknown>['$tuple']>(true)
testType.equal<ArrayPlus.Find<number[], 1, { $unionNotMatch: 123 }>, ArrayPlus.Find.DefaultOptions<1>['$widen']>(true)
})

it('support readonly array', () => {
Expand Down
18 changes: 9 additions & 9 deletions type-plus/ts/array/array_plus.find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ import type { ElementMatch } from './array_plus.element_match.js'
* With widen match, a narrowed type will match its widen type.
* e.g. matching `1` against `number` yields `1 | undefined`
*
* The widen behavior can be customized by `Options['caseWiden']`
* The widen behavior can be customized by `Options['$widen']`
*
* @typeParam Options['caseNever'] return type when `A` is `never`. Default to `never`.
* @typeParam Options['$never'] return type when `A` is `never`. Default to `never`.
*
* @typeParam Options['caseNotMatch'] Return value when `T` does not match `Criteria`.
* @typeParam Options['$notMatch'] 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.
* @typeParam Options['$tuple'] return type when `A` is a tuple. Default to `not supported` message.
*
* @typeParam Options['caseWiden'] return type when `T` in `A` is a widen type of `Criteria`.
* @typeParam Options['$widen'] 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['caseUnionNotMatch'] Return value when a branch of the union `T` does not match `Criteria`.
* @typeParam Options['$unionNotMatch'] 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,
Expand All @@ -54,18 +54,18 @@ export type Find<
TypePlusOptions.Merge<Options, Find.DefaultOptions<Criteria>> extends infer O extends Find.Options
? TupleType<
A,
O['caseTuple'],
O['$tuple'],
A extends Readonly<Array<infer T>> ? ElementMatch<T, Criteria, O> : never,
O
>
: never

export namespace Find {
export interface Options extends ElementMatch.Options, NeverType.Options {
caseTuple?: unknown,
$tuple?: unknown,
}

export interface DefaultOptions<Criteria> extends ElementMatch.DefaultOptions<Criteria>, NeverType.DefaultOptions {
caseTuple: 'does not support tuple. Please use `FindFirst` or `TuplePlus.Find` instead.',
$tuple: 'does not support tuple. Please use `FindFirst` or `TuplePlus.Find` instead.',
}
}
4 changes: 2 additions & 2 deletions type-plus/ts/array/array_plus.is_readonly.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ it('detects regular tuple as not readonly', () => {
it('', () => {
testType.equal<ArrayPlus.IsReadonly<any>, false>(true)
testType.equal<ArrayPlus.IsReadonly<unknown>, false>(true)
testType.equal<ArrayPlus.IsReadonly<never, { caseNever: 'n' }>, 'n'>(true)
testType.equal<ArrayPlus.IsReadonly<never, { $never: 'n' }>, 'n'>(true)
testType.equal<ArrayPlus.IsReadonly<void>, false>(true)

})

it('detects non array case', () => {
testType.equal<ArrayPlus.IsReadonly<string, { caseNotArray: 'n' }>, 'n'>(true)
testType.equal<ArrayPlus.IsReadonly<string, { $notArray: 'n' }>, 'n'>(true)
})

it('distributes over union', () => {
Expand Down
16 changes: 8 additions & 8 deletions type-plus/ts/array/array_plus.is_readonly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,24 @@ export type IsReadonly<
TypePlusOptions.Merge<Options, IsReadonly.DefaultOptions> extends infer O extends IsReadonly.Options ?
NeverType<
A,
O['caseNever'],
O['$never'],
A extends any ?
LooseArrayType<A,
Readonly<A> extends A ? O['caseThen'] : O['caseElse'],
O['caseNotArray']
Readonly<A> extends A ? O['$then'] : O['$else'],
O['$notArray']
> : never
>
: never

export namespace IsReadonly {
export interface Options extends NeverType.Options, TypePlusOptions.Selection {
caseNotArray?: unknown
$notArray?: unknown
}

export interface DefaultOptions {
caseThen: true,
caseElse: false,
caseNever: false,
caseNotArray: false
$then: true,
$else: false,
$never: false,
$notArray: false
}
}
6 changes: 3 additions & 3 deletions type-plus/ts/array/find_first.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('For Array', () => {
})

it('can override widen case', () => {
testType.equal<FindFirst<number[], 1, { caseWiden: never }>, never>(true)
testType.equal<FindFirst<number[], 1, { $widen: never }>, never>(true)
})

it('returns Criteria if T is a union partially satisfies the Criteria', () => {
Expand All @@ -29,8 +29,8 @@ describe('For Array', () => {
// adding `undefined` to the result better match the behavior in JavaScript,
// as an array of `Array<string | number>` can contains only `string` or `number`.
// so `Find<Array<string | number>, string>` returns `string | undefined`.
testType.equal<FindFirst<Array<string | number>, number, { caseUnionNotMatch: undefined }>, number | undefined>(true)
testType.equal<FindFirst<Array<1 | 2 | 'x'>, number, { caseUnionNotMatch: undefined }>, 1 | 2 | undefined>(true)
testType.equal<FindFirst<Array<string | number>, number, { $unionNotMatch: undefined }>, number | undefined>(true)
testType.equal<FindFirst<Array<1 | 2 | 'x'>, number, { $unionNotMatch: undefined }>, 1 | 2 | undefined>(true)
})
it('support readonly array', () => {
testType.equal<FindFirst<Readonly<Array<string | number>>, number>, number>(true)
Expand Down
10 changes: 5 additions & 5 deletions type-plus/ts/array/find_first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@ import type { Find as ArrayFind } from './array_plus.find.js'
* With widen match, a narrowed type will match its widen type.
* e.g. matching `1` against `number` yields `1 | undefined`
*
* The widen behavior can be customized by `Options['caseWiden']`
* The widen behavior can be customized by `Options['$widen']`
*
* @typeParam Options['caseEmptyTuple'] return type when `A` is an empty tuple.
* Default to `never`.
*
* @typeParam Options['caseNever'] return type when `A` is `never`. Default to `never`.
* @typeParam Options['$never'] return type when `A` is `never`. Default to `never`.
*
* @typeParam Options['caseNoMatch'] Return value when `T` does not match `Criteria`.
* @typeParam Options['$noMatch'] 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`.
* @typeParam Options['$widen'] 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`.
* @typeParam Options['$unionMiss'] 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.
*/
Expand Down
2 changes: 1 addition & 1 deletion type-plus/ts/array/head.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ it('returns never for empty tuple', () => {
})

it('can override never case', () => {
testType.equal<Head<never, { caseNever: 1 }>, 1>(true)
testType.equal<Head<never, { $never: 1 }>, 1>(true)
})

it('gets the type of an array', () => {
Expand Down
4 changes: 2 additions & 2 deletions type-plus/ts/array/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { NeverType } from '../never/never_type.js'
* type R = Head<[]> // never
* ```
*
* @typeParam Options['caseNever'] Return type when `T` is `never`.
* @typeParam Options['$never'] Return type when `T` is `never`.
* Default to `never`.
*
* @typeParam Options['caseEmptyTuple'] Return type when `T` is `[]`.
Expand All @@ -25,7 +25,7 @@ export type Head<
Options extends Head.Options = Head.DefaultOptions
> = NeverType<
T,
Options['caseNever'],
Options['$never'],
T['length'] extends 0 ? Options['caseEmptyTuple'] : T[0]
>

Expand Down
2 changes: 1 addition & 1 deletion type-plus/ts/array/last.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ it('returns never for empty tuple', () => {
})

it('can override never case', () => {
testType.equal<Last<never, { caseNever: 1 }>, 1>(true)
testType.equal<Last<never, { $never: 1 }>, 1>(true)
})

it('gets the type of an array', () => {
Expand Down
4 changes: 2 additions & 2 deletions type-plus/ts/array/last.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { NeverType } from '../never/never_type.js'
* type R = Last<[]> // never
* ```
*
* @typeParam Options['caseNever'] Return type when `T` is `never`.
* @typeParam Options['$never'] Return type when `T` is `never`.
* Default to `never`.
*
* @typeParam Options['caseEmptyTuple'] Return type when `T` is `[]`.
Expand All @@ -23,7 +23,7 @@ export type Last<
T extends readonly unknown[],
Options extends Last.Options = Last.DefaultOptions
> = NeverType<T,
Options['caseNever'],
Options['$never'],
T['length'] extends 0
? Options['caseEmptyTuple']
: T extends readonly [...unknown[], infer R] ? R : T[0]
Expand Down
Loading

0 comments on commit 2a0d791

Please sign in to comment.