Spaces:
Paused
Paused
| ; | |
| 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]), | |
| structuredCloneExists: typeof structuredClone === 'function' | |
| }; | |
| 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); | |
| switch (baseProto) { | |
| case Types.buffer: | |
| return Buffer?.from(obj); | |
| case Types.date: | |
| return new Date(obj.getTime()); | |
| case Types.regex: | |
| case Types.url: | |
| return new baseProto.constructor(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; | |
| } | |
| // Can only be covered in node 21+ | |
| /* $lab:coverage:off$ */ | |
| if (internals.structuredCloneExists && | |
| baseProto === Types.error && | |
| key === 'stack') { | |
| continue; // Already a part of the base object | |
| } | |
| /* $lab:coverage:on$ */ | |
| 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; | |
| } | |
| // Can only be covered in node 21+ | |
| /* $lab:coverage:off$ */ | |
| else if (baseProto === Types.error && internals.structuredCloneExists && | |
| (proto === baseProto || Error.isPrototypeOf(proto.constructor))) { // Don't match Util.inherit() subclassed errors | |
| const err = structuredClone(obj); // Needed to copy internal stack state | |
| if (Object.getPrototypeOf(err) !== proto) { | |
| Object.setPrototypeOf(err, proto); // Fix prototype | |
| } | |
| return err; | |
| } | |
| /* $lab:coverage:on$ */ | |
| if (internals.needsProtoHack.has(baseProto)) { | |
| const newObj = new proto.constructor(); | |
| if (proto !== baseProto) { | |
| Object.setPrototypeOf(newObj, proto); | |
| } | |
| return newObj; | |
| } | |
| return Object.create(proto); | |
| }; | |