@qntm-code/utils
Version:
A collection of useful utility functions with associated TypeScript types. All functions have been unit tested.
145 lines (144 loc) • 4.8 kB
JavaScript
import { isPlainObject } from '../type-predicates/isPlainObject.js';
import { typeOf, ValueType } from '../type-predicates/typeOf.js';
/**
* Recursively (deep) clones native types, like Object, Array, RegExp, Date, Map, Set, Symbol, Error as well as primitives.
*/
export function clone(value, instanceClone = false) {
switch (typeOf(value)) {
case ValueType.object: {
return cloneObjectDeep(value, instanceClone);
}
case ValueType.map: {
return cloneMapDeep(value, instanceClone);
}
case ValueType.set: {
return cloneSetDeep(value, instanceClone);
}
case ValueType.array:
case ValueType.int8array:
case ValueType.uint8array:
case ValueType.uint8clampedarray:
case ValueType.int16array:
case ValueType.uint16array:
case ValueType.int32array:
case ValueType.uint32array:
case ValueType.float32array:
case ValueType.float64array:
case ValueType.bigint64array:
case ValueType.biguint64array: {
return cloneArrayDeep(value, instanceClone);
}
default: {
return cloneShallow(value);
}
}
}
function cloneShallow(value) {
switch (typeOf(value)) {
case ValueType.buffer: {
return cloneBuffer(value);
}
case ValueType.symbol: {
return cloneSymbol(value);
}
case ValueType.error: {
return Object.create(value);
}
case ValueType.date: {
return new Date(value);
}
case ValueType.regexp: {
return cloneRegExp(value);
}
case ValueType.moment: {
return value.clone();
}
}
return value;
}
function cloneObjectDeep(value, instanceClone) {
if (typeof instanceClone === 'function') {
return instanceClone(value);
}
if (instanceClone || isPlainObject(value)) {
const source = value;
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const ctor = value.constructor;
const cloned = (ctor === undefined ? Object.create(null) : new ctor());
// Only clone own enumerable properties (matches `isEqual` semantics)
for (const key of Object.keys(source)) {
cloned[key] = clone(source[key], instanceClone);
}
return cloned;
}
return value;
}
function cloneArrayDeep(value, instanceClone) {
const length = value.length;
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const cloned = new value.constructor(length);
for (let i = 0; i < length; i++) {
cloned[i] = clone(value[i], instanceClone);
}
return cloned;
}
function cloneMapDeep(value, instanceClone) {
const cloned = new Map();
const nestedInstanceClone = instanceClone;
for (const [key, item] of value.entries()) {
const clonedKey = clone(key, nestedInstanceClone);
const clonedValue = clone(item, nestedInstanceClone);
cloned.set(clonedKey, clonedValue);
}
return cloned;
}
function cloneSetDeep(value, instanceClone) {
const cloned = new Set();
const nestedInstanceClone = instanceClone;
for (const item of value.values()) {
cloned.add(clone(item, nestedInstanceClone));
}
return cloned;
}
function cloneRegExp(value) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const cloned = new value.constructor(value.source, value.flags);
cloned.lastIndex = value.lastIndex;
return cloned;
}
function cloneBuffer(value) {
const length = value.length;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(length) : Buffer.from(length);
value.copy(buffer);
return buffer;
}
function cloneSymbol(value) {
// Symbols are primitives and cannot be truly cloned; create a new symbol with the same description.
const description = value.description;
// Preserve well-known symbols (they cannot be recreated)
const wellKnownSymbolKeys = [
'asyncIterator',
'hasInstance',
'isConcatSpreadable',
'iterator',
'match',
'matchAll',
'replace',
'search',
'species',
'split',
'toPrimitive',
'toStringTag',
'unscopables',
'dispose',
'asyncDispose',
];
for (const key of wellKnownSymbolKeys) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (Symbol[key] === value) {
return value;
}
}
return Symbol(description);
}