Skip to content

Commit

Permalink
✨ Add map function to feature of object
Browse files Browse the repository at this point in the history
  • Loading branch information
TomokiMiyauci committed May 9, 2021
1 parent cbb99d0 commit 4adeb0e
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 7 deletions.
53 changes: 53 additions & 0 deletions src/_/mapObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2021-present the Fonction authors. All rights reserved. MIT license.

import { entries } from '../entries.ts'
/**
* Infer object applied return value to value
*
* @typeParam T - Return types of function
* @typeParam U - Input any object
* @returns The result of `{[k in keyof U]: T extends void ? undefined : T }`
*
* @example
* ```ts
* MapObject<void, {}> // {}
* MapObject<void, { '': '' }> // { '': undefined }
* MapObject<number, { hoge: 'huga', 1: 2 }> // { hoge: number, 1: number }
* ```
*
* @category `Object`
*
* @internal
*/
type MapObject<T, U extends { [k: string]: unknown }> = {
[k in keyof U]: T extends void ? undefined : T
}

/**
* Takes a function, applies the function to each, and returns a result of the same shape.
*
* @param fn - The function to be called on every element of the input object.
* @param obj - The object to be iterated over
* @returns The result of object applied function to value
*
* @example
* ```ts
* const triple = (val: number):number => val * 3
* mapObject(triple, { tom: 1, john: 2, bob: 3 }) // { tom: 3, john: 6, bob: 9}
* ```
*
* @category `Object`
*
* @internal
*/
const mapObject = <T extends { [k: string]: unknown }, U>(
fn: (val: T[keyof T], prop: keyof T, obj: T) => U,
obj: T
): MapObject<U, T> =>
entries(obj).reduce((acc, [key, val]) => {
acc[key] = fn(val as T[keyof T], key, obj)
return acc
}, {} as { [k: string]: unknown }) as MapObject<U, T>

export { mapObject }
export type { MapObject }
27 changes: 20 additions & 7 deletions src/map.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
// Copyright 2021-present the Fonction authors. All rights reserved. MIT license.

/* eslint-disable @typescript-eslint/no-explicit-any */
import { MapArray, mapArray as _mapArray } from './_/mapArray.ts'

import { MapObject, mapObject as _mapObject } from './_/mapObject.ts'
import { isArray } from './isArray.ts'
/**
* Takes a function, applies the function to each, and returns a result of the same shape.
*
* @param fn - The function to be called on every element of the input array.
* @param array - The array to be iterated over.
* @returns The result of `array.map(fn)`
* @param fn - The function to be called on every element of the input list.
* @param list - The list to be iterated over.
* @returns The result of `list.map(fn)` or object applied function to value
*
* @example
* ```ts
* const triple = (val: number):number => val * 3
* map(triple, [1, 2, 3]) // [3, 6, 9]
* mapObject(triple, { tom: 1, john: 2, bob: 3 }) // { tom: 3, john: 6, bob: 9}
* ```
*
* @category `Array`
* @category `Array` `Object`
*
* @beta
*/
const map = _mapArray
const map: {
<T extends readonly unknown[], U>(
fn: (value: T[number], index: number, list: T) => U,
list: T
): MapArray<U, T>
<T extends { [k: string]: unknown }, U>(
fn: (val: T[keyof T], prop: keyof T, list: T) => U,
list: T
): MapObject<U, T>
} = (fn: any, list: any) =>
isArray(list) ? _mapArray(fn, list) : (_mapObject(fn, list) as any)

export { map }
export type { MapArray }
export type { MapArray, MapObject }
85 changes: 85 additions & 0 deletions test/_/mapObject.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2021-present the Fonction authors. All rights reserved. MIT license.

/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/ban-types */
import { assertEquals } from '../../deps.ts'
import { MapObject, mapObject } from '../../src/_/mapObject.ts'
import { AnyFn } from '../../src/types/index.ts'
import { assertEqual } from '../asserts.ts'
Deno.test('mapObject', () => {
const table: [AnyFn, Record<string, unknown>, Record<string, unknown>][] = [
[() => {}, {}, {}],
[() => 1, {}, {}],
[() => undefined, {}, {}],
[() => {}, { '': '' }, { '': undefined }],
[() => 1, { '': '' }, { '': 1 }],
[() => '', { '': '' }, { '': '' }],
[() => '', { '': '' }, { '': '' }],
[() => '', { '': '' }, { '': '' }],
[() => ({}), { '': '' }, { '': {} }],
[() => ({}), { hoge: 'huga', hello: 'world' }, { hoge: {}, hello: {} }],
[
(val: string, key: string) => `${key}-${val}`,
{ hoge: 'huga', hello: 'world' },
{ hoge: 'hoge-huga', hello: 'hello-world' }
],
[
(_, __, obj) => obj,
{ hoge: 'huga', hello: 'world' },
{
hoge: { hoge: 'huga', hello: 'world' },
hello: { hoge: 'huga', hello: 'world' }
}
]
]
table.forEach(([fn, array, expected]) => {
assertEquals(
mapObject(fn, array),
expected,
`mapObject(${fn}, ${array}) -> ${expected}`
)
})

assertEqual<{}>(mapObject(() => {}, {}))
assertEqual<{}>(mapObject(() => {}, {} as const))
assertEqual<{}>(mapObject(() => {}, {} as {}))
assertEqual<{ hoge: undefined }>(mapObject(() => {}, { hoge: 'huga' }))
assertEqual<{ readonly hoge: undefined }>(
mapObject(() => {}, { hoge: 'huga' } as const)
)
assertEqual<{ hoge: undefined; hello: undefined }>(
mapObject(() => {}, { hoge: 'huga', hello: 'world' })
)
assertEqual<{ hoge: number }>(mapObject(() => 1, { hoge: 'huga' }))
assertEqual<{ hoge: 1 }>(mapObject(() => 1 as const, { hoge: 'huga' }))
assertEqual<{ readonly hoge: 1 }>(
mapObject(() => 1 as const, { hoge: 'huga' } as const)
)
assertEqual<{ hoge: string }>(
mapObject((val, key) => `${key}-${val}`, { hoge: 'huga' })
)
assertEqual<{ hoge: `hoge-${string}` }>(
mapObject((val, key) => `${key}-${val}` as const, { hoge: 'huga' })
)
assertEqual<{ hoge: `hoge-huga` }>(
mapObject((val, key) => `${key}-${val}` as const, { hoge: 'huga' } as const)
)
})

Deno.test('types', () => {
assertEqual<{}, MapObject<void, {}>>()
assertEqual<{}, MapObject<undefined, {}>>()
assertEqual<{}, MapObject<null, {}>>()
assertEqual<{ hoge: undefined }, MapObject<void, { hoge: 'huga' }>>()
assertEqual<{ hoge: number }, MapObject<number, { hoge: 'huga' }>>()
assertEqual<
{ readonly hoge: number },
MapObject<number, { readonly hoge: 'huga' }>
>()
assertEqual<{ hoge: {} }, MapObject<{}, { hoge: 'huga' }>>()
assertEqual<{ hoge: [] }, MapObject<[], { hoge: 'huga' }>>()
assertEqual<
{ hoge: number; hello: number },
MapObject<number, { hoge: 'huga'; hello: 'world' }>
>()
})
102 changes: 102 additions & 0 deletions test/map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2021-present the Fonction authors. All rights reserved. MIT license.

/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/ban-types */
import { assertEquals } from '../deps.ts'
import { map } from '../src/map.ts'
import { AnyFn } from '../src/types/index.ts'
import { assertEqual } from './asserts.ts'

Deno.test('map', () => {
const tableArray: [AnyFn, unknown[], unknown[]][] = [
[() => {}, [], []],
[() => 1, [], []],
[() => '', [], []],
[() => {}, [''], [undefined]],
[() => {}, ['', 1], [undefined, undefined]],
[() => {}, ['', 1, {}], [undefined, undefined, undefined]],
[() => 1, [[]], [1]],
[() => 1, [[], [], 1, 0], [1, 1, 1, 1]]
]
tableArray.forEach(([fn, array, expected]) => {
assertEquals(
map(fn, array),
expected,
`map(${fn}, ${array}) -> ${expected}`
)
})

const tableObject: [
AnyFn,
Record<string, unknown>,
Record<string, unknown>
][] = [
[() => {}, {}, {}],
[() => 1, {}, {}],
[() => undefined, {}, {}],
[() => {}, { '': '' }, { '': undefined }],
[() => 1, { '': '' }, { '': 1 }],
[() => '', { '': '' }, { '': '' }],
[() => '', { '': '' }, { '': '' }],
[() => '', { '': '' }, { '': '' }],
[() => ({}), { '': '' }, { '': {} }],
[() => ({}), { hoge: 'huga', hello: 'world' }, { hoge: {}, hello: {} }],
[
(val: string, key: string) => `${key}-${val}`,
{ hoge: 'huga', hello: 'world' },
{ hoge: 'hoge-huga', hello: 'hello-world' }
],
[
(_, __, obj) => obj,
{ hoge: 'huga', hello: 'world' },
{
hoge: { hoge: 'huga', hello: 'world' },
hello: { hoge: 'huga', hello: 'world' }
}
]
]
tableObject.forEach(([fn, array, expected]) => {
assertEquals(
map(fn, array),
expected,
`map(${fn}, ${array}) -> ${expected}`
)
})

assertEqual<[]>(map(() => {}, []))
assertEqual<[]>(map(() => {}, [] as []))
assertEqual<[]>(map(() => {}, [] as const))
assertEqual<[]>(map(() => 1, []))
assertEqual<1[]>(map(() => 1, ['']))
assertEqual<1[]>(map(() => 1, [''] as const))
assertEqual<1[]>(map(() => 1, [''] as ['']))
assertEqual<(1 | 2)[]>(map(() => 1 as 1 | 2, [''] as ['']))
assertEqual<number[]>(map(() => 1, [[]]))
assertEqual<1[]>(map(() => 1, [[]]))
assertEqual<1[]>(map(() => 1, [[]] as const))

assertEqual<{}>(map(() => {}, {}))
assertEqual<{}>(map(() => {}, {} as const))
assertEqual<{}>(map(() => {}, {} as {}))
assertEqual<{ hoge: undefined }>(map(() => {}, { hoge: 'huga' }))
assertEqual<{ readonly hoge: undefined }>(
map(() => {}, { hoge: 'huga' } as const)
)
assertEqual<{ hoge: undefined; hello: undefined }>(
map(() => {}, { hoge: 'huga', hello: 'world' })
)
assertEqual<{ hoge: number }>(map(() => 1, { hoge: 'huga' }))
assertEqual<{ hoge: 1 }>(map(() => 1 as const, { hoge: 'huga' }))
assertEqual<{ readonly hoge: 1 }>(
map(() => 1 as const, { hoge: 'huga' } as const)
)
assertEqual<{ hoge: string }>(
map((val, key) => `${key}-${val}`, { hoge: 'huga' })
)
assertEqual<{ hoge: `hoge-${string}` }>(
map((val, key) => `${key}-${val}` as const, { hoge: 'huga' })
)
assertEqual<{ hoge: `hoge-huga` }>(
map((val, key) => `${key}-${val}` as const, { hoge: 'huga' } as const)
)
})

0 comments on commit 4adeb0e

Please sign in to comment.