Skip to content

Commit f1c7f92

Browse files
authored
fix(cloneDeepWith): handle cloning of Boolean, Number and String objects (#1372)
1 parent d6489a3 commit f1c7f92

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

src/object/cloneDeep.spec.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,4 +402,89 @@ describe('cloneDeep', () => {
402402
expect(clonedInstance.value).toBe(instance.value);
403403
expect(clonedInstance.getValue()).toBe(123);
404404
});
405+
406+
it('should clone arguments objects', () => {
407+
function func() {
408+
// eslint-disable-next-line prefer-rest-params
409+
return cloneDeep(arguments);
410+
}
411+
// @ts-expect-error: arguments object allows calling with parameters despite no formal params
412+
const args = func(1, 2, 3);
413+
const cloned = cloneDeep(args);
414+
415+
expect(cloned).toEqual(args);
416+
expect(cloned).not.toBe(args);
417+
});
418+
419+
it('should clone Boolean objects', () => {
420+
const boolObj = new Boolean(true);
421+
const cloned = cloneDeep(boolObj);
422+
423+
expect(cloned).toEqual(boolObj);
424+
expect(cloned).not.toBe(boolObj);
425+
expect(cloned).toBeInstanceOf(Boolean);
426+
});
427+
428+
it('should clone String objects', () => {
429+
const strObj = new String('es-toolkit');
430+
const cloned = cloneDeep(strObj);
431+
432+
expect(cloned).toEqual(strObj);
433+
expect(cloned).not.toBe(strObj);
434+
expect(cloned).toBeInstanceOf(String);
435+
});
436+
437+
it('should clone Number objects', () => {
438+
const numObj = new Number(42);
439+
const cloned = cloneDeep(numObj);
440+
441+
expect(cloned).toEqual(numObj);
442+
expect(cloned).not.toBe(numObj);
443+
expect(cloned).toBeInstanceOf(Number);
444+
});
445+
446+
it('should clone Float32Array', () => {
447+
const arr = new Float32Array([1.1, 2.2, 3.3]);
448+
const cloned = cloneDeep(arr);
449+
450+
expect(cloned).toEqual(arr);
451+
expect(cloned).not.toBe(arr);
452+
expect(cloned).toBeInstanceOf(Float32Array);
453+
});
454+
455+
it('should clone Float64Array', () => {
456+
const arr = new Float64Array([1.1, 2.2, 3.3]);
457+
const cloned = cloneDeep(arr);
458+
459+
expect(cloned).toEqual(arr);
460+
expect(cloned).not.toBe(arr);
461+
expect(cloned).toBeInstanceOf(Float64Array);
462+
});
463+
464+
it('should clone Int8Array', () => {
465+
const arr = new Int8Array([1, 2, 3]);
466+
const cloned = cloneDeep(arr);
467+
468+
expect(cloned).toEqual(arr);
469+
expect(cloned).not.toBe(arr);
470+
expect(cloned).toBeInstanceOf(Int8Array);
471+
});
472+
473+
it('should clone Int16Array', () => {
474+
const arr = new Int16Array([1, 2, 3]);
475+
const cloned = cloneDeep(arr);
476+
477+
expect(cloned).toEqual(arr);
478+
expect(cloned).not.toBe(arr);
479+
expect(cloned).toBeInstanceOf(Int16Array);
480+
});
481+
482+
it('should clone Int32Array', () => {
483+
const arr = new Int32Array([1, 2, 3]);
484+
const cloned = cloneDeep(arr);
485+
486+
expect(cloned).toEqual(arr);
487+
expect(cloned).not.toBe(arr);
488+
expect(cloned).toBeInstanceOf(Int32Array);
489+
});
405490
});

src/object/cloneDeepWith.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,27 @@ export function cloneDeepWithImpl<T>(
223223
return result as T;
224224
}
225225

226+
if (valueToClone instanceof Boolean) {
227+
const result = new Boolean(valueToClone.valueOf()) as T;
228+
stack.set(valueToClone, result);
229+
copyProperties(result, valueToClone, objectToClone, stack, cloneValue);
230+
return result;
231+
}
232+
233+
if (valueToClone instanceof Number) {
234+
const result = new Number(valueToClone.valueOf()) as T;
235+
stack.set(valueToClone, result);
236+
copyProperties(result, valueToClone, objectToClone, stack, cloneValue);
237+
return result;
238+
}
239+
240+
if (valueToClone instanceof String) {
241+
const result = new String(valueToClone.valueOf()) as T;
242+
stack.set(valueToClone, result);
243+
copyProperties(result, valueToClone, objectToClone, stack, cloneValue);
244+
return result;
245+
}
246+
226247
if (typeof valueToClone === 'object' && isCloneableObject(valueToClone)) {
227248
const result = Object.create(Object.getPrototypeOf(valueToClone));
228249

src/object/merge.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,15 @@ describe('merge', () => {
107107

108108
expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } });
109109
});
110+
111+
it('should skip unsafe properties like __proto__', () => {
112+
const target = { a: 1 };
113+
const source = Object.create(null);
114+
source.__proto__ = { b: 2 };
115+
source.a = 2;
116+
const result = merge(target, source);
117+
118+
expect(result).toEqual({ a: 2 });
119+
expect(result.__proto__).toBe(Object.prototype);
120+
});
110121
});

src/object/mergeWith.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,17 @@ describe('mergeWith', () => {
7676
})
7777
).toEqual({ prop: null });
7878
});
79+
80+
it('should skip unsafe properties like __proto__', () => {
81+
const target = { a: 1 };
82+
const source = Object.create(null);
83+
source.__proto__ = { b: 2 };
84+
source.a = 2;
85+
const result = mergeWith(target, source, (targetValue, sourceValue) => {
86+
return sourceValue;
87+
});
88+
89+
expect(result).toEqual({ a: 2 });
90+
expect(result.__proto__).toBe(Object.prototype);
91+
});
7992
});

0 commit comments

Comments
 (0)