-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: ArrayPlus.Find, TuplePlus.Find
- Loading branch information
Showing
14 changed files
with
307 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"type-plus": minor | ||
--- | ||
|
||
Improve `FindFirst`, | ||
add `ArrayPlus.Find` and `TuplePlus.Find` |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { it, test } from '@jest/globals' | ||
import { testType, type ArrayPlus } from '../index.js' | ||
|
||
test('behavior of array.find()', () => { | ||
const array = [1, 2, '3'] | ||
const r = array.find(x => typeof x === 'number') | ||
testType.equal<typeof r, number | string | undefined>(true) | ||
}) | ||
|
||
it('returns never if the type in the array does not satisfy the criteria', () => { | ||
testType.equal<ArrayPlus.Find<string[], number>, never>(true) | ||
}) | ||
|
||
it('returns T if T satisfies the Criteria', () => { | ||
testType.equal<ArrayPlus.Find<number[], number>, number>(true) | ||
}) | ||
|
||
it('returns Criteria | undefined if T is a widen type of Criteria', () => { | ||
testType.equal<ArrayPlus.Find<number[], 1>, 1 | undefined>(true) | ||
testType.equal<ArrayPlus.Find<Array<string | number>, 1>, 1 | undefined>(true) | ||
testType.equal<ArrayPlus.Find<Array<{ a: number } | { b: number }>, { a: 1 }>, { a: 1 } | undefined>(true) | ||
}) | ||
|
||
it('does not support tuple', () => { | ||
testType.equal< | ||
ArrayPlus.Find<[true, 1, 'x', 3], number>, | ||
'does not support tuple. Please use `FindFirst` or `TuplePlus.Find` instead.'>(true) | ||
}) | ||
|
||
it('can override widen case', () => { | ||
testType.equal<ArrayPlus.Find<number[], 1, { widen: never }>, never>(true) | ||
}) | ||
|
||
it('returns T | undefined for T[] if T is a union satisfies the Criteria', () => { | ||
// 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>, number | undefined>(true) | ||
testType.equal<ArrayPlus.Find<Array<1 | 2 | 'x'>, number>, 1 | 2 | undefined>(true) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import type { IsTuple } from '../tuple/tuple_type.js' | ||
import type { IsUnion } from '../union/union.js' | ||
|
||
/** | ||
* 🦴 *utilities* | ||
* | ||
* Gets the first type in the array that matches the `Criteria`. | ||
* | ||
* | ||
* For `Array<T>`, it will return `T | undefined` if `T` satisfies `Criteria`. | ||
* | ||
* @example | ||
* ```ts | ||
* ArrayPlus.Find<Array<1 | 2 | 'x'>, number> // 1 | 2 | undefined | ||
* | ||
* ArrayPlus.Find<[true, 1, 'x', 3], string> // 'x' | ||
* ``` | ||
*/ | ||
export type Find<A extends unknown[], Criteria, Cases extends { | ||
tuple?: unknown, | ||
widen?: unknown | ||
} = { | ||
tuple: 'does not support tuple. Please use `FindFirst` or `TuplePlus.Find` instead.', | ||
widen: Criteria | undefined | ||
}> = | ||
IsTuple< | ||
A, | ||
Cases['tuple'], | ||
A extends Array<infer T> | ||
? (T extends Criteria | ||
? T | ||
: Criteria extends T ? Cases['widen'] : never) extends infer R | ||
? IsUnion<T, R | undefined, R> : never | ||
: never | ||
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
export type { At } from './array.at.js' | ||
export type { Concat } from './array_plus.concat.js' | ||
export type { Entries } from './array.entries.js' | ||
export type { FindFirst as Find } from './array.find.js' | ||
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 { Concat } from './array_plus.concat.js' | ||
export type { Filter } from './array_plus.filter.js' | ||
export type { SplitAt } from './array_plus.split_at.js' | ||
export type { Find } from './array_plus.find.js' | ||
export type { PadStart } from './array_plus.pad_start.js' | ||
export type { SplitAt } from './array_plus.split_at.js' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { it } from '@jest/globals' | ||
import { testType, type FindFirst } from '../index.js' | ||
import { describe } from 'node:test' | ||
|
||
describe('For Array', () => { | ||
it('returns never if the type in the array does not satisfy the criteria', () => { | ||
testType.equal<FindFirst<string[], number>, never>(true) | ||
}) | ||
|
||
it('returns T if T satisfies the Criteria', () => { | ||
testType.equal<FindFirst<number[], number>, number>(true) | ||
}) | ||
|
||
it('returns Criteria | undefined if T is a widen type of Criteria', () => { | ||
testType.equal<FindFirst<number[], 1>, 1 | undefined>(true) | ||
testType.equal<FindFirst<Array<string | number>, 1>, 1 | undefined>(true) | ||
testType.equal<FindFirst<Array<{ a: number } | { b: number }>, { a: 1 }>, { a: 1 } | undefined>(true) | ||
}) | ||
|
||
it('can override widen case', () => { | ||
testType.equal<FindFirst<number[], 1, { widen: never }>, never>(true) | ||
}) | ||
|
||
it('returns T | undefined for T[] if T is a union satisfies the Criteria', () => { | ||
// 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>, number | undefined>(true) | ||
testType.equal<FindFirst<Array<1 | 2 | 'x'>, number>, 1 | 2 | undefined>(true) | ||
}) | ||
}) | ||
|
||
describe('for Tuple', () => { | ||
it('returns never for empty tuple', () => { | ||
testType.equal<FindFirst<[], number>, never>(true) | ||
}) | ||
|
||
it('can override empty tuple case', () => { | ||
testType.equal<FindFirst<[], number, { empty_tuple: 1 }>, 1>(true) | ||
}) | ||
|
||
it('pick first type matching criteria', () => { | ||
testType.equal<FindFirst<[true, 1, 'x', 3], 1>, 1>(true) | ||
testType.equal<FindFirst<[true, 1, 'x', 3], 'x'>, 'x'>(true) | ||
testType.equal<FindFirst<[true, 1, 'x', 3], true>, true>(true) | ||
}) | ||
|
||
it('uses widen type to match literal types', () => { | ||
testType.equal<FindFirst<[true, 1, 'x', 3], number>, 1>(true) | ||
testType.equal<FindFirst<[true, 1, 'x', 3], string>, 'x'>(true) | ||
testType.equal<FindFirst<[true, 1, 'x', 3], boolean>, true>(true) | ||
}) | ||
|
||
it('no match gets never', () => { | ||
type Actual = FindFirst<[true, 1, 'x'], 2> | ||
testType.equal<Actual, never>(true) | ||
}) | ||
|
||
it('pick object', () => { | ||
type Actual = FindFirst< | ||
[{ name: 'a', type: 1 }, { name: 'b', type: 2 }, { name: 'c', type: 3 }, { name: 'b', type: 4 }], | ||
{ name: 'b' } | ||
>['type'] | ||
testType.equal<Actual, 2>(true) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import type { TupleType } from '../tuple/tuple_type.js' | ||
import type { Find as TupleFind } from '../tuple/tuple_plus.find.js' | ||
import type { Find as ArrayFind } from './array_plus.find.js' | ||
|
||
/** | ||
* 🦴 *utilities* | ||
* | ||
* Gets the first type in the array or tuple that matches the `Criteria`. | ||
* | ||
* If the `Criteria` is not met, it will return `never'. | ||
* | ||
* For `Array<T>`, it will return `T | undefined` if `T` satisfies `Criteria`. | ||
* | ||
* @example | ||
* ```ts | ||
* FindFirst<Array<1 | 2 | 'x'>, number> // 1 | 2 | undefined | ||
* | ||
* FindFirst<[true, 1, 'x', 3], string> // 'x' | ||
* ``` | ||
*/ | ||
export type FindFirst<A extends unknown[], Criteria, Cases extends { | ||
empty_tuple?: unknown, | ||
widen?: unknown | ||
} = { | ||
empty_tuple: never, | ||
widen: Criteria | undefined | ||
}> = TupleType< | ||
A, | ||
TupleFind<A, Criteria, Cases>, | ||
ArrayFind<A, Criteria, Cases> | ||
> | ||
|
||
/** | ||
* @deprecated use FindFirst | ||
*/ | ||
export type First<A extends any[], Criteria> = FindFirst<A, Criteria> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { it, test } from '@jest/globals' | ||
import { testType, type TuplePlus } from '../index.js' | ||
|
||
test('behavior of tuple.find()', () => { | ||
const tuple: [1, 2, '3'] = [1, 2, '3'] | ||
const r = tuple.find(x => typeof x === 'number') | ||
testType.equal<typeof r, 1 | 2 | '3' | undefined>(true) | ||
}) | ||
|
||
it('returns never for empty tuple', () => { | ||
testType.equal<TuplePlus.Find<[], number>, never>(true) | ||
}) | ||
|
||
it('can override empty tuple case', () => { | ||
testType.equal<TuplePlus.Find<[], number, { empty_tuple: 1 }>, 1>(true) | ||
}) | ||
|
||
it('does not work with array type', () => { | ||
testType.equal<TuplePlus.Find<string[], number>, 'does not support array. Please use `FindFirst` or `ArrayPlus.Find` instead.'>(true) | ||
}) | ||
|
||
it('pick first type matching criteria', () => { | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], 1>, 1>(true) | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], 'x'>, 'x'>(true) | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], true>, true>(true) | ||
}) | ||
|
||
it('uses widen type to match literal types', () => { | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], number>, 1>(true) | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], string>, 'x'>(true) | ||
testType.equal<TuplePlus.Find<[true, 1, 'x', 3], boolean>, true>(true) | ||
}) | ||
|
||
it('no match gets never', () => { | ||
type Actual = TuplePlus.Find<[true, 1, 'x'], 2> | ||
testType.equal<Actual, never>(true) | ||
}) | ||
|
||
it('pick object', () => { | ||
type Actual = TuplePlus.Find< | ||
[{ name: 'a', type: 1 }, { name: 'b', type: 2 }, { name: 'c', type: 3 }, { name: 'b', type: 4 }], | ||
{ name: 'b' } | ||
>['type'] | ||
testType.equal<Actual, 2>(true) | ||
}) |
Oops, something went wrong.