Skip to content

Commit

Permalink
fix: separate PadStart and Filter
Browse files Browse the repository at this point in the history
  • Loading branch information
unional committed Jul 2, 2023
1 parent 58da4b3 commit 8115873
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 89 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-students-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"type-plus": patch
---

Separate `Filter` and `PadStart` for array and tuple
5 changes: 5 additions & 0 deletions type-plus/ts/array/array_plus.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type Filter<A extends unknown[], Criteria> = A[0] extends Criteria
? A
: Criteria extends A[0]
? Array<Criteria>
: never[]
13 changes: 13 additions & 0 deletions type-plus/ts/array/array_plus.pad_start.ts
Original file line number Diff line number Diff line change
@@ -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<PadWith, UnionOfValues<A>> extends true
? A
: PadStart<[...CreateTuple<MaxLength, PadWith>, ...A], MaxLength, PadWith>
3 changes: 2 additions & 1 deletion type-plus/ts/array/array_plus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
69 changes: 33 additions & 36 deletions type-plus/ts/array/filter.spec.ts
Original file line number Diff line number Diff line change
@@ -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<A, C>', () => {
describe('A is array', () => {
test('array matching criteria gets itself', () => {
type Actual = Filter<string[], string>
testType.equal<string[], Actual>(true)
it('returns A when criteria matches the type of the entries', () => {
testType.equal<Filter<string[], string>, string[]>(true)
})

test('array not matching criteria gets never[]', () => {
type Actual = Filter<string[], number>
testType.equal<never[], Actual>(true)
it('returns never[] when criteria does not match the type of the entries', () => {
testType.equal<Filter<string[], number>, never[]>(true)
})

test('remove unmatched type form array', () => {
type Actual = Filter<Array<string | number>, string>

testType.equal<string[], Actual>(true)
it('removes unmatched type form array', () => {
testType.equal<Filter<Array<string | number>, string>, string[]>(true)
})

test('remove undefined and null', () => {
type Actual = Filter<Array<string | undefined | null>, string>
testType.equal<string[], Actual>(true)
it('removes unmatched undefined and null as expected', () => {
testType.equal<Filter<Array<string | undefined | null>, string>, string[]>(true)
})

test('can filter with undefined and null', () => {
type Actual = Filter<Array<string | undefined | null>, undefined | null>
it('can filter with undefined and null', () => {
// Array<undefined | null> is destructured to undefined[] | null[] by TypeScript
testType.equal<undefined[] | null[], Actual>(true)
testType.equal<Filter<Array<string | undefined | null>, undefined | null>, undefined[] | null[]>(true)
})

test('work with never[]', () => {
type Actual = Filter<never[], undefined>
testType.equal<never[], Actual>(true)
it('work with never[]', () => {
testType.equal<Filter<never[], undefined>, 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<Filter<[1, 2, 3, 4], 2 | 4>, [2, 4]>(true)
testType.equal<Filter<[1, 2, '3'], number>, [1, 2]>(true)
})

test('no match gets never[]', () => {
it('no match gets []', () => {
type Actual = Filter<[1, 2, 3, 4], 5>
testType.equal<never[], Actual>(true)
testType.equal<[], Actual>(true)
})

test('matching undefined and null', () => {
it('empty tuple gets empty tuple', () => {
testType.equal<Filter<[], number>, []>(true)
})

it('matching undefined and null', () => {
type Actual = Filter<[1, undefined, 3, null], undefined | null>
testType.equal<[undefined, null], Actual>(true)
})
Expand All @@ -56,51 +53,51 @@ describe('Filter<A, C>', () => {

describe('KeepMatch<A, C>', () => {
describe('A is array', () => {
test('array matching criteria gets itself', () => {
it('array matching criteria gets itself', () => {
type Actual = KeepMatch<string[], string>
testType.equal<string[], Actual>(true)
})

test('array not matching criteria gets never[]', () => {
it('array not matching criteria gets never[]', () => {
type Actual = KeepMatch<string[], number>
testType.equal<never[], Actual>(true)
})

test('remove unmatched type form array', () => {
it('remove unmatched type form array', () => {
type Actual = KeepMatch<Array<string | number>, string>

testType.equal<string[], Actual>(true)
})

test('remove undefined and null', () => {
it('remove undefined and null', () => {
type Actual = KeepMatch<Array<string | undefined | null>, string>
testType.equal<string[], Actual>(true)
})

test('can filter with undefined and null', () => {
it('can filter with undefined and null', () => {
type Actual = KeepMatch<Array<string | undefined | null>, undefined | null>
// Array<undefined | null> is destructured to undefined[] | null[] by TypeScript
testType.equal<undefined[] | null[], Actual>(true)
})

test('work with never[]', () => {
it('work with never[]', () => {
type Actual = KeepMatch<never[], undefined>
testType.equal<never[], Actual>(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<never[], Actual>(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)
})
Expand Down
28 changes: 8 additions & 20 deletions type-plus/ts/array/filter.ts
Original file line number Diff line number Diff line change
@@ -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<A extends Array<any>, Criteria> = number extends A['length']
? // array
A[0] extends Criteria
? A
: Criteria extends A[0]
? Array<Criteria>
: 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<Tail, Criteria>]
: Filter<Tail, Criteria>
: never
export type Filter<A extends unknown[], Criteria> = number extends A['length']
? FilterArray<A, Criteria>
: FilterTuple<A, Criteria>


/**
* keeps entries satisfying `Criteria` in array or tuple `A`.
*/
export type KeepMatch<A extends Array<any>, Criteria> = Filter<A, Criteria>
export type KeepMatch<A extends unknown[], Criteria> = Filter<A, Criteria>
16 changes: 6 additions & 10 deletions type-plus/ts/array/pad_start.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -30,12 +30,8 @@ export type PadStart<
MaxLength extends number,
PadWith = unknown
> = number extends A['length']
? MaxLength extends 0
? A
: CanAssign<PadWith, UnionOfValues<A>> extends true
? A
: PadStart<[...CreateTuple<MaxLength, PadWith>, ...A], MaxLength, PadWith>
: TuplePadStart<A, MaxLength, PadWith>
? PadStartArray<A, MaxLength, PadWith>
: PadStartTuple<A, MaxLength, PadWith>

/**
* @deprecated use PadStart instead
Expand Down
6 changes: 3 additions & 3 deletions type-plus/ts/assertion/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(subject)`:
`assertType<T>(subject)`

✔️ `immediate`
💥 `immediate`

It ensures `subject` satisfies `T`.
It is similar to `const x: T = subject` without introducing an unused variable.
Expand Down
40 changes: 40 additions & 0 deletions type-plus/ts/tuple/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,46 @@ type R = DropMatch<Array<string>, string> // never[]
type R = DropMatch<Array<1 | 2>, 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<T, Criteria>`
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<T, MaxLength, 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*
```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]
Expand Down
29 changes: 29 additions & 0 deletions type-plus/ts/tuple/tuple_plus.filter.ts
Original file line number Diff line number Diff line change
@@ -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 extends unknown[], Criteria> = T['length'] extends 0
? []
: (
T extends [infer Head, ...infer Tail]
? (
Tail['length'] extends 0
? (
Head extends Criteria
? [Head]
: [])
: (
Head extends Criteria
? [Head, ...Filter<Tail, Criteria>]
: Filter<Tail, Criteria>
)
)
: never
)
9 changes: 4 additions & 5 deletions type-plus/ts/tuple/tuple_plus.pad_start.spec.ts
Original file line number Diff line number Diff line change
@@ -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<PadStart<[1, 2, 3], 5>, [unknown, unknown, 1, 2, 3]>(true)
testType.equal<TuplePlus.PadStart<[1, 2, 3], 5>, [unknown, unknown, 1, 2, 3]>(true)
})

it('returns source type if total length is less than source length', () => {
testType.equal<PadStart<[1, 2, 3], 2>, [1, 2, 3]>(true)
testType.equal<TuplePlus.PadStart<[1, 2, 3], 2>, [1, 2, 3]>(true)
})

it('can specify what to pad with', () => {
testType.equal<PadStart<[1, 2, 3], 5, 0>, [0, 0, 1, 2, 3]>(true)
testType.equal<TuplePlus.PadStart<[1, 2, 3], 5, 0>, [0, 0, 1, 2, 3]>(true)
})
Loading

0 comments on commit 8115873

Please sign in to comment.