Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
113 additions
and
166 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,3 @@ | ||
import property from 'lodash-es/property'; | ||
|
||
console.log(property(['a', 'b'])({ a: 1 })); |
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,3 @@ | ||
import { property } from '@s-libs/micro-dash'; | ||
|
||
console.log(property(['a', 'b'])({ a: 1 })); |
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 |
---|---|---|
@@ -1,119 +1,71 @@ | ||
// There's no good way to make this type safe. | ||
import { expectTypeOf } from 'expect-type'; | ||
import { property } from './property'; | ||
|
||
// import { property } from "./property"; | ||
// | ||
// describe("property()", () => { | ||
// // | ||
// // stolen from https://github.com/lodash/lodash | ||
// // | ||
// | ||
// it("should create a function that plucks a property value of a given object", () => { | ||
// const object = { a: 1 }; | ||
// const prop = property<typeof object>(["a"]); | ||
// expect(prop.length).toBe(1); | ||
// expect(prop(object)).toBe(1); | ||
// }); | ||
// | ||
// it("should pluck deep property values", () => { | ||
// expect(property(["a", "b"])({ a: { b: 2 } })).toBe(2); | ||
// }); | ||
// | ||
// it("should work with a non-string `path`", () => { | ||
// expect(property([1])([1, 2, 3])).toBe(2); | ||
// }); | ||
// | ||
// // it("should preserve the sign of `0`", () => { | ||
// // const object = { "-0": "a", "0": "b" }, | ||
// // props = [-0, Object(-0), 0, Object(0)]; | ||
// // | ||
// // const actual = lodashStable.map(props, function(key) { | ||
// // const prop = _.property(key); | ||
// // return prop(object); | ||
// // }); | ||
// // | ||
// // assert.deepEqual(actual, ["a", "a", "b", "b"]); | ||
// // }); | ||
// // | ||
// // it("should coerce `path` to a string", () => { | ||
// // function fn() {} | ||
// // fn.toString = lodashStable.constant("fn"); | ||
// // | ||
// // const expected = [1, 2, 3, 4], | ||
// // object = { null: 1, undefined: 2, fn: 3, "[object Object]": 4 }, | ||
// // paths = [null, undefined, fn, {}]; | ||
// // | ||
// // lodashStable.times(2, function(index) { | ||
// // const actual = lodashStable.map(paths, function(path) { | ||
// // const prop = _.property(index ? [path] : path); | ||
// // return prop(object); | ||
// // }); | ||
// // | ||
// // assert.deepEqual(actual, expected); | ||
// // }); | ||
// // }); | ||
// // | ||
// // it("should pluck a key over a path", () => { | ||
// // const object = { "a.b": 1, a: { b: 2 } }; | ||
// // | ||
// // lodashStable.each(["a.b", ["a.b"]], function(path) { | ||
// // const prop = _.property(path); | ||
// // assert.strictEqual(prop(object), 1); | ||
// // }); | ||
// // }); | ||
// // | ||
// // it("should return `undefined` when `object` is nullish", function( | ||
// // assert, | ||
// // ) { | ||
// // const values = [, null, undefined], | ||
// // expected = lodashStable.map(values, noop); | ||
// // | ||
// // lodashStable.each(["constructor", ["constructor"]], function(path) { | ||
// // const prop = _.property(path); | ||
// // | ||
// // const actual = lodashStable.map(values, function(value, index) { | ||
// // return index ? prop(value) : prop(); | ||
// // }); | ||
// // | ||
// // assert.deepEqual(actual, expected); | ||
// // }); | ||
// // }); | ||
// // | ||
// // it( | ||
// // "should return `undefined` for deep paths when `object` is nullish", | ||
// // () => { | ||
// // const values = [, null, undefined], | ||
// // expected = lodashStable.map(values, noop); | ||
// // | ||
// // lodashStable.each( | ||
// // [ | ||
// // "constructor.prototype.valueOf", | ||
// // ["constructor", "prototype", "valueOf"], | ||
// // ], | ||
// // function(path) { | ||
// // const prop = _.property(path); | ||
// // | ||
// // const actual = lodashStable.map(values, function(value, index) { | ||
// // return index ? prop(value) : prop(); | ||
// // }); | ||
// // | ||
// // assert.deepEqual(actual, expected); | ||
// // }, | ||
// // ); | ||
// // }, | ||
// // ); | ||
// // | ||
// // it( | ||
// // "should return `undefined` if parts of `path` are missing", | ||
// // () => { | ||
// // const object = {}; | ||
// // | ||
// // lodashStable.each( | ||
// // ["a", "a[1].b.c", ["a"], ["a", "1", "b", "c"]], | ||
// // function(path) { | ||
// // const prop = _.property(path); | ||
// // assert.strictEqual(prop(object), undefined); | ||
// // }, | ||
// // ); | ||
// // }, | ||
// // ); | ||
// }); | ||
describe('property()', () => { | ||
it('has fancy typing', () => { | ||
expect().nothing(); | ||
|
||
interface O { | ||
a: number; | ||
b: Array<{ c: 3 }>; | ||
d?: { e: Date }; | ||
} | ||
|
||
const obj: O = { | ||
a: 1, | ||
b: [{ c: 3 }], | ||
}; | ||
expectTypeOf(property(['a'])(obj)).toEqualTypeOf<number>(); | ||
expectTypeOf(property(['b'])(obj)).toEqualTypeOf<Array<{ c: 3 }>>(); | ||
expectTypeOf(property(['b', 0])(obj)).toEqualTypeOf<{ c: 3 }>(); | ||
expectTypeOf(property(['b', 0, 'c'])(obj)).toEqualTypeOf<3>(); | ||
|
||
expectTypeOf(property(['d'])(obj)).toEqualTypeOf<{ e: Date } | undefined>(); | ||
const blah = property(['d', 'e'])(obj); | ||
expectTypeOf(blah).toEqualTypeOf<Date | undefined>(); | ||
|
||
const oOrN = obj as O | null; | ||
const blah2 = property(['a'])(oOrN); | ||
expectTypeOf(blah2).toEqualTypeOf<number | undefined>(); | ||
|
||
expectTypeOf(property(['a'])(undefined)).toEqualTypeOf<undefined>(); | ||
}); | ||
|
||
// | ||
// stolen from https://github.com/lodash/lodash | ||
// | ||
|
||
it('should create a function that plucks a property value of a given object', () => { | ||
const object = { a: 1 }; | ||
const prop = property(['a']); | ||
expect(prop.length).toBe(1); | ||
expect(prop(object)).toBe(1); | ||
}); | ||
|
||
it('should pluck deep property values', () => { | ||
expect(property(['a', 'b'])({ a: { b: 2 } })).toBe(2); | ||
}); | ||
|
||
it('should work with a non-string `path`', () => { | ||
expect(property([1])([1, 2, 3])).toBe(2); | ||
}); | ||
|
||
it('should return `undefined` when `object` is nullish', () => { | ||
expect(property(['constructor'])(null)).toBe(undefined); | ||
expect(property(['constructor'])(undefined)).toBe(undefined); | ||
}); | ||
|
||
it('should return `undefined` for deep paths when `object` is nullish', () => { | ||
expect(property(['constructor', 'prototype', 'valueOf'])(null)).toBe( | ||
undefined, | ||
); | ||
expect(property(['constructor', 'prototype', 'valueOf'])(undefined)).toBe( | ||
undefined, | ||
); | ||
}); | ||
|
||
it('should return `undefined` if parts of `path` are missing', () => { | ||
expect(property(['a'])({})).toBe(undefined); | ||
expect(property(['a', '1', 'b', 'c'])({})).toBe(undefined); | ||
}); | ||
}); |
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,41 +1,29 @@ | ||
// There's no good way to make this type safe. | ||
import { IfCouldBe, Nil } from '../interfaces'; | ||
import { getWithoutDefault } from '../object/get'; | ||
|
||
// import { Function1 } from "ng-dev"; | ||
// | ||
// /** | ||
// * Creates a function that returns the value at `path` of a given object. | ||
// * | ||
// * Contribution to minified bundle size, when it is the only function imported: | ||
// */ | ||
// | ||
// export function property<T, K1 extends keyof T>( | ||
// path: [K1], | ||
// ): Function1<T, T[K1]>; | ||
// export function property<T, K1 extends keyof T, K2 extends keyof T[K1]>( | ||
// path: [K1, K2], | ||
// ): Function1<T, T[K1][K2]>; | ||
// export function property< | ||
// T, | ||
// K1 extends keyof T, | ||
// K2 extends keyof T[K1], | ||
// K3 extends keyof T[K1][K2] | ||
// >(path: [K1, K2, K3]): Function1<T, T[K1][K2][K3]>; | ||
// export function property< | ||
// T, | ||
// K1 extends keyof T, | ||
// K2 extends keyof T[K1], | ||
// K3 extends keyof T[K1][K2], | ||
// K4 extends keyof T[K1][K2][K3] | ||
// >(path: [K1, K2, K3, K4]): Function1<T, T[K1][K2][K3][K4]>; | ||
// // export function property(path: string[]): any; | ||
// | ||
// export function property(path: string[]) { | ||
// const length = path.length; | ||
// return (object: any) => { | ||
// let index = 0; | ||
// while (object != null && index < length) { | ||
// object = object[path[index++]]; | ||
// } | ||
// return !index || index < length ? undefined : object; | ||
// }; | ||
// } | ||
// https://stackoverflow.com/a/64776616/1836506 | ||
type First<T extends any[]> = T extends [infer U, ...any[]] ? U : never; | ||
type Rest<T extends any[]> = T extends [any, ...infer U] ? U : never; | ||
type PropertyAtPath<T, Path extends any[]> = First<Path> extends never | ||
? T | ||
: First<Path> extends keyof NonNullable<T> | ||
? | ||
| PropertyAtPath<NonNullable<T>[First<Path>], Rest<Path>> | ||
| IfCouldBe<T, Nil, undefined> | ||
: undefined; | ||
|
||
/** | ||
* Creates a function that returns the value at `path` of a given object. | ||
* | ||
* Differences from lodash: | ||
* - does not handle a dot-separated string for `path` | ||
* | ||
* Contribution to minified bundle size, when it is the only function imported: | ||
* - Lodash: 5,261 bytes | ||
* - Micro-dash: 127 bytes | ||
*/ | ||
export function property<P extends PropertyKey[]>( | ||
path: readonly [...P], | ||
): <T>(object: T) => PropertyAtPath<T, P> { | ||
return getWithoutDefault.bind(0, path); | ||
} |