177 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
const Reach = require('./reach');
 | 
						|
const Types = require('./types');
 | 
						|
const Utils = require('./utils');
 | 
						|
 | 
						|
 | 
						|
const internals = {
 | 
						|
    needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap])
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
module.exports = internals.clone = function (obj, options = {}, _seen = null) {
 | 
						|
 | 
						|
    if (typeof obj !== 'object' ||
 | 
						|
        obj === null) {
 | 
						|
 | 
						|
        return obj;
 | 
						|
    }
 | 
						|
 | 
						|
    let clone = internals.clone;
 | 
						|
    let seen = _seen;
 | 
						|
 | 
						|
    if (options.shallow) {
 | 
						|
        if (options.shallow !== true) {
 | 
						|
            return internals.cloneWithShallow(obj, options);
 | 
						|
        }
 | 
						|
 | 
						|
        clone = (value) => value;
 | 
						|
    }
 | 
						|
    else if (seen) {
 | 
						|
        const lookup = seen.get(obj);
 | 
						|
        if (lookup) {
 | 
						|
            return lookup;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        seen = new Map();
 | 
						|
    }
 | 
						|
 | 
						|
    // Built-in object types
 | 
						|
 | 
						|
    const baseProto = Types.getInternalProto(obj);
 | 
						|
    if (baseProto === Types.buffer) {
 | 
						|
        return Buffer && Buffer.from(obj);              // $lab:coverage:ignore$
 | 
						|
    }
 | 
						|
 | 
						|
    if (baseProto === Types.date) {
 | 
						|
        return new Date(obj.getTime());
 | 
						|
    }
 | 
						|
 | 
						|
    if (baseProto === Types.regex) {
 | 
						|
        return new RegExp(obj);
 | 
						|
    }
 | 
						|
 | 
						|
    // Generic objects
 | 
						|
 | 
						|
    const newObj = internals.base(obj, baseProto, options);
 | 
						|
    if (newObj === obj) {
 | 
						|
        return obj;
 | 
						|
    }
 | 
						|
 | 
						|
    if (seen) {
 | 
						|
        seen.set(obj, newObj);                              // Set seen, since obj could recurse
 | 
						|
    }
 | 
						|
 | 
						|
    if (baseProto === Types.set) {
 | 
						|
        for (const value of obj) {
 | 
						|
            newObj.add(clone(value, options, seen));
 | 
						|
        }
 | 
						|
    }
 | 
						|
    else if (baseProto === Types.map) {
 | 
						|
        for (const [key, value] of obj) {
 | 
						|
            newObj.set(key, clone(value, options, seen));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    const keys = Utils.keys(obj, options);
 | 
						|
    for (const key of keys) {
 | 
						|
        if (key === '__proto__') {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if (baseProto === Types.array &&
 | 
						|
            key === 'length') {
 | 
						|
 | 
						|
            newObj.length = obj.length;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        const descriptor = Object.getOwnPropertyDescriptor(obj, key);
 | 
						|
        if (descriptor) {
 | 
						|
            if (descriptor.get ||
 | 
						|
                descriptor.set) {
 | 
						|
 | 
						|
                Object.defineProperty(newObj, key, descriptor);
 | 
						|
            }
 | 
						|
            else if (descriptor.enumerable) {
 | 
						|
                newObj[key] = clone(obj[key], options, seen);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
                Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) });
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            Object.defineProperty(newObj, key, {
 | 
						|
                enumerable: true,
 | 
						|
                writable: true,
 | 
						|
                configurable: true,
 | 
						|
                value: clone(obj[key], options, seen)
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return newObj;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
internals.cloneWithShallow = function (source, options) {
 | 
						|
 | 
						|
    const keys = options.shallow;
 | 
						|
    options = Object.assign({}, options);
 | 
						|
    options.shallow = false;
 | 
						|
 | 
						|
    const seen = new Map();
 | 
						|
 | 
						|
    for (const key of keys) {
 | 
						|
        const ref = Reach(source, key);
 | 
						|
        if (typeof ref === 'object' ||
 | 
						|
            typeof ref === 'function') {
 | 
						|
 | 
						|
            seen.set(ref, ref);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return internals.clone(source, options, seen);
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
internals.base = function (obj, baseProto, options) {
 | 
						|
 | 
						|
    if (options.prototype === false) {                  // Defaults to true
 | 
						|
        if (internals.needsProtoHack.has(baseProto)) {
 | 
						|
            return new baseProto.constructor();
 | 
						|
        }
 | 
						|
 | 
						|
        return baseProto === Types.array ? [] : {};
 | 
						|
    }
 | 
						|
 | 
						|
    const proto = Object.getPrototypeOf(obj);
 | 
						|
    if (proto &&
 | 
						|
        proto.isImmutable) {
 | 
						|
 | 
						|
        return obj;
 | 
						|
    }
 | 
						|
 | 
						|
    if (baseProto === Types.array) {
 | 
						|
        const newObj = [];
 | 
						|
        if (proto !== baseProto) {
 | 
						|
            Object.setPrototypeOf(newObj, proto);
 | 
						|
        }
 | 
						|
 | 
						|
        return newObj;
 | 
						|
    }
 | 
						|
 | 
						|
    if (internals.needsProtoHack.has(baseProto)) {
 | 
						|
        const newObj = new proto.constructor();
 | 
						|
        if (proto !== baseProto) {
 | 
						|
            Object.setPrototypeOf(newObj, proto);
 | 
						|
        }
 | 
						|
 | 
						|
        return newObj;
 | 
						|
    }
 | 
						|
 | 
						|
    return Object.create(proto);
 | 
						|
};
 |