Skip to content

Commit

Permalink
Improve the type coverage of clone function (#62)
Browse files Browse the repository at this point in the history
* Improve the type coverage of clone function
  • Loading branch information
mertdy authored Nov 14, 2022
1 parent b206854 commit 6952a98
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 27 deletions.
2 changes: 1 addition & 1 deletion cdn/.version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9.1.0
9.2.0
23 changes: 15 additions & 8 deletions cdn/radash.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ const isArray = (value) => {
const isObject = (value) => {
return !!value && value.constructor === Object;
};
const isPrimitive = (value) => {
return value === void 0 || value === null || typeof value !== "object" && typeof value !== "function";
};
const isFunction = (value) => {
return !!(value && value.constructor && value.call && value.apply);
};
Expand Down Expand Up @@ -560,13 +563,17 @@ const invert = (obj) => {
const lowerize = (obj) => mapKeys(obj, (k) => k.toLowerCase());
const upperize = (obj) => mapKeys(obj, (k) => k.toUpperCase());
const clone = (obj) => {
return Object.getOwnPropertyNames(obj).reduce(
(acc, name) => ({
...acc,
[name]: obj[name]
}),
{}
);
if (isPrimitive(obj)) {
return obj;
}
if (typeof obj === "function") {
return obj.bind({});
}
const newObj = new obj.constructor();
Object.getOwnPropertyNames(obj).forEach((prop) => {
newObj[prop] = obj[prop];
});
return newObj;
};
const listify = (obj, toItem) => {
if (!obj)
Expand Down Expand Up @@ -754,4 +761,4 @@ const template = (str, data, regex = /\{\{(.+?)\}\}/g) => {
}, str);
};

export { alphabetical, boil, camel as camal, camel, capitalize, chain, clone, cluster, compose, counting, dash, debounce, defer, diff, draw, first, flat, fork, get, group, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isString, isSymbol, iterate, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, toFloat, toInt, toggle, tryit as try, tryit, uid, unique, upperize, zip };
export { alphabetical, boil, camel as camal, camel, capitalize, chain, clone, cluster, compose, counting, dash, debounce, defer, diff, draw, first, flat, fork, get, group, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, toFloat, toInt, toggle, tryit as try, tryit, uid, unique, upperize, zip };
22 changes: 15 additions & 7 deletions cdn/radash.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ var radash = (function (exports) {
const isObject = (value) => {
return !!value && value.constructor === Object;
};
const isPrimitive = (value) => {
return value === void 0 || value === null || typeof value !== "object" && typeof value !== "function";
};
const isFunction = (value) => {
return !!(value && value.constructor && value.call && value.apply);
};
Expand Down Expand Up @@ -563,13 +566,17 @@ var radash = (function (exports) {
const lowerize = (obj) => mapKeys(obj, (k) => k.toLowerCase());
const upperize = (obj) => mapKeys(obj, (k) => k.toUpperCase());
const clone = (obj) => {
return Object.getOwnPropertyNames(obj).reduce(
(acc, name) => ({
...acc,
[name]: obj[name]
}),
{}
);
if (isPrimitive(obj)) {
return obj;
}
if (typeof obj === "function") {
return obj.bind({});
}
const newObj = new obj.constructor();
Object.getOwnPropertyNames(obj).forEach((prop) => {
newObj[prop] = obj[prop];
});
return newObj;
};
const listify = (obj, toItem) => {
if (!obj)
Expand Down Expand Up @@ -788,6 +795,7 @@ var radash = (function (exports) {
exports.isInt = isInt;
exports.isNumber = isNumber;
exports.isObject = isObject;
exports.isPrimitive = isPrimitive;
exports.isString = isString;
exports.isSymbol = isSymbol;
exports.iterate = iterate;
Expand Down
2 changes: 1 addition & 1 deletion cdn/radash.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "radash",
"version": "9.1.0",
"version": "9.2.0",
"description": "Functional utility library - modern, simple, typed, powerful",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.mjs",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export {
isInt,
isNumber,
isObject,
isPrimitive,
isString,
isSymbol
} from './typed'
36 changes: 27 additions & 9 deletions src/object.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isFunction, isObject } from './typed'
import { isFunction, isObject, isPrimitive } from './typed'

type LowercasedKeys<T extends Record<string, any>> = {
[P in keyof T & string as Lowercase<P>]: T[P]
Expand Down Expand Up @@ -124,14 +124,32 @@ export const lowerize = <T extends Record<string, any>>(obj: T) =>
export const upperize = <T extends Record<string, any>>(obj: T) =>
mapKeys(obj, k => k.toUpperCase()) as UppercasedKeys<T>

export const clone = <T extends object = object>(obj: T): T => {
return Object.getOwnPropertyNames(obj).reduce(
(acc, name) => ({
...acc,
[name]: obj[name as keyof T]
}),
{} as T
)
/**
* Creates a shallow copy of the given obejct/value.
* @param {*} obj value to clone
* @returns {*} shallow clone of the given value
*/
export const clone = <T>(obj: T): T => {
// Primitive values do not need cloning.
if (isPrimitive(obj)) {
return obj
}

// Binding a function to an empty object creates a copy function.
if (typeof obj === 'function') {
return obj.bind({})
}

// Access the constructor and create a new object. This method can create an array as well.
const newObj = new ((obj as Object).constructor as { new (): T })()

// Assign the props.
Object.getOwnPropertyNames(obj).forEach(prop => {
// Bypass type checking since the primitive cases are already checked in the beginning
;(newObj as any)[prop] = (obj as any)[prop]
})

return newObj
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/tests/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,50 @@ describe('object module', () => {
})

describe('clone function', () => {
test('copies the primitives', () => {
const arr = [
1.1,
'How you doin?',
false,
Symbol('key'),
BigInt('1'),
undefined,
null
]
for (const elm of arr) {
const newElm = _.clone(elm)
assert.equal(elm, newElm)
}
})
test('copies arrays', () => {
const arr = [{ a: 0 }, 1, 2, 3]
const result = _.clone(arr)

assert.notEqual(arr, result)
for (const i in result) {
assert.equal(arr[i], result[i])
}
})
test('copies functions', () => {
const fa = () => 0
const fb = _.clone(fa)

assert.notEqual(fa, fb)
assert.equal(fa(), fb())
})
test('copies objects (class instances) without losing the class type', () => {
class Data {
val = 0
}

const obj = new Data()
obj.val = 1
const result = _.clone(obj)

assert.notEqual(obj, result)
assert.equal(obj.constructor.name, result.constructor.name)
assert.equal(obj.val, result.val)
})
test('copies all attributes from object', () => {
const obj = {
x: 22,
Expand Down
25 changes: 25 additions & 0 deletions src/tests/typed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,31 @@ describe('typed module', () => {
})
})

describe('isPrimitive function', () => {
test('returns true for all the primitives', () => {
const arr = [
1.1,
'How you doin?',
false,
Symbol('key'),
BigInt('1'),
undefined,
null
]

for (const elm of arr) {
assert.isTrue(_.isPrimitive(elm))
}
}),
test('returns false for non-primitives', () => {
const arr = [new Date(), Number, {}, Object({}), () => 0, [1, 2]]

for (const elm of arr) {
assert.isFalse(_.isPrimitive(elm))
}
})
})

describe('isFunction function', () => {
test('returns false for null', () => {
const result = _.isFunction(null)
Expand Down
16 changes: 16 additions & 0 deletions src/typed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ export const isObject = (value: any): value is object => {
return !!value && value.constructor === Object
}

/**
* Checks if the given value is primitive.
*
* Primitive Types: number , string , boolean , symbol, bigint, undefined, null
*
* @param {*} value value to check
* @returns {boolean} result
*/
export const isPrimitive = (value: any): boolean => {
return (
value === undefined ||
value === null ||
(typeof value !== 'object' && typeof value !== 'function')
)
}

export const isFunction = (value: any): value is Function => {
return !!(value && value.constructor && value.call && value.apply)
}
Expand Down

0 comments on commit 6952a98

Please sign in to comment.