Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add castArray and castArrayIfExists #97

Merged
merged 10 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions benchmarks/casted/castArray.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as _ from 'radashi'
import { bench } from 'vitest'

describe('castArray', () => {
bench('with an array', () => {
_.castArray(new Array(100))
})
bench('with number', () => {
_.castArray(1)
})
bench('with null', () => {
_.castArray(null)
})
})
14 changes: 14 additions & 0 deletions benchmarks/casted/castArrayIfExists.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as _ from 'radashi'
import { bench } from 'vitest'

describe('castArrayIfExists', () => {
bench('with an array', () => {
_.castArrayIfExists(new Array(100))
})
bench('with number', () => {
_.castArrayIfExists(1)
})
bench('with null', () => {
_.castArrayIfExists(null)
})
})
16 changes: 16 additions & 0 deletions docs/casted/castArray.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: castArray
description: Cast a value into an array
---

### Usage

The `castArray` function ensures that the input value is always returned as an array. If the input is already an array, it returns a shallow copy of the array. If the input is not an array, it wraps the input in a new array.

```ts
import * as _ from 'radashi'

_.castArray(1) // => [1]
_.castArray([1, 2, 3]) // => [1, 2, 3]
_.castArray('hello') // => ['hello']
```
18 changes: 18 additions & 0 deletions docs/casted/castArrayIfExists.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: castArrayIfExists
description: Cast a non-nullish value into an array
---

### Usage

The `castArrayIfExists` function ensures that a non-nullish input value is always returned as an array. If the input is already an array, it returns a shallow copy of the array. If the input is not an array, it wraps the input in a new array. Nullish values (null or undefined) are passed through as is.

```ts
import * as _ from 'radashi'

_.castArrayIfExists(1) // => [1]
_.castArrayIfExists([1, 2, 3]) // => [1, 2, 3]
_.castArrayIfExists('hello') // => ['hello']
_.castArrayIfExists(null) // => null
_.castArrayIfExists(undefined) // => undefined
```
37 changes: 37 additions & 0 deletions src/casted/castArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Casts the given value to an array. If the value is already an
* array, a shallow copy is returned. Otherwise, a new array
* containing the value is returned.
*
* @see https://radashi-org.github.io/reference/casted/castArray
* @example
* ```ts
* castArray(1) // => [1]
* castArray([1, 2]) // => [1, 2]
* castArray(null) // => [null]
* castArray(undefined) // => [undefined]
* ```
*/
export function castArray<T>(value: T): CastArray<T>
export function castArray(value: unknown): unknown {
return Array.isArray(value) ? value.slice() : [value]
}

/**
* The return type of the {@link castArray} function.
*
* @see https://radashi-org.github.io/reference/casted/castArray
*/
export type CastArray<T> = [T] extends [never]
? never[]
: [unknown] extends [T]
? unknown[]
:
| (T extends any
? T extends readonly (infer U)[]
? U[]
: never
: never)
| (Exclude<T, readonly any[]> extends never
? never
: Exclude<T, readonly any[]>[])
38 changes: 38 additions & 0 deletions src/casted/castArrayIfExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Casts the given value to an array if it's not equal to `null` or
* `undefined`. If the value is an array, it returns a shallow copy of
* the array. Otherwise, it returns a new array containing the value.
*
* @see https://radashi-org.github.io/reference/casted/castArrayIfExists
* @example
* ```ts
* castArrayIfExists(1) // => [1]
* castArrayIfExists(null) // => null
* castArrayIfExists(undefined) // => undefined
* castArrayIfExists([1, 2, 3]) // => [1, 2, 3]
* ```
*/
export function castArrayIfExists<T>(value: T): CastArrayIfExists<T>
export function castArrayIfExists(value: unknown): unknown {
return Array.isArray(value) ? value.slice() : value != null ? [value] : value
}

/**
* The return type of the {@link castArrayIfExists} function.
*
* @see https://radashi-org.github.io/reference/casted/castArrayIfExists
*/
export type CastArrayIfExists<T> = [T] extends [never]
? never[]
: [unknown] extends [T]
? unknown[] | null | undefined
:
| (T extends any
? T extends readonly (infer U)[]
? U[]
: never
: never)
| (Exclude<T, readonly any[] | null | undefined> extends never
? never
: Exclude<T, readonly any[] | null | undefined>[])
| Extract<T, null | undefined>
4 changes: 3 additions & 1 deletion src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export * from './async/retry.ts'
export * from './async/sleep.ts'
export * from './async/tryit.ts'

export * from './casted/castMapping'
export * from './casted/castArray.ts'
export * from './casted/castArrayIfExists.ts'
export * from './casted/castMapping.ts'

export * from './curry/callable.ts'
export * from './curry/chain.ts'
Expand Down
64 changes: 64 additions & 0 deletions tests/casted/castArray.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as _ from 'radashi'

describe('castArray', () => {
test('mutable arrays', () => {
const input: number[] = [1, 2, 3]
expectTypeOf(_.castArray(input)).toEqualTypeOf<number[]>()
})
test('readonly arrays', () => {
const input: readonly number[] = [1, 2, 3]
const output = _.castArray(input)

// castArray clones the input array, so "readonly" is removed.
expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('added properties are lost', () => {
const input = [] as number[] & { foo?: boolean }
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('union type with array type and primitive type', () => {
const input = 1 as number[] | number
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('union type with two primitive types', () => {
const input = 1 as number | string
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<(number | string)[]>()
})
test('union type with two array types', () => {
const input = [1, 2, 3] as number[] | readonly string[]
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<number[] | string[]>()
})
test('primitive types', () => {
expectTypeOf(_.castArray(1)).toEqualTypeOf<number[]>()
expectTypeOf(_.castArray('a')).toEqualTypeOf<string[]>()
expectTypeOf(_.castArray(true)).toEqualTypeOf<boolean[]>()
expectTypeOf(_.castArray(null)).toEqualTypeOf<null[]>()
expectTypeOf(_.castArray(undefined)).toEqualTypeOf<undefined[]>()
})
test('never type', () => {
const input = 1 as never
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<never[]>()
})
test('any type', () => {
const input = 1 as any
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<unknown[]>()
})
test('unknown type', () => {
const input = 1 as unknown
const output = _.castArray(input)

expectTypeOf(output).toEqualTypeOf<unknown[]>()
})
})
32 changes: 32 additions & 0 deletions tests/casted/castArray.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as _ from 'radashi'

describe('castArray', () => {
test('return a shallow copy if input is an array', () => {
const input = [1, 2, 3]
expect(_.castArray(input)).toEqual(input)
expect(_.castArray(input)).not.toBe(input)
})
test('return a new array with the same elements if input is an array', () => {
const input = [1, 2, 3]
const result = _.castArray(input)
expect(result).toEqual(input)
expect(result).not.toBe(input) // Ensure it's a copy
})
test('return an array with the input if input is not an array', () => {
const input = 1
expect(_.castArray(input)).toEqual([input])
})
test('handle primitives', () => {
expect(_.castArray(1)).toEqual([1])
expect(_.castArray('a')).toEqual(['a'])
expect(_.castArray(true)).toEqual([true])
expect(_.castArray(null)).toEqual([null])
expect(_.castArray(undefined)).toEqual([undefined])
})
test('handle objects and functions', () => {
const obj = { a: 1 }
const func = () => {}
expect(_.castArray(obj)).toEqual([obj])
expect(_.castArray(func)).toEqual([func])
})
})
66 changes: 66 additions & 0 deletions tests/casted/castArrayIfExists.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as _ from 'radashi'

describe('castArrayIfExists', () => {
test('mutable arrays', () => {
const input: number[] = [1, 2, 3]
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('readonly arrays', () => {
const input: readonly number[] = [1, 2, 3]
const output = _.castArrayIfExists(input)

// castArrayIfExists clones the input array, so "readonly" is removed.
expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('added properties are lost', () => {
const input = [] as number[] & { foo?: boolean }
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('union type with array type and primitive type', () => {
const input = 1 as number[] | number
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<number[]>()
})
test('union type with two primitive types', () => {
const input = 1 as number | string
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<(number | string)[]>()
})
test('union type with two array types', () => {
const input = [1, 2, 3] as number[] | readonly string[]
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<number[] | string[]>()
})
test('primitive types', () => {
expectTypeOf(_.castArrayIfExists(1)).toEqualTypeOf<number[]>()
expectTypeOf(_.castArrayIfExists('a')).toEqualTypeOf<string[]>()
expectTypeOf(_.castArrayIfExists(true)).toEqualTypeOf<boolean[]>()
expectTypeOf(_.castArrayIfExists(null)).toEqualTypeOf<null>()
expectTypeOf(_.castArrayIfExists(undefined)).toEqualTypeOf<undefined>()
})
test('never type', () => {
const input = 1 as never
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<never[]>()
})
test('any type', () => {
const input = 1 as any
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<unknown[] | null | undefined>()
})
test('unknown type', () => {
const input = 1 as unknown
const output = _.castArrayIfExists(input)

expectTypeOf(output).toEqualTypeOf<unknown[] | null | undefined>()
})
})
34 changes: 34 additions & 0 deletions tests/casted/castArrayIfExists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as _ from 'radashi'

describe('castArrayIfExists', () => {
test('return a shallow copy if input is an array', () => {
const input = [1, 2, 3]
expect(_.castArrayIfExists(input)).toEqual(input)
expect(_.castArrayIfExists(input)).not.toBe(input)
})
test('return a new array with the same elements if input is an array', () => {
const input = [1, 2, 3]
const result = _.castArrayIfExists(input)
expect(result).toEqual(input)
expect(result).not.toBe(input) // Ensure it's a copy
})
test('return an array with the input if input is not an array', () => {
const input = 1
expect(_.castArrayIfExists(input)).toEqual([input])
})
test('handle nullish values', () => {
expect(_.castArrayIfExists(null)).toEqual(null)
expect(_.castArrayIfExists(undefined)).toEqual(undefined)
})
test('handle primitives', () => {
expect(_.castArrayIfExists(1)).toEqual([1])
expect(_.castArrayIfExists('a')).toEqual(['a'])
expect(_.castArrayIfExists(true)).toEqual([true])
})
test('handle objects and functions', () => {
const obj = { a: 1 }
const func = () => {}
expect(_.castArrayIfExists(obj)).toEqual([obj])
expect(_.castArrayIfExists(func)).toEqual([func])
})
})
Loading