├── .babelrc ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── __test__ └── omniclone.test.js ├── dist └── main.js ├── package-lock.json ├── package.json ├── src ├── deepClone.js ├── handlers │ ├── arrayBufferObjectsHandler.js │ ├── dateObjectsHandler.js │ ├── errorsObjectsHandler.js │ ├── mapEntriesHandler.js │ ├── otherObjectsDescriptorsHandler.js │ ├── primitiveObjectsHandler.js │ ├── regexpObjectsHanlder.js │ ├── setEntriesHandler.js │ └── typedArraysObjectsHandler.js ├── omniclone.js ├── propsHandler.js ├── updateReferences.js └── utility │ ├── createDependenciesMap.js │ ├── dependenciesMapHandler.js │ ├── prevReferencesHelper.js │ ├── safeReferencesHelper.js │ ├── updateCircReferencesIntoMapObjects.js │ ├── updateCircReferencesIntoOtherObjects.js │ └── updateCircReferencesIntoSetObjects.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb-base", 4 | "prettier", 5 | "plugin:jest/recommended" 6 | ], 7 | "plugins": [ 8 | "prettier", 9 | "jest" 10 | ], 11 | "rules": { 12 | "prettier/prettier": "error", 13 | "linebreak-style": 0, 14 | "no-shadow": "off", 15 | "no-restricted-syntax": "off", 16 | "no-new-wrappers": "off", 17 | "no-continue": "off", 18 | "jest/no-disabled-tests": "warn", 19 | "jest/no-focused-tests": "error", 20 | "jest/no-identical-title": "error", 21 | "jest/prefer-to-have-length": "warn", 22 | "jest/valid-expect": "error" 23 | } 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage/ 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | <<<<<<< HEAD 81 | .dynamodb/ 82 | ======= 83 | .dynamodb/ 84 | >>>>>>> e34632c8f535718a3a6a54e8c7a6ee6fab79751f 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrea Simone Costa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://img.shields.io/npm/v/omniclone.svg)](https://www.npmjs.com/package/omniclone) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/jfet97/omniclone/blob/master/LICENSE) ![](https://img.shields.io/npm/dt/omniclone.svg) ![](https://img.shields.io/badge/dependencies-no%20dependencies-%231e88e5%20.svg) 2 | # omniclone 3 | An isomorphic and configurable javascript function for object deep cloning. 4 | ```js 5 | omniclone(source [, config, [, visitor]]); 6 | ``` 7 | 8 | Example: 9 | ```js 10 | const obj = { foo: { bar: 'baz' } }; 11 | const obj2 = omniclone(obj); 12 | 13 | obj2; // { foo: { bar: 'baz' } }; 14 | obj === obj2; // false 15 | obj.foo === obj2.foo; // false 16 | ``` 17 | ## installation 18 | ``` 19 | $ npm install --save omniclone 20 | ``` 21 | 22 | ## usage 23 | ``` 24 | const omniclone = require('omniclone'); 25 | import omniclone from 'omniclone'; 26 | ``` 27 | 28 | ## test 29 | [Test it](https://npm.runkit.com/omniclone) in no time with RunKit! 30 | 31 | ## strengths 32 | 1. automatically invoke object constructors before copying properties (customizable behavior) 33 | 2. let you to share the `[[Prototype]]` object between source and the resulting object (customizable behavior) 34 | 3. let you to clone objects with circular references (customizable behavior) 35 | 4. let you to copy getters and setters, non enumerables properties and also symbols (customizable behavior) 36 | 5. correct handling of String, Boolean, Number, Error, Promise, Map, Set, WeakMap, WeakSet, ArrayBuffer, TypedArray and DataView objects 37 | 6. similar references are not duplicated 38 | 7. correct cloning of Array objects 39 | 8. correct cloning of RegExp and Date objects 40 | 9. let you define custom cloning logic 41 | 42 | ## config 43 | 44 | ### invokeConstructors (default true) 45 | If you need to invoke the objects constructors for each object prop set the `invokeConstructors` flag to `true`: 46 | ```js 47 | const res = omniclone(source, { 48 | invokeConstructors: true 49 | }); 50 | ``` 51 | This option will correctly set up the new object, because __constructors are invoked to create it__. The resulting object and each of its object property therefore will have the `[[Prototype]]` and the `constructor` props correctly setted up, corresponding to the source object and its object properties for everyone. 52 | 53 | ```js 54 | class Test { 55 | constructor() { 56 | console.log('constructor invoked'); 57 | } 58 | }; 59 | 60 | const t = new Test(); // 'constructor invoked' 61 | t.foo = new Test(); // 'constructor invoked' 62 | t; // Test { t: Test {} } 63 | 64 | const res = omniclone(t, { 65 | invokeConstructors: true 66 | }); // 'constructor invoked' 'constructor invoked' 67 | 68 | res; // Test { t: Test {} } 69 | res instanceof Test; // true 70 | res.foo instanceof Test; // true 71 | ``` 72 | 73 | It is actually a default enabled setting, but you can disable it (Array, Map and Set objects will be properly cloned anyway).\ 74 | __It is highly discouraged to disable this flag__, do it only if you know what you are doing. 75 | 76 | If the `invokeConstructors` flag is set to `false`, a plain new object will be created for each object prop and for the resulting object as well. So the `constructor` prop will be set to the `Object` function, and the `[[Prototype]]` prop will be `Object.prototype`.\ 77 | Unless you use the `setPrototype` flag. 78 | 79 | 80 | ### setPrototype (default false) 81 | If the `invokeConstructors` flag is setted to `false` we could anyway share the `[[Prototype]]` object between the source object and the resulting object thanks to the `setPrototype` flag, __without calling the constructors__.\ 82 | (Array, Map and Set objects will be properly cloned anyway because for them the constructor will be always called).\ 83 | This means that the `constructor` prop will be shared as well because it is related to the `[[Prototype]]` prop.\ 84 | This flag affects all the object properties as weel, like the previous flag.\ 85 | If the `invokeConstructors` flag is setted to `true`, the `setPrototype` flag will be is ignored. 86 | 87 | ```js 88 | const res = omniclone(source, { 89 | invokeConstructors: false, 90 | setPrototype: true 91 | }); 92 | ``` 93 | 94 | The resulting object therefore will have the `[[Prototype]]` and the `constructor` props correctly setted up, but the constructors are not invoked. 95 | 96 | ```js 97 | class Test { 98 | constructor() { 99 | console.log('constructor invoked'); 100 | } 101 | }; 102 | 103 | const t = new Test(); // 'constructor invoked' 104 | t.foo = new Test(); // 'constructor invoked' 105 | t; // Test { t: Test {} } 106 | 107 | const res = omniclone(t, { 108 | invokeConstructors: false, 109 | setPrototype: true 110 | }); 111 | 112 | res; // Test { t: Test {} } 113 | res instanceof Test; // true 114 | res.foo instanceof Test; // true 115 | ``` 116 | 117 | ```js 118 | const prot = { foo:'bar' }; 119 | 120 | const obj1 = Object.create(prot); 121 | Object.getPrototypeOf(obj1) === prot; // true 122 | 123 | const res = omniclone(obj1, { 124 | invokeConstructors: false, 125 | setPrototype: true 126 | }); 127 | Object.getPrototypeOf(res) === prot; // true 128 | ``` 129 | 130 | ### copyNonEnumerables (default false) 131 | Enable it to deep copy also non enumerables properties.\ 132 | Disable it to ignore them. 133 | ```js 134 | const res = omniclone(source, { 135 | copyNonEnumerables: true 136 | }); 137 | ``` 138 | 139 | ### copySymbols (default false) 140 | Enable it to deep copy also symbol properties.\ 141 | Disable it to ignore them.\ 142 | Symbols are shallow copied; 143 | ```js 144 | const res = omniclone(source, { 145 | copySymbols: true 146 | }); 147 | ``` 148 | 149 | ### copyGettersSetters (default false) 150 | Enable it to copy also getters and setters.\ 151 | Disable it to ignore them. 152 | ```js 153 | const res = omniclone(source, { 154 | copyGettersSetters: true 155 | }); 156 | ``` 157 | Odds are that to properly copy gets&setts you have also to enable the `copyNonEnumerables` flag. 158 | 159 | ### allowCircularReferences (default true) 160 | Enable it to allow circular references.\ 161 | Disable it to throw an error if one is met. 162 | Know that `omniclone` is more performing with this flag __enabled__, so disable it only if you really need. 163 | ```js 164 | const res = omniclone(source, { 165 | allowCircularReferences: true 166 | }); 167 | ``` 168 | 169 | ### discardErrorObjects (default true) 170 | Enable it to discard Error objects props.\ 171 | Know that `omnicopy` will return `null` if one is passed as source.\ 172 | Disable it to throw an error if one is met. 173 | ```js 174 | const res = omniclone(source, { 175 | discardErrorObjects: true 176 | }); 177 | ``` 178 | 179 | ## default config 180 | The default config is the following: 181 | ```js 182 | omniclone(source, { 183 | invokeConstructors : true, 184 | setPrototype : false, 185 | copyNonEnumerables : false, 186 | copySymbols : false, 187 | copyGettersSetters : false, 188 | allowCircularReferences: true, 189 | discardErrorObjects: true, 190 | }); 191 | ``` 192 | 193 | ## what about String, Boolean, Number, Error, Promise, Map, Set, WeakMap, WeakSet, ArrayBuffer, TypedArray and DataView objects? 194 | 195 | String, Boolean and Number objects passed to `omniclone` as sources will produce `null`.\ 196 | Error objects passed to `omniclone` as sources will produce `null` if the `discardErrorObjects` is set to `true` (as default).\ 197 | Error objects passed to `omniclone` as sources will produce a `TypeError` if the `discardErrorObjects` is set to `false` (not the predefined behaviour). 198 | 199 | String, Boolean and Number objects props will be unwrapped.\ 200 | Error objects props will be discarded if the `discardErrorObjects` is set to `true` (as default).\ 201 | Error objects props will produce a `TypeError` if the `discardErrorObjects` is set to `false` (not the predefined behaviour). 202 | 203 | Promise, WeakMap and WeakSet objects will be returned if passed to `omniclone` as sources.\ 204 | Promise, WeakMap and WeakSet objects props will be copied by reference. 205 | 206 | Map objects (keys/values) will be always deeply cloned, but any properties added to the map object itself will not be copied.\ 207 | Set objects will be always deeply cloned, but any properties added to the set object itself will not be copied.\ 208 | ArrayBuffer and TypedArray objects will be always deeply cloned, but any properties added to the array objects themselves will not be copied. 209 | DataView objects are copied by reference. 210 | 211 | ## what about the 6th strength? 212 | 213 | To understand it, let's compare the function `omniclone` with the well-know `JSON.parse(JSON.stringify(source))`: 214 | ```js 215 | const obj = { foo: 'bar'}; 216 | const source = { 217 | a: obj, 218 | b: obj, 219 | }; 220 | 221 | JSON.stringify(source); // '{"a":{"foo":"bar"},"b":{"foo":"bar"}}' 222 | ``` 223 | When you will use `JSON.parse()`, an `{"foo":"bar"}` object will be created for the `a` prop and a `{"foo":"bar"}` distinct object will be created for the `b` prop. But this is not the initial situation where `source.a == source.b; // true`. 224 | 225 | ## how to define custom cloning logic? 226 | You can define a callback function that will be called on each node which copy can be customized: 227 | ```js 228 | function visitor(node, config) { 229 | // do stuff 230 | } 231 | ``` 232 | The function will receive the `node` and a copy of the `config` object. If the function returns something different than `undefined`, that returned value will become the cloned value. On the contrary if the function returns `undefined` the default cloning algorithm will be used. 233 | 234 | You cannot overwrite the default algorithm logic for String, Boolean, Number, Error, Promise, Map, Set, WeakMap, WeakSet, Date and RegExp objects. 235 | You can overwrite the default algorithm logic for Array, ArrayBuffer, TypedArray, DataView, plain js and custom objects. 236 | 237 | Let's see an example where we add custom logic to properly clone a Node.js Buffer object: 238 | ```js 239 | const buffer = Buffer.from([1, 2, 3, 4]); 240 | 241 | const resBuffer = omniclone(buffer, {}, node => { 242 | if (node instanceof Buffer) { // it affects only Buffer objects 243 | return Buffer.from(node); 244 | } 245 | return undefined; // all the other nodes will be cloned as usual 246 | }); 247 | ``` 248 | Thanks to the `instanceof` check, only Buffer objects will be affected by the intervention of the visitor callback. 249 | 250 | 251 | ## warnings and limitations 252 | 1. promises and methods are always copied by reference 253 | 2. `super` is statically bound to a class heirarchy, remember it 254 | 3. `Error` objects cannot be properly copied because of js limitations 255 | 4. currently there is no isomorphic way to detect if an object is a `Proxy` nor is possible to access the handler object. Because of transparent virtualization, `omniclone` will copy each properties, the `constructor` and the `[[Prototype]]` directly from the proxed object. 256 | 257 | ## support 258 | Let me know that you like this project: star it! Thanks :) 259 | -------------------------------------------------------------------------------- /__test__/omniclone.test.js: -------------------------------------------------------------------------------- 1 | const omniclone = require("../src/omniclone"); 2 | 3 | describe("it works", () => { 4 | it("should work", () => { 5 | expect(true).toBe(true); 6 | }); 7 | }); 8 | 9 | describe("omniclone", () => { 10 | it("should import omniclone", () => { 11 | const t = omniclone(); 12 | expect(t).toBeDefined(); 13 | }); 14 | 15 | it(`should set setPrototype to false, invokeConstructors to true, discardErrorObjects to true, 16 | copyNonEnumerables to false, copySymbols to false, copyGettersSetters to false and 17 | allowCircularReferences to true if the config argument is undefined`, () => { 18 | (() => { 19 | let i = 0; 20 | class Test { 21 | constructor() { 22 | i += 1; 23 | } 24 | } 25 | const testObj = new Test(); 26 | 27 | Object.defineProperty(testObj, "notEnum", { 28 | enumerable: false, 29 | value: 42 30 | }); 31 | testObj[Symbol("symbol")] = "value"; 32 | Object.defineProperty(testObj, "g&s", { 33 | get: () => 42, 34 | set: () => {} 35 | }); 36 | 37 | const res = omniclone(testObj); 38 | 39 | expect(i).toBe(2); // +1 because of the call to create testObj 40 | expect(res.constructor).toBe(Test); 41 | expect(Object.getPrototypeOf(res)).toBe(Test.prototype); 42 | expect(res.notEnum).toBeUndefined(); 43 | expect(res[Symbol("symbol")]).toBeUndefined(); 44 | expect(res["g&s"]).toBeUndefined(); 45 | })(); 46 | 47 | expect(() => { 48 | const ob1 = {}; 49 | ob1.ob1 = ob1; 50 | omniclone(ob1); 51 | }).not.toThrow(TypeError("TypeError: circular reference found")); 52 | 53 | (() => { 54 | class MyError extends Error {} 55 | const res = omniclone(new MyError()); 56 | expect(res).toBeNull(); 57 | })(); 58 | 59 | (() => { 60 | class MyError extends Error {} 61 | const ob1 = { e: new MyError() }; 62 | const res = omniclone(ob1); 63 | expect(res.e).toBeUndefined(); 64 | })(); 65 | }); 66 | 67 | it("should throw a TypeError when omniclone is called with an invalid 'customHandler' argument's type", () => { 68 | expect(() => { 69 | omniclone({}, {}, null); 70 | }).toThrow( 71 | TypeError("TypeError: invalid 'customHandler' arguments's type") 72 | ); 73 | 74 | expect(() => { 75 | omniclone({}, {}, {}); 76 | }).toThrow( 77 | TypeError("TypeError: invalid 'customHandler' arguments's type") 78 | ); 79 | 80 | expect(() => { 81 | omniclone({}, {}, "foo"); 82 | }).toThrow( 83 | TypeError("TypeError: invalid 'customHandler' arguments's type") 84 | ); 85 | 86 | expect(() => { 87 | omniclone({}, {}, 42); 88 | }).toThrow( 89 | TypeError("TypeError: invalid 'customHandler' arguments's type") 90 | ); 91 | 92 | expect(() => { 93 | omniclone({}, {}, Symbol("test")); 94 | }).toThrow( 95 | TypeError("TypeError: invalid 'customHandler' arguments's type") 96 | ); 97 | }); 98 | 99 | it("should throw a TypeError when omniclone is called with an invalid 'obj' argument's type", () => { 100 | expect(() => { 101 | omniclone(null); 102 | }).toThrow(TypeError("TypeError: invalid 'obj' argument's type")); 103 | 104 | expect(() => { 105 | omniclone(() => {}); 106 | }).toThrow(TypeError("TypeError: invalid 'obj' argument's type")); 107 | 108 | expect(() => { 109 | omniclone("foo"); 110 | }).toThrow(TypeError("TypeError: invalid 'obj' argument's type")); 111 | 112 | expect(() => { 113 | omniclone(42); 114 | }).toThrow(TypeError("TypeError: invalid 'obj' argument's type")); 115 | 116 | expect(() => { 117 | omniclone(Symbol("test")); 118 | }).toThrow(TypeError("TypeError: invalid 'obj' argument's type")); 119 | }); 120 | 121 | it("should throw a TypeError when omniclone is called with an Error object and the discardErrorObjects is set to false", () => { 122 | expect(() => { 123 | omniclone(new Error(), { discardErrorObjects: false }); 124 | }).toThrow(TypeError("TypeError: cannot copy Error objects")); 125 | 126 | expect(() => { 127 | class CustomError extends Error {} 128 | omniclone(new CustomError(), { discardErrorObjects: false }); 129 | }).toThrow(TypeError("TypeError: cannot copy Error objects")); 130 | }); 131 | 132 | it("should throw a TypeError when the discardErrorObjects flag setted into the config object passed to omniclone has not Boolean type", () => { 133 | expect(() => { 134 | omniclone({}, { discardErrorObjects: () => {} }); 135 | }).toThrow( 136 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 137 | ); 138 | 139 | expect(() => { 140 | omniclone({}, { discardErrorObjects: {} }); 141 | }).toThrow( 142 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 143 | ); 144 | 145 | expect(() => { 146 | omniclone({}, { discardErrorObjects: 42 }); 147 | }).toThrow( 148 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 149 | ); 150 | 151 | expect(() => { 152 | omniclone({}, { discardErrorObjects: "" }); 153 | }).toThrow( 154 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 155 | ); 156 | 157 | expect(() => { 158 | omniclone({}, { discardErrorObjects: "foo" }); 159 | }).toThrow( 160 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 161 | ); 162 | 163 | expect(() => { 164 | omniclone({}, { discardErrorObjects: null }); 165 | }).toThrow( 166 | TypeError("TypeError: invalid 'discardErrorObjects' flag's type") 167 | ); 168 | }); 169 | 170 | it("should throw a TypeError when the setPrototype flag setted into the config object passed to omniclone has not Boolean type", () => { 171 | expect(() => { 172 | omniclone({}, { setPrototype: () => {} }); 173 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 174 | 175 | expect(() => { 176 | omniclone({}, { setPrototype: {} }); 177 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 178 | 179 | expect(() => { 180 | omniclone({}, { setPrototype: 42 }); 181 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 182 | 183 | expect(() => { 184 | omniclone({}, { setPrototype: "" }); 185 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 186 | 187 | expect(() => { 188 | omniclone({}, { setPrototype: "foo" }); 189 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 190 | 191 | expect(() => { 192 | omniclone({}, { setPrototype: null }); 193 | }).toThrow(TypeError("TypeError: invalid 'setPrototype' flag's type")); 194 | }); 195 | 196 | it("should throw a TypeError when the invokeConstructors flag setted into the config object passed to omniclone has not Boolean type", () => { 197 | expect(() => { 198 | omniclone({}, { invokeConstructors: () => {} }); 199 | }).toThrow( 200 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 201 | ); 202 | 203 | expect(() => { 204 | omniclone({}, { invokeConstructors: {} }); 205 | }).toThrow( 206 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 207 | ); 208 | 209 | expect(() => { 210 | omniclone({}, { invokeConstructors: 42 }); 211 | }).toThrow( 212 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 213 | ); 214 | 215 | expect(() => { 216 | omniclone({}, { invokeConstructors: "" }); 217 | }).toThrow( 218 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 219 | ); 220 | 221 | expect(() => { 222 | omniclone({}, { invokeConstructors: "foo" }); 223 | }).toThrow( 224 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 225 | ); 226 | 227 | expect(() => { 228 | omniclone({}, { invokeConstructors: null }); 229 | }).toThrow( 230 | TypeError("TypeError: invalid 'invokeConstructors' flag's type") 231 | ); 232 | }); 233 | 234 | it("should throw a TypeError when the copyNonEnumerables flag setted into the config object passed to omniclone has not Boolean type", () => { 235 | expect(() => { 236 | omniclone({}, { copyNonEnumerables: () => {} }); 237 | }).toThrow( 238 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 239 | ); 240 | 241 | expect(() => { 242 | omniclone({}, { copyNonEnumerables: {} }); 243 | }).toThrow( 244 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 245 | ); 246 | 247 | expect(() => { 248 | omniclone({}, { copyNonEnumerables: 42 }); 249 | }).toThrow( 250 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 251 | ); 252 | 253 | expect(() => { 254 | omniclone({}, { copyNonEnumerables: "" }); 255 | }).toThrow( 256 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 257 | ); 258 | 259 | expect(() => { 260 | omniclone({}, { copyNonEnumerables: "foo" }); 261 | }).toThrow( 262 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 263 | ); 264 | 265 | expect(() => { 266 | omniclone({}, { copyNonEnumerables: null }); 267 | }).toThrow( 268 | TypeError("TypeError: invalid 'copyNonEnumerables' flag's type") 269 | ); 270 | }); 271 | 272 | it("should throw a TypeError when the copySymbols flag setted into the config object passed to omniclone has not Boolean type", () => { 273 | expect(() => { 274 | omniclone({}, { copySymbols: () => {} }); 275 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 276 | 277 | expect(() => { 278 | omniclone({}, { copySymbols: {} }); 279 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 280 | 281 | expect(() => { 282 | omniclone({}, { copySymbols: 42 }); 283 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 284 | 285 | expect(() => { 286 | omniclone({}, { copySymbols: "" }); 287 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 288 | 289 | expect(() => { 290 | omniclone({}, { copySymbols: "foo" }); 291 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 292 | 293 | expect(() => { 294 | omniclone({}, { copySymbols: null }); 295 | }).toThrow(TypeError("TypeError: invalid 'copySymbols' flag's type")); 296 | }); 297 | 298 | it("should throw a TypeError when the copyGettersSetters flag setted into the config object passed to omniclone has not Boolean type", () => { 299 | expect(() => { 300 | omniclone({}, { copyGettersSetters: () => {} }); 301 | }).toThrow( 302 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 303 | ); 304 | 305 | expect(() => { 306 | omniclone({}, { copyGettersSetters: {} }); 307 | }).toThrow( 308 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 309 | ); 310 | 311 | expect(() => { 312 | omniclone({}, { copyGettersSetters: 42 }); 313 | }).toThrow( 314 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 315 | ); 316 | 317 | expect(() => { 318 | omniclone({}, { copyGettersSetters: "" }); 319 | }).toThrow( 320 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 321 | ); 322 | 323 | expect(() => { 324 | omniclone({}, { copyGettersSetters: "foo" }); 325 | }).toThrow( 326 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 327 | ); 328 | 329 | expect(() => { 330 | omniclone({}, { copyGettersSetters: null }); 331 | }).toThrow( 332 | TypeError("TypeError: invalid 'copyGettersSetters' flag's type") 333 | ); 334 | }); 335 | 336 | it("should throw a TypeError when the allowCircularReferences flag setted into the config object passed to omniclone has not Boolean type", () => { 337 | expect(() => { 338 | omniclone({}, { allowCircularReferences: () => {} }); 339 | }).toThrow( 340 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 341 | ); 342 | 343 | expect(() => { 344 | omniclone({}, { allowCircularReferences: {} }); 345 | }).toThrow( 346 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 347 | ); 348 | 349 | expect(() => { 350 | omniclone({}, { allowCircularReferences: 42 }); 351 | }).toThrow( 352 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 353 | ); 354 | 355 | expect(() => { 356 | omniclone({}, { allowCircularReferences: "" }); 357 | }).toThrow( 358 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 359 | ); 360 | 361 | expect(() => { 362 | omniclone({}, { allowCircularReferences: "foo" }); 363 | }).toThrow( 364 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 365 | ); 366 | 367 | expect(() => { 368 | omniclone({}, { allowCircularReferences: null }); 369 | }).toThrow( 370 | TypeError("TypeError: invalid 'allowCircularReferences' flag's type") 371 | ); 372 | }); 373 | 374 | it("should set proper constructor property and the [[Prototype]] property equals to the constructor.prototype when the invokeConstructors flag is set to true", () => { 375 | (() => { 376 | let i = 0; 377 | class Test { 378 | constructor() { 379 | i += 1; 380 | } 381 | } 382 | const testObj = new Test(); 383 | const res = omniclone(testObj); 384 | 385 | expect(i).toBe(2); // +1 because of the call to create testObj 386 | expect(res.constructor).toBe(Test); 387 | expect(Object.getPrototypeOf(res)).toBe(Test.prototype); 388 | })(); 389 | 390 | (() => { 391 | let i = 0; 392 | class Test { 393 | constructor() { 394 | i += 1; 395 | } 396 | } 397 | const testObj = new Test(); 398 | const res = omniclone(testObj, { invokeConstructors: true }); 399 | 400 | expect(i).toBe(2); // +1 because of the call to create testObj 401 | expect(res.constructor).toBe(Test); 402 | expect(Object.getPrototypeOf(res)).toBe(Test.prototype); 403 | })(); 404 | 405 | (() => { 406 | let i = 0; 407 | class Test { 408 | constructor() { 409 | i += 1; 410 | } 411 | } 412 | const testObj = new Test(); 413 | const res = omniclone(testObj, { 414 | invokeConstructors: true, 415 | setPrototype: true 416 | }); 417 | 418 | expect(i).toBe(2); // +1 because of the call to create testObj 419 | expect(res.constructor).toBe(Test); 420 | expect(Object.getPrototypeOf(res)).toBe(Test.prototype); 421 | })(); 422 | 423 | (() => { 424 | let i = 0; 425 | class Test { 426 | constructor() { 427 | i += 1; 428 | } 429 | } 430 | const testObj = new Test(); 431 | const res = omniclone(testObj, { setPrototype: true }); 432 | 433 | expect(i).toBe(2); // +1 because of the call to create testObj 434 | expect(res.constructor).toBe(Test); 435 | expect(Object.getPrototypeOf(res)).toBe(Test.prototype); 436 | })(); 437 | }); 438 | 439 | it("should set proper constructor property and the [[Prototype]] property equals to the source [[Prototype]] when the invokeConstructors flag is set to false and setPrototype flag is set to true", () => { 440 | (() => { 441 | let i = 0; 442 | class Test { 443 | constructor() { 444 | i += 1; 445 | } 446 | } 447 | const testObj = new Test(); 448 | const res = omniclone(testObj, { 449 | invokeConstructors: false, 450 | setPrototype: true 451 | }); 452 | 453 | expect(i).toBe(1); // +1 because of the call to create testObj 454 | expect(res.constructor).toBe(Test); 455 | expect(Object.getPrototypeOf(res)).toBe(Object.getPrototypeOf(testObj)); 456 | })(); 457 | }); 458 | 459 | it("should set the `constructor` prop to the `Object` function, and the `[[Prototype]]` prop to `Object.prototype` when both the invokeConstructors and the setPrototype flag are set to false", () => { 460 | (() => { 461 | let i = 0; 462 | class Test { 463 | constructor() { 464 | i += 1; 465 | } 466 | } 467 | const testObj = new Test(); 468 | const res = omniclone(testObj, { 469 | invokeConstructors: false, 470 | setPrototype: false 471 | }); 472 | 473 | expect(i).toBe(1); // +1 because of the call to create testObj 474 | expect(res.constructor).toBe(Object); 475 | expect(Object.getPrototypeOf(res)).toBe(Object.prototype); 476 | })(); 477 | }); 478 | 479 | it("should not copy non-enumerables properties if the copyNonEnumerables flag is set to false", () => { 480 | (() => { 481 | const obj = {}; 482 | Object.defineProperty(obj, "prop", { enumerable: false, value: 42 }); 483 | 484 | const res = omniclone(obj); 485 | 486 | expect(res.prop).toBeUndefined(); 487 | })(); 488 | 489 | (() => { 490 | const obj = {}; 491 | Object.defineProperty(obj, "prop", { enumerable: false, value: 42 }); 492 | 493 | const res = omniclone(obj, { copyNonEnumerables: false }); 494 | 495 | expect(res.prop).toBeUndefined(); 496 | })(); 497 | }); 498 | 499 | it("should copy non-enumerables properties if the copyNonEnumerables flag is set to true", () => { 500 | (() => { 501 | const obj = {}; 502 | Object.defineProperty(obj, "prop", { enumerable: false, value: 42 }); 503 | 504 | const res = omniclone(obj, { copyNonEnumerables: true }); 505 | 506 | expect(res.prop).toBe(42); 507 | })(); 508 | }); 509 | 510 | it("should not copy symbols if the copySymbols flag is set to false", () => { 511 | (() => { 512 | const obj = {}; 513 | obj[Symbol("s")] = "s"; 514 | const res = omniclone(obj); 515 | expect(res[Symbol("s")]).toBeUndefined(); 516 | })(); 517 | 518 | (() => { 519 | const obj = {}; 520 | obj[Symbol("s")] = "s"; 521 | const res = omniclone(obj, { copySymbols: false }); 522 | expect(res[Symbol("s")]).toBeUndefined(); 523 | })(); 524 | }); 525 | 526 | it("should shallow copy symbols if the copySymbols flag is set to true", () => { 527 | (() => { 528 | const obj = {}; 529 | obj[Symbol("s")] = "s"; 530 | const res = omniclone(obj, { copySymbols: true }); 531 | expect(res[Symbol("s")]).toBe(obj[Symbol("s")]); 532 | })(); 533 | }); 534 | 535 | it("should not copy getters&setters if the copyGettersSetters flag is set to false", () => { 536 | (() => { 537 | const testObj = {}; 538 | Object.defineProperty(testObj, "g&s", { 539 | get: () => 42, 540 | set: () => {} 541 | }); 542 | // copyNonEnumerables: true because in this case set&get are not enumerable 543 | const res = omniclone(testObj, { copyNonEnumerables: true }); 544 | expect(res["g&s"]).toBeUndefined(); 545 | })(); 546 | 547 | (() => { 548 | const testObj = {}; 549 | Object.defineProperty(testObj, "g&s", { 550 | get: () => 42, 551 | set: () => {} 552 | }); 553 | const res = omniclone(testObj, { copyGettersSetters: false }); 554 | expect(res["g&s"]).toBeUndefined(); 555 | })(); 556 | }); 557 | 558 | it("should shallow copy getters&setters if the copyGettersSetters flag is set to true", () => { 559 | (() => { 560 | const testObj = {}; 561 | Object.defineProperty(testObj, "g&s", { 562 | get: () => 42, 563 | set: () => {} 564 | }); 565 | // copyNonEnumerables: true because in this case set&get are also not enumerable 566 | const res = omniclone(testObj, { 567 | copyGettersSetters: true, 568 | copyNonEnumerables: true 569 | }); 570 | expect(res["g&s"]).toBeDefined(); 571 | })(); 572 | }); 573 | 574 | it("should throw a TypeError when there is a circular reference and the allowCircularReferences is set to false", () => { 575 | (() => { 576 | const ob1 = {}; 577 | ob1.ob1 = ob1; 578 | expect(() => { 579 | omniclone(ob1, { allowCircularReferences: false }); 580 | }).toThrow(TypeError("TypeError: circular reference found")); 581 | })(); 582 | }); 583 | 584 | it("should not throw a TypeError when there is a circular reference and the allowCircularReferences is set to true", () => { 585 | (() => { 586 | const ob1 = {}; 587 | ob1.ob1 = ob1; 588 | expect(() => { 589 | omniclone(ob1, { allowCircularReferences: true }); 590 | }).toBeDefined(); 591 | })(); 592 | 593 | (() => { 594 | const ob1 = {}; 595 | const ob2 = { ob1 }; 596 | ob1.ob2 = ob2; 597 | expect(() => { 598 | omniclone(ob1); 599 | }).toBeDefined(); 600 | })(); 601 | 602 | (() => { 603 | const ob1 = {}; 604 | const ob2 = {}; 605 | const ob3 = {}; 606 | const ob4 = {}; 607 | ob1.ob2 = ob2; 608 | ob1.ob4 = ob4; 609 | ob2.ob3 = ob3; 610 | ob4.ob3 = ob3; 611 | ob3.ob1 = ob1; 612 | 613 | expect(() => { 614 | omniclone(ob1, { allowCircularReferences: true }); 615 | }).toBeDefined(); 616 | })(); 617 | }); 618 | 619 | it("should throw a TypeError when a prop is an Error object and the discardErrorObjects flag is set to false", () => { 620 | (() => { 621 | class MyError extends Error {} 622 | const ob1 = { p: new MyError() }; 623 | expect(() => { 624 | omniclone(ob1, { discardErrorObjects: false }); 625 | }).toThrow(TypeError("TypeError: cannot copy Error objects")); 626 | })(); 627 | }); 628 | 629 | it("should discard an Error object prop if the discardErrorObjects flag is set to true", () => { 630 | (() => { 631 | class MyError extends Error {} 632 | const ob1 = { p: new MyError() }; 633 | 634 | const res = omniclone(ob1, { discardErrorObjects: true }); 635 | expect(res.p).toBeUndefined(); 636 | })(); 637 | 638 | (() => { 639 | class MyError extends Error {} 640 | const ob1 = { p: new MyError() }; 641 | 642 | const res = omniclone(ob1); 643 | expect(res.p).toBeUndefined(); 644 | })(); 645 | }); 646 | 647 | it("should return null when a String object is passed as source", () => { 648 | const str = new String("foo"); 649 | expect(omniclone(str)).toBeNull(); 650 | }); 651 | 652 | it("should convert String objects into primitive values ", () => { 653 | (() => { 654 | const str = { s: new String("foo") }; 655 | const res = omniclone(str); 656 | expect(res.s).toBe("foo"); 657 | })(); 658 | }); 659 | 660 | it("should return null when a Number object is passed as source", () => { 661 | const n = new Number("1"); 662 | expect(omniclone(n)).toBeNull(); 663 | }); 664 | 665 | it("should convert Number objects into primitive values ", () => { 666 | (() => { 667 | const nb = { n: new Number("1") }; 668 | const res = omniclone(nb); 669 | expect(res.n).toBe(1); 670 | })(); 671 | }); 672 | 673 | it("should return null when a Boolean object is passed as source", () => { 674 | const b = new Boolean(true); 675 | expect(omniclone(b)).toBeNull(); 676 | }); 677 | 678 | it("should convert Boolean objects into primitive values ", () => { 679 | (() => { 680 | const bo = { b: new Boolean(true) }; 681 | const res = omniclone(bo); 682 | expect(res.b).toBe(true); 683 | })(); 684 | }); 685 | 686 | it("should return the promise when a Promise object is passed as source", () => { 687 | const p = Promise.resolve(); 688 | const res = omniclone(p); 689 | expect(res).toBe(p); 690 | }); 691 | 692 | it("should return the weakmap when a WeakMap object is passed as source", () => { 693 | const wm = new WeakMap(); 694 | const res = omniclone(wm); 695 | expect(res).toBe(wm); 696 | }); 697 | 698 | it("should return the weakset when a WeakSet object is passed as source", () => { 699 | const ws = new WeakSet(); 700 | const res = omniclone(ws); 701 | expect(res).toBe(ws); 702 | }); 703 | 704 | it("should shallow copy a Promise prop", () => { 705 | const p = Promise.resolve(); 706 | const ob1 = { p }; 707 | const res = omniclone(ob1); 708 | expect(res.p).toBe(ob1.p); 709 | }); 710 | 711 | it("should shallow copy a WeakMap prop", () => { 712 | const wm = new WeakMap(); 713 | const ob1 = { wm }; 714 | const res = omniclone(ob1); 715 | expect(res.vm).toBe(ob1.vm); 716 | }); 717 | 718 | it("should shallow copy a WeakSet prop", () => { 719 | const ws = new WeakSet(); 720 | const ob1 = { ws }; 721 | const res = omniclone(ob1); 722 | expect(res.ws).toBe(ob1.ws); 723 | }); 724 | 725 | it("should clone a RegExp if it is passed as source", () => { 726 | (() => { 727 | const r = new RegExp("foo", "g"); 728 | r.lastIndex = 10; 729 | const res = omniclone(r); 730 | expect(res.flags).toBe(r.flags); 731 | expect(res.source).toBe(r.source); 732 | expect(res.lastIndex).toBe(r.lastIndex); 733 | })(); 734 | }); 735 | 736 | it("should clone a RegExp property", () => { 737 | (() => { 738 | const r = new RegExp("foo", "g"); 739 | r.lastIndex = 10; 740 | const ob1 = { r }; 741 | const res = omniclone(ob1); 742 | expect(res.r.flags).toBe(ob1.r.flags); 743 | expect(res.r.source).toBe(ob1.r.source); 744 | expect(res.r.lastIndex).toBe(ob1.r.lastIndex); 745 | })(); 746 | }); 747 | 748 | it("should clone a Date if it is passed as source", done => { 749 | (() => { 750 | const d = new Date(); 751 | new Promise(ok => { 752 | setTimeout(ok, 2000); 753 | }).then(() => { 754 | const res = omniclone(d); 755 | expect(res.getTime()).toBe(d.getTime()); 756 | done(); 757 | }); 758 | })(); 759 | }); 760 | 761 | it("should clone a Date property", done => { 762 | (() => { 763 | const d = new Date(); 764 | const ob1 = { d }; 765 | new Promise(ok => { 766 | setTimeout(ok, 2000); 767 | }).then(() => { 768 | const res = omniclone(ob1); 769 | expect(res.d.getTime()).toBe(ob1.d.getTime()); 770 | done(); 771 | }); 772 | })(); 773 | }); 774 | 775 | it("should properly clone an Array if the invokeConstructors flag is set", () => { 776 | (() => { 777 | const arr = [1, 2, 3, 4, 5]; 778 | const res = omniclone(arr); 779 | 780 | expect(Array.isArray(res)).toBeTruthy(); 781 | expect(res).toEqual(arr); 782 | expect(res === arr).toBeFalsy(); 783 | })(); 784 | 785 | (() => { 786 | const arr = [1, 2, 3, 4, 5]; 787 | const res = omniclone(arr, { 788 | invokeConstructors: true 789 | }); 790 | 791 | expect(Array.isArray(res)).toBeTruthy(); 792 | expect(res).toEqual(arr); 793 | })(); 794 | 795 | (() => { 796 | const arr = [1, 2, 3, 4, 5]; 797 | const ob1 = { arr }; 798 | const res = omniclone(ob1); 799 | 800 | expect(Array.isArray(res.arr)).toBeTruthy(); 801 | expect(res.arr).toEqual(ob1.arr); 802 | })(); 803 | 804 | (() => { 805 | const arr = [1, 2, 3, 4, 5]; 806 | const ob1 = { arr }; 807 | const res = omniclone(ob1); 808 | 809 | expect(Array.isArray(res.arr)).toBeTruthy(); 810 | expect(res.arr).toEqual(ob1.arr); 811 | })(); 812 | }); 813 | 814 | it(`should not copy a non config prop setted by the calling to a constructor`, () => { 815 | (() => { 816 | class Test { 817 | constructor() { 818 | Object.defineProperty(this, "prop", { 819 | configurable: false, 820 | writable: false, 821 | enumerable: true, 822 | value: 42 823 | }); 824 | } 825 | } 826 | const test = new Test(); 827 | expect(() => { 828 | omniclone(test); 829 | }).not.toThrow( 830 | `TypeError: can't redefine non-configurable property prop` 831 | ); 832 | })(); 833 | }); 834 | 835 | it(`should properly copy [[Prototype]], constructor, props, non-enum props, gets&sets and shallow copy symbols`, () => { 836 | (() => { 837 | const testObj = {}; 838 | testObj.foo = "bar"; 839 | Object.defineProperty(testObj, "notEnum", { 840 | enumerable: false, 841 | value: 42 842 | }); 843 | Object.defineProperty(testObj, "g&s", { 844 | get: () => 42, 845 | set: () => {} 846 | }); 847 | testObj.symbol = Symbol("symbol"); 848 | 849 | const res = omniclone(testObj, { 850 | copyGettersSetters: true, 851 | copySymbols: true, 852 | copyNonEnumerables: true 853 | }); 854 | 855 | expect(res === testObj).toBeFalsy(); 856 | expect(res).toEqual(testObj); 857 | expect(res.constructor).toEqual(testObj.constructor); 858 | expect(Object.getPrototypeOf(res)).toEqual( 859 | Object.getPrototypeOf(testObj) 860 | ); 861 | 862 | expect(res.foo).toBe("bar"); 863 | expect(Object.getOwnPropertyDescriptor(res, "notEnum")).toEqual( 864 | Object.getOwnPropertyDescriptor(testObj, "notEnum") 865 | ); 866 | expect(Object.getOwnPropertyDescriptor(res, "g&s")).toEqual( 867 | Object.getOwnPropertyDescriptor(testObj, "g&s") 868 | ); 869 | expect(res.symbol).toBe(testObj.symbol); // shallow copy symbols 870 | })(); 871 | 872 | (() => { 873 | const testObj = {}; 874 | testObj.foo = "bar"; 875 | Object.defineProperty(testObj, "notEnum", { 876 | enumerable: false, 877 | value: 42 878 | }); 879 | Object.defineProperty(testObj, "g&s", { 880 | get: () => 42, 881 | set: () => {} 882 | }); 883 | testObj.symbol = Symbol("symbol"); 884 | testObj.innerObj = { 885 | prop: "value", 886 | symbol: Symbol("innerSymbol") 887 | }; 888 | 889 | const res = omniclone(testObj, { 890 | copyGettersSetters: true, 891 | copySymbols: true, 892 | copyNonEnumerables: true 893 | }); 894 | 895 | expect(res === testObj).toBeFalsy(); 896 | expect(res.innerObj === testObj.innerObj).toBeFalsy(); 897 | expect(res).toEqual(testObj); 898 | expect(res.innerObj).toEqual(testObj.innerObj); 899 | expect(res.constructor).toEqual(testObj.constructor); 900 | expect(Object.getPrototypeOf(res)).toEqual( 901 | Object.getPrototypeOf(testObj) 902 | ); 903 | 904 | expect(res.foo).toBe("bar"); 905 | expect(Object.getOwnPropertyDescriptor(res, "notEnum")).toEqual( 906 | Object.getOwnPropertyDescriptor(testObj, "notEnum") 907 | ); 908 | expect(Object.getOwnPropertyDescriptor(res, "g&s")).toEqual( 909 | Object.getOwnPropertyDescriptor(testObj, "g&s") 910 | ); 911 | expect(res.symbol).toBe(testObj.symbol); // shallow copy symbols 912 | expect(res.innerObj.symbol).toBe(testObj.innerObj.symbol); // shallow copy symbols 913 | expect(res.innerObj.prop).toBe(testObj.innerObj.prop); 914 | })(); 915 | }); 916 | 917 | it(`should properly restore a circular references structure`, () => { 918 | (() => { 919 | const ob1 = { value: 1 }; 920 | 921 | const ob2 = { value: 2 }; 922 | 923 | const ob3 = { value: 3 }; 924 | 925 | const ob4 = { value: 4 }; 926 | 927 | ob1.ob2 = ob2; 928 | ob1.ob4 = ob4; 929 | ob2.ob3 = ob3; 930 | ob3.ob1 = ob1; 931 | ob4.ob3 = ob3; 932 | 933 | const res = omniclone(ob1, { 934 | allowCircularReferences: true 935 | }); 936 | 937 | // checks for differents references between old and new structure 938 | expect(res === ob1).toBeFalsy(); 939 | expect(res.ob2 === ob1.ob2).toBeFalsy(); 940 | expect(res.ob4 === ob1.ob4).toBeFalsy(); 941 | expect(res.ob2.ob3 === ob1.ob2.ob3).toBeFalsy(); 942 | expect(res.ob2.ob3.ob1 === ob1.ob2.ob3.ob1).toBeFalsy(); 943 | 944 | // check for equals references inside the returned object 945 | expect(res.ob2.ob3.ob1 === res).toBeTruthy(); 946 | expect(res.ob4.ob3 === res.ob2.ob3).toBeTruthy(); 947 | 948 | // check for props equality 949 | expect(res).toEqual(ob1); 950 | })(); 951 | }); 952 | 953 | it(`should properly handle duplicated sibiling object references without circular references`, () => { 954 | (() => { 955 | const duplicatedObj = {}; 956 | const source = { 957 | a: duplicatedObj, 958 | b: duplicatedObj 959 | }; 960 | duplicatedObj.prop = "value"; 961 | 962 | const res = omniclone(source); 963 | 964 | expect(res.a).toBe(res.b); 965 | expect(res === source).toBeFalsy(); 966 | expect(res.a === source.a).toBeFalsy(); 967 | 968 | // props check 969 | expect(res).toEqual(source); 970 | })(); 971 | }); 972 | 973 | it(`should properly handle duplicated sibiling object references with circular references`, () => { 974 | (() => { 975 | const duplicatedObj = {}; 976 | const source = { 977 | a: duplicatedObj, 978 | b: duplicatedObj 979 | }; 980 | duplicatedObj.prop = "value"; 981 | duplicatedObj.source = source; 982 | 983 | const res = omniclone(source, { 984 | allowCircularReferences: true 985 | }); 986 | 987 | expect(res.a).toBe(res.b); 988 | expect(res === source).toBeFalsy(); 989 | expect(res.a === source.a).toBeFalsy(); 990 | expect(res.a.source === source.a.source).toBeFalsy(); 991 | expect(res.a.source === res).toBeTruthy(); 992 | 993 | // props check 994 | expect(res).toEqual(source); 995 | })(); 996 | }); 997 | 998 | it(`should properly duplicate a simple Map (without circular references)`, () => { 999 | (() => { 1000 | const map = new Map(); 1001 | const key = {}; 1002 | map.set("prop1", "value1"); 1003 | map.set(key, { 1004 | a: { 1005 | a: "a" 1006 | }, 1007 | b: 42 1008 | }); 1009 | 1010 | map.set(Symbol.iterator, 42); // symbols should be copied because in a Map them aren't 'hidden' keys 1011 | 1012 | const res = omniclone(map); 1013 | 1014 | expect(map === res).toBeFalsy(); 1015 | expect(map.get("prop1") === res.get("prop1")).toBeTruthy(); 1016 | expect(map.get(key) === res.get(key)).toBeFalsy(); 1017 | expect(map.get(key)).toEqual(res.get(key)); 1018 | expect( 1019 | map.get(Symbol.iterator) === res.get(Symbol.iterator) 1020 | ).toBeTruthy(); 1021 | expect(map.size).toBe(res.size); 1022 | })(); 1023 | }); 1024 | 1025 | it(`should properly duplicate a Map with circular references`, () => { 1026 | (() => { 1027 | const map = new Map(); 1028 | const key = {}; 1029 | map.set("prop1", "value1"); 1030 | map.set(key, { 1031 | a: { 1032 | a: "a" 1033 | }, 1034 | b: 42, 1035 | map 1036 | }); 1037 | map.set(map, map); 1038 | 1039 | const res = omniclone(map, { 1040 | allowCircularReferences: true 1041 | }); 1042 | 1043 | expect(map === res).toBeFalsy(); 1044 | expect(map.get("prop1") === res.get("prop1")).toBeTruthy(); 1045 | expect(map.get(key) === res.get(key)).toBeFalsy(); 1046 | expect(map.size).toBe(res.size); 1047 | 1048 | expect(map.get(key).map).toBe(map); 1049 | expect(res.get(key).map).toBe(res); 1050 | 1051 | // map and object keys remains untouched, values needs to be updated 1052 | expect(res.get(map) === res).toBeTruthy(); 1053 | expect(res.get(key).a === map.get(key).a).toBeFalsy(); 1054 | expect(res.get(key).map === res).toBeTruthy(); 1055 | })(); 1056 | }); 1057 | 1058 | it(`it should throw a TypeError when a circular reference is found into a Map object and the allowCircularReferences flag is set to false`, () => { 1059 | (() => { 1060 | const map = new Map(); 1061 | map.set(map, map); 1062 | expect(() => { 1063 | omniclone(map, { 1064 | allowCircularReferences: false 1065 | }); 1066 | }).toThrow(TypeError("TypeError: circular reference found")); 1067 | })(); 1068 | 1069 | (() => { 1070 | const map = new Map(); 1071 | map.set(map, map); 1072 | expect(() => { 1073 | omniclone(map, { 1074 | allowCircularReferences: false 1075 | }); 1076 | }).toThrow(TypeError("TypeError: circular reference found")); 1077 | })(); 1078 | }); 1079 | 1080 | it(`should properly handle duplicated sibiling object references into a Map without circular references`, () => { 1081 | (() => { 1082 | const duplicatedObj = {}; 1083 | const map = new Map(); 1084 | map.set("a", duplicatedObj); 1085 | map.set("b", duplicatedObj); 1086 | 1087 | const res = omniclone(map); 1088 | 1089 | expect(res.get("a")).toBe(res.get("b")); 1090 | expect(res === map).toBeFalsy(); 1091 | expect(res.get("a") === map.get("a")).toBeFalsy(); 1092 | })(); 1093 | }); 1094 | 1095 | it(`should properly handle duplicated sibiling object references into a Map with circular references`, () => { 1096 | (() => { 1097 | const duplicatedObj = {}; 1098 | const map = new Map(); 1099 | map.set("a", duplicatedObj); 1100 | map.set("b", duplicatedObj); 1101 | 1102 | duplicatedObj.map = map; 1103 | duplicatedObj.a = "a"; 1104 | 1105 | const res = omniclone(map, { 1106 | allowCircularReferences: true 1107 | }); 1108 | 1109 | expect(res.get("a")).toBe(res.get("b")); 1110 | expect(res === map).toBeFalsy(); 1111 | expect(res.get("a") === map.get("a")).toBeFalsy(); 1112 | expect(res.get("a").map === res).toBeTruthy(); 1113 | })(); 1114 | }); 1115 | 1116 | it(`should call the proper constructor for an object into a Map`, () => { 1117 | (() => { 1118 | class Test {} 1119 | const map = new Map(); 1120 | map.set("a", new Test()); 1121 | 1122 | const res = omniclone(map); 1123 | 1124 | expect(res.get("a").constructor).toBe(Test); 1125 | expect(res === map).toBeFalsy(); 1126 | expect(res.get("a") === map.get("a")).toBeFalsy(); 1127 | expect( 1128 | Object.getPrototypeOf(res.get("a")) === Test.prototype 1129 | ).toBeTruthy(); 1130 | })(); 1131 | }); 1132 | 1133 | it("should throw a TypeError when a value in a Map object is an Error object and the discardErrorObjects flag is set to false", () => { 1134 | (() => { 1135 | class MyError extends Error {} 1136 | const map = new Map(); 1137 | map.set("p", new MyError()); 1138 | 1139 | expect(() => { 1140 | omniclone(map, { discardErrorObjects: false }); 1141 | }).toThrow(TypeError("TypeError: cannot copy Error objects")); 1142 | })(); 1143 | }); 1144 | 1145 | it("should discard an Error map element object if the discardErrorObjects flag is set to true", () => { 1146 | (() => { 1147 | class MyError extends Error {} 1148 | const map = new Map(); 1149 | map.set("e", new MyError()); 1150 | 1151 | const res = omniclone(map, { discardErrorObjects: true }); 1152 | expect(res.get("e")).toBeUndefined(); 1153 | })(); 1154 | 1155 | (() => { 1156 | class MyError extends Error {} 1157 | const map = new Map(); 1158 | map.set("e", new MyError()); 1159 | 1160 | const res = omniclone(map); 1161 | 1162 | expect(res.get("e")).toBeUndefined(); 1163 | })(); 1164 | }); 1165 | 1166 | it("should convert String map elements into primitive values ", () => { 1167 | (() => { 1168 | const map = new Map(); 1169 | const str = new String("foo"); 1170 | map.set("s", str); 1171 | const res = omniclone(map); 1172 | expect(res.get("s")).toBe("foo"); 1173 | })(); 1174 | }); 1175 | 1176 | it("should convert Number map elements into primitive values ", () => { 1177 | (() => { 1178 | const map = new Map(); 1179 | const n = new Number(1); 1180 | map.set("n", n); 1181 | const res = omniclone(map); 1182 | expect(res.get("n")).toBe(1); 1183 | })(); 1184 | }); 1185 | 1186 | it("should convert Boolean map elements into primitive values ", () => { 1187 | (() => { 1188 | const map = new Map(); 1189 | const b = new Boolean(true); 1190 | map.set("b", b); 1191 | const res = omniclone(map); 1192 | expect(res.get("b")).toBe(true); 1193 | })(); 1194 | }); 1195 | 1196 | it("should shallow copy a Promise map element ", () => { 1197 | const map = new Map(); 1198 | const p = Promise.resolve(); 1199 | map.set("p", p); 1200 | const res = omniclone(map); 1201 | expect(res.get("p")).toBe(map.get("p")); 1202 | }); 1203 | 1204 | it("should shallow copy a WeakMap map element", () => { 1205 | const wm = new WeakMap(); 1206 | const map = new Map(); 1207 | map.set("wm", wm); 1208 | const res = omniclone(map); 1209 | expect(res.get("wm")).toBe(map.get("wm")); 1210 | }); 1211 | 1212 | it("should shallow copy a WeakSet map element", () => { 1213 | const ws = new WeakSet(); 1214 | const map = new Map(); 1215 | map.set("ws", ws); 1216 | const res = omniclone(map); 1217 | expect(res.get("ws")).toBe(map.get("ws")); 1218 | }); 1219 | 1220 | it("should clone a RegExp map element", () => { 1221 | (() => { 1222 | const map = new Map(); 1223 | const r = new RegExp("foo", "g"); 1224 | r.lastIndex = 10; 1225 | map.set("r", r); 1226 | 1227 | const res = omniclone(map); 1228 | expect(res.get("r") === map.get("r")).toBe(false); 1229 | expect(res.get("r").flags).toBe(map.get("r").flags); 1230 | expect(res.get("r").source).toBe(map.get("r").source); 1231 | expect(res.get("r").lastIndex).toBe(map.get("r").lastIndex); 1232 | })(); 1233 | }); 1234 | 1235 | it("should clone a Date map element", done => { 1236 | (() => { 1237 | const map = new Map(); 1238 | const d = new Date(); 1239 | map.set("d", d); 1240 | 1241 | new Promise(ok => { 1242 | setTimeout(ok, 2000); 1243 | }).then(() => { 1244 | const res = omniclone(map); 1245 | expect(res.get("d") === map.get("d")).toBe(false); 1246 | expect(res.get("d").getTime()).toBe(map.get("d").getTime()); 1247 | done(); 1248 | }); 1249 | })(); 1250 | }); 1251 | 1252 | it("should properly clone an Array into a map if the invokeConstructors flag is set", () => { 1253 | (() => { 1254 | const arr = [1, 2, 3, 4, 5]; 1255 | const map = new Map(); 1256 | map.set("arr", arr); 1257 | 1258 | const res = omniclone(map); 1259 | 1260 | expect(Array.isArray(res.get("arr"))).toBeTruthy(); 1261 | expect(res).toEqual(map); 1262 | expect(res.get("arr") === map.get("arr")).toBeFalsy(); 1263 | })(); 1264 | 1265 | (() => { 1266 | const arr = [1, 2, 3, 4, 5]; 1267 | const map = new Map(); 1268 | map.set("arr", arr); 1269 | arr.push(map); 1270 | arr.push(map); 1271 | 1272 | const res = omniclone(map, { 1273 | allowCircularReferences: true 1274 | }); 1275 | 1276 | expect(Array.isArray(res.get("arr"))).toBeTruthy(); 1277 | expect(res.get("arr") === map.get("arr")).toBeFalsy(); 1278 | expect(res.get("arr")[5] === map.get("arr")[5]).toBeFalsy(); 1279 | expect(res.get("arr")[5] === res.get("arr")[6]).toBeTruthy(); 1280 | expect(res.get("arr")[5] === res).toBeTruthy(); 1281 | })(); 1282 | }); 1283 | 1284 | it(`should properly duplicate a simple Set (without circular references)`, () => { 1285 | (() => { 1286 | const set = new Set(); 1287 | set.add("prop1", "value1"); 1288 | set.add({ 1289 | a: { 1290 | a: "a" 1291 | }, 1292 | b: 42 1293 | }); 1294 | 1295 | set.add(Symbol.iterator, 42); // symbols should be copied because in a Set them aren't 'hidden' keys 1296 | 1297 | const res = omniclone(set); 1298 | expect(set === res).toBeFalsy(); 1299 | 1300 | const setIt = set.values(); 1301 | const resIt = res.values(); 1302 | 1303 | expect(setIt.next().value === resIt.next().value).toBeTruthy(); // prop1 1304 | 1305 | // the object 1306 | const setObj = setIt.next().value; 1307 | const resObj = resIt.next().value; 1308 | expect(setObj === resObj).toBeFalsy(); 1309 | expect(setObj).toEqual(resObj); 1310 | 1311 | expect(setIt.next().value === resIt.next().value).toBeTruthy(); // the symbol 1312 | expect(set.size).toBe(res.size); 1313 | })(); 1314 | }); 1315 | 1316 | it(`should properly duplicate a Set with circular references`, () => { 1317 | (() => { 1318 | const set = new Set(); 1319 | set.add("prop1", "value1"); 1320 | set.add({ 1321 | a: { 1322 | a: "a" 1323 | }, 1324 | b: 42, 1325 | set 1326 | }); 1327 | set.add(set); 1328 | 1329 | const res = omniclone(set, { 1330 | allowCircularReferences: true 1331 | }); 1332 | expect(set === res).toBeFalsy(); 1333 | 1334 | const setIt = set.values(); 1335 | const resIt = res.values(); 1336 | 1337 | expect(setIt.next().value === resIt.next().value).toBeTruthy(); // prop1 1338 | 1339 | // the object 1340 | const setObj = setIt.next().value; 1341 | const resObj = resIt.next().value; 1342 | expect(setObj === resObj).toBeFalsy(); 1343 | 1344 | expect(setObj.set === set).toBeTruthy(); 1345 | expect(resObj.set === res).toBeTruthy(); 1346 | 1347 | // the inner reference to the Set 1348 | const setSet = setIt.next().value; 1349 | const resSet = resIt.next().value; 1350 | 1351 | expect(resSet === res).toBeTruthy(); 1352 | expect(setSet === resSet).toBeFalsy(); 1353 | 1354 | expect(set.size).toBe(res.size); 1355 | })(); 1356 | }); 1357 | 1358 | it(`it should throw a TypeError when a circular reference is found into a Set object and the allowCircularReferences flag is set to false`, () => { 1359 | (() => { 1360 | const set = new Set(); 1361 | set.add(set); 1362 | expect(() => { 1363 | omniclone(set, { 1364 | allowCircularReferences: false 1365 | }); 1366 | }).toThrow(TypeError("TypeError: circular reference found")); 1367 | })(); 1368 | 1369 | (() => { 1370 | const set = new Set(); 1371 | set.add(set); 1372 | expect(() => { 1373 | omniclone(set, { 1374 | allowCircularReferences: false 1375 | }); 1376 | }).toThrow(TypeError("TypeError: circular reference found")); 1377 | })(); 1378 | }); 1379 | 1380 | it(`should call the proper constructor for an object into a Set`, () => { 1381 | (() => { 1382 | let i = 0; 1383 | class Test { 1384 | constructor() { 1385 | i += 1; 1386 | } 1387 | } 1388 | const set = new Set(); 1389 | set.add(new Test()); 1390 | const res = omniclone(set); 1391 | 1392 | expect(i).toBe(2); 1393 | 1394 | const setTestEntry = set.values().next().value; 1395 | const resTestEntry = res.values().next().value; 1396 | 1397 | expect(resTestEntry.constructor).toBe(Test); 1398 | expect(res === set).toBeFalsy(); 1399 | expect(setTestEntry === resTestEntry).toBeFalsy(); 1400 | expect( 1401 | Object.getPrototypeOf(resTestEntry) === Test.prototype 1402 | ).toBeTruthy(); 1403 | })(); 1404 | }); 1405 | 1406 | it("should throw a TypeError when a value in a Set object is an Error object and the discardErrorObjects flag is set to false", () => { 1407 | (() => { 1408 | class MyError extends Error {} 1409 | const set = new Set(); 1410 | set.add(new MyError()); 1411 | 1412 | expect(() => { 1413 | omniclone(set, { discardErrorObjects: false }); 1414 | }).toThrow(TypeError("TypeError: cannot copy Error objects")); 1415 | })(); 1416 | }); 1417 | 1418 | it("should discard an Error set element object if the discardErrorObjects flag is set to true", () => { 1419 | (() => { 1420 | class MyError extends Error {} 1421 | const set = new Set(); 1422 | const e = new MyError(); 1423 | set.add(e); 1424 | 1425 | const res = omniclone(set, { discardErrorObjects: true }); 1426 | expect(res.has(e)).toBeFalsy(); 1427 | })(); 1428 | 1429 | (() => { 1430 | class MyError extends Error {} 1431 | const set = new Set(); 1432 | const e = new MyError(); 1433 | set.has(e); 1434 | 1435 | const res = omniclone(set); 1436 | expect(res.has(e)).toBeFalsy(); 1437 | })(); 1438 | }); 1439 | 1440 | it("should convert String set elements into primitive values ", () => { 1441 | (() => { 1442 | const set = new Set(); 1443 | const str = new String("foo"); 1444 | set.add(str); 1445 | const res = omniclone(set); 1446 | expect(res.has("foo")).toBe(true); 1447 | })(); 1448 | }); 1449 | 1450 | it("should convert Number set elements into primitive values ", () => { 1451 | (() => { 1452 | const set = new Set(); 1453 | const n = new Number(42); 1454 | set.add(n); 1455 | const res = omniclone(set); 1456 | expect(res.has(42)).toBe(true); 1457 | })(); 1458 | }); 1459 | 1460 | it("should convert Boolean set elements into primitive values ", () => { 1461 | (() => { 1462 | const set = new Set(); 1463 | const b = new Boolean(true); 1464 | set.add(b); 1465 | const res = omniclone(set); 1466 | expect(res.has(true)).toBe(true); 1467 | })(); 1468 | }); 1469 | 1470 | it("should shallow copy a Promise set element ", () => { 1471 | const set = new Set(); 1472 | const p = Promise.resolve(); 1473 | set.add(p); 1474 | const res = omniclone(set); 1475 | expect(res.has(p)).toBe(true); 1476 | }); 1477 | 1478 | it("should shallow copy a WeakMap set element", () => { 1479 | const wm = new WeakMap(); 1480 | const set = new Set(); 1481 | set.add(wm); 1482 | const res = omniclone(set); 1483 | expect(res.has(wm)).toBe(true); 1484 | }); 1485 | 1486 | it("should shallow copy a WeakSet set element", () => { 1487 | const ws = new WeakSet(); 1488 | const set = new Set(); 1489 | set.add(ws); 1490 | const res = omniclone(set); 1491 | expect(res.has(ws)).toBe(true); 1492 | }); 1493 | 1494 | it("should clone a RegExp set element", () => { 1495 | (() => { 1496 | const set = new Set(); 1497 | const r = new RegExp("foo", "g"); 1498 | r.lastIndex = 10; 1499 | set.add(r); 1500 | const res = omniclone(set); 1501 | 1502 | const resRegExp = res.values().next().value; 1503 | 1504 | expect(r === resRegExp).toBe(false); 1505 | expect(resRegExp.flags).toBe(r.flags); 1506 | expect(resRegExp.source).toBe(r.source); 1507 | expect(resRegExp.lastIndex).toBe(r.lastIndex); 1508 | })(); 1509 | }); 1510 | 1511 | it("should clone a Date set element", done => { 1512 | (() => { 1513 | const set = new Set(); 1514 | const d = new Date(); 1515 | set.add(d); 1516 | 1517 | new Promise(ok => { 1518 | setTimeout(ok, 2000); 1519 | }).then(() => { 1520 | const res = omniclone(set); 1521 | const resDate = res.values().next().value; 1522 | expect(resDate === d).toBe(false); 1523 | expect(resDate.getTime() === d.getTime()).toBe(true); 1524 | done(); 1525 | }); 1526 | })(); 1527 | }); 1528 | 1529 | it("should properly clone an Array into a set if the invokeConstructors flag is set", () => { 1530 | (() => { 1531 | const arr = [1, 2, 3, 4, 5]; 1532 | const set = new Set(); 1533 | set.add(arr); 1534 | 1535 | const res = omniclone(set); 1536 | const resArr = res.values().next().value; 1537 | 1538 | expect(Array.isArray(resArr)).toBeTruthy(); 1539 | expect(res).toEqual(set); 1540 | expect(resArr === arr).toBeFalsy(); 1541 | expect(resArr).toEqual(arr); 1542 | })(); 1543 | 1544 | (() => { 1545 | const arr = [1, 2, 3, 4, 5]; 1546 | const set = new Set(); 1547 | set.add(arr); 1548 | 1549 | arr.push(set); 1550 | arr.push(set); 1551 | 1552 | const res = omniclone(set, { 1553 | allowCircularReferences: true 1554 | }); 1555 | const resArr = res.values().next().value; 1556 | 1557 | expect(Array.isArray(resArr)).toBeTruthy(); 1558 | expect(resArr === arr).toBeFalsy(); 1559 | 1560 | expect(resArr[5] === arr[5]).toBeFalsy(); 1561 | expect(resArr[5] === resArr[6]).toBeTruthy(); 1562 | expect(resArr[5] === res).toBeTruthy(); 1563 | })(); 1564 | }); 1565 | 1566 | it("should pass when there are circ references into a Set object contained into an object", () => { 1567 | (() => { 1568 | const obj = {}; 1569 | const obj2 = { a: "a" }; 1570 | obj.obj2 = obj2; 1571 | const set = new Set(); 1572 | set.add(obj); 1573 | set.add(obj2); 1574 | obj.set = set; 1575 | 1576 | const res = omniclone(obj, { 1577 | allowCircularReferences: true 1578 | }); 1579 | 1580 | expect(obj === res).toBe(false); 1581 | expect(obj.obj2 === res.obj2).toBe(false); 1582 | expect(obj.set === res.set).toBe(false); 1583 | 1584 | const objSetIt = obj.set.values(); 1585 | const objSetObj = objSetIt.next().value; 1586 | const objSetObj2 = objSetIt.next().value; 1587 | const resSetIt = res.set.values(); 1588 | const resSetObj = resSetIt.next().value; 1589 | const resSetObj2 = resSetIt.next().value; 1590 | 1591 | // them are inverted, idk why 1592 | expect(resSetObj2 === objSetObj).toBe(false); 1593 | expect(resSetObj === objSetObj2).toBe(false); 1594 | expect(objSetObj === obj).toBe(true); 1595 | expect(resSetObj === res.obj2).toBe(true); 1596 | expect(resSetObj2 === res).toBe(true); 1597 | })(); 1598 | }); 1599 | 1600 | it("should not throw a TypeError because there are not circ references", () => { 1601 | (() => { 1602 | const ob0 = {}; 1603 | const ob1 = {}; 1604 | const ob2 = {}; 1605 | const ob3 = {}; 1606 | 1607 | ob0.ob1 = ob1; 1608 | ob0.ob2 = ob2; 1609 | 1610 | ob1.ob3 = ob3; 1611 | ob2.ob3 = ob3; 1612 | expect(() => { 1613 | omniclone(ob0, { allowCircularReferences: false }); 1614 | }).not.toThrow(TypeError("TypeError: circular reference found")); 1615 | })(); 1616 | 1617 | (() => { 1618 | const ob0 = {}; 1619 | const ob1 = {}; 1620 | const ob2 = {}; 1621 | ob0.ob1 = ob1; 1622 | ob0.ob2 = ob2; 1623 | 1624 | ob1.ob2 = ob2; 1625 | expect(() => { 1626 | omniclone(ob0, { allowCircularReferences: false }); 1627 | }).not.toThrow(TypeError("TypeError: circular reference found")); 1628 | })(); 1629 | 1630 | (() => { 1631 | const ob0 = {}; 1632 | const ob1 = {}; 1633 | const ob2 = {}; 1634 | const ob3 = {}; 1635 | 1636 | ob0.ob1 = ob1; 1637 | ob0.ob2 = ob2; 1638 | 1639 | ob2.ob3 = ob3; 1640 | ob3.ob1 = ob1; 1641 | 1642 | expect(() => { 1643 | omniclone(ob0, { allowCircularReferences: false }); 1644 | }).not.toThrow(TypeError("TypeError: circular reference found")); 1645 | })(); 1646 | }); 1647 | 1648 | it("should properly clone a Node Buffer when we pass an appropriate customHandler callback", () => { 1649 | (() => { 1650 | const buffer = Buffer.from([1, 2, 3, 4]); 1651 | 1652 | const resBuffer = omniclone(buffer, {}, node => { 1653 | if (node instanceof Buffer) { 1654 | return Buffer.from(node); 1655 | } 1656 | return undefined; 1657 | }); 1658 | 1659 | const comp = Buffer.compare(buffer, resBuffer); 1660 | 1661 | expect(comp).toBe(0); 1662 | expect(resBuffer.constructor).toBe(Buffer); 1663 | })(); 1664 | }); 1665 | 1666 | it("should properly clone the custom type object", () => { 1667 | (() => { 1668 | class Test { 1669 | constructor(a, b) { 1670 | this.a = a; 1671 | this.b = b; 1672 | } 1673 | 1674 | setC(val) { 1675 | this.c = val; 1676 | } 1677 | } 1678 | 1679 | const test = new Test(); 1680 | test.setC(10); 1681 | 1682 | const res = omniclone(test, {}, node => { 1683 | if (node instanceof Test) { 1684 | const result = new Test(node.a, node.b); 1685 | return result; 1686 | } 1687 | return undefined; 1688 | }); 1689 | expect(res instanceof Test).toBe(true); 1690 | expect(res.a === test.a).toBe(true); 1691 | expect(res.b === test.b).toBe(true); 1692 | expect(res.c).toBeUndefined(); 1693 | })(); 1694 | 1695 | (() => { 1696 | class Test { 1697 | constructor(a, b) { 1698 | this.a = a; 1699 | this.b = b; 1700 | } 1701 | 1702 | setC(val) { 1703 | this.c = val; 1704 | } 1705 | } 1706 | 1707 | const test = new Test(); 1708 | test.setC(10); 1709 | const source = { test }; 1710 | 1711 | const res = omniclone(source, {}, node => { 1712 | if (node instanceof Test) { 1713 | const result = new Test(node.a, node.b); 1714 | return result; 1715 | } 1716 | return undefined; 1717 | }); 1718 | expect(res.test instanceof Test).toBe(true); 1719 | expect(res.test.a === test.a).toBe(true); 1720 | expect(res.test.b === test.b).toBe(true); 1721 | expect(res.test.c).toBeUndefined(); 1722 | })(); 1723 | 1724 | (() => { 1725 | class Test { 1726 | constructor(a, b) { 1727 | this.a = a; 1728 | this.b = b; 1729 | } 1730 | 1731 | setC(val) { 1732 | this.c = val; 1733 | } 1734 | } 1735 | 1736 | const test = new Test(); 1737 | test.setC(10); 1738 | 1739 | const map = new Map(); 1740 | map.set("t", test); 1741 | 1742 | const res = omniclone(map, {}, node => { 1743 | if (node instanceof Test) { 1744 | const result = new Test(node.a, node.b); 1745 | return result; 1746 | } 1747 | return undefined; 1748 | }); 1749 | expect(res.get("t") instanceof Test).toBe(true); 1750 | expect(res.get("t").a === test.a).toBe(true); 1751 | expect(res.get("t").b === test.b).toBe(true); 1752 | expect(res.get("t").c).toBeUndefined(); 1753 | })(); 1754 | 1755 | (() => { 1756 | class Test { 1757 | constructor(a, b) { 1758 | this.a = a; 1759 | this.b = b; 1760 | } 1761 | 1762 | setC(val) { 1763 | this.c = val; 1764 | } 1765 | } 1766 | 1767 | const test = new Test(); 1768 | test.setC(10); 1769 | 1770 | const set = new Set(); 1771 | set.add(test); 1772 | 1773 | const resSet = omniclone(set, {}, node => { 1774 | if (node instanceof Test) { 1775 | const result = new Test(node.a, node.b); 1776 | return result; 1777 | } 1778 | return undefined; 1779 | }); 1780 | 1781 | const res = resSet.values().next().value; 1782 | 1783 | expect(res instanceof Test).toBe(true); 1784 | expect(res.a === test.a).toBe(true); 1785 | expect(res.b === test.b).toBe(true); 1786 | expect(res.c).toBeUndefined(); 1787 | })(); 1788 | }); 1789 | }); 1790 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | module.exports=function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=7)}([function(e,t){e.exports=(e=>{if(e)return null;throw new TypeError("TypeError: cannot copy Error objects")})},function(e,t){e.exports=(e=>{const{source:t,flags:n,lastIndex:o}=e,r=new RegExp(t,n);return r.lastIndex=o,r})},function(e,t){e.exports=(e=>new Date(e.getTime()))},function(e,t){e.exports=(e=>e.slice())},function(e,t){e.exports=(e=>e.slice())},function(e,t){e.exports=(e=>e.valueOf())},function(e,t){e.exports=((e,t)=>e.has(t)?e.get(t):null)},function(e,t,n){const o=n(8),r=n(0),i=n(1),s=n(4),c=n(3),a=n(2),f=n(17),u=n(18);e.exports=function(e={},{setPrototype:t=!1,invokeConstructors:n=!0,copyNonEnumerables:p=!1,copySymbols:y=!1,copyGettersSetters:l=!1,allowCircularReferences:d=!0,discardErrorObjects:b=!0}={},E=(()=>{})){if(!e||"object"!=typeof e)throw new TypeError("TypeError: invalid 'obj' argument's type");if("boolean"!=typeof t)throw new TypeError("TypeError: invalid 'setPrototype' flag's type");if("boolean"!=typeof n)throw new TypeError("TypeError: invalid 'invokeConstructors' flag's type");if("boolean"!=typeof p)throw new TypeError("TypeError: invalid 'copyNonEnumerables' flag's type");if("boolean"!=typeof y)throw new TypeError("TypeError: invalid 'copySymbols' flag's type");if("boolean"!=typeof l)throw new TypeError("TypeError: invalid 'copyGettersSetters' flag's type");if("boolean"!=typeof d)throw new TypeError("TypeError: invalid 'allowCircularReferences' flag's type");if("boolean"!=typeof b)throw new TypeError("TypeError: invalid 'discardErrorObjects' flag's type");if("function"!=typeof E)throw new TypeError("TypeError: invalid 'customHandler' arguments's type");const v={setPrototype:t,invokeConstructors:n,copyNonEnumerables:p,copySymbols:y,copyGettersSetters:l,allowCircularReferences:d,discardErrorObjects:b};if(e instanceof Number||e instanceof String||e instanceof Boolean)return null;if(e instanceof Promise||e instanceof WeakMap||e instanceof WeakSet)return e;if(e instanceof Error)return r(b);if(e instanceof RegExp)return i(e);if(e instanceof Date)return a(e);if(!d){const t=f(e,p,y);if(u(t))throw new TypeError("TypeError: circular reference found")}if(e instanceof Map||e instanceof Set)return o(e,v,E);const w=E(e,{...v});return void 0!==w?w:e instanceof DataView?e:e instanceof ArrayBuffer?s(e):e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array?c(e):o(e,v,E)}},function(e,t,n){const o=n(9),r=n(13);e.exports=function(e,t,n){return function e(t,i,s,c){const{setPrototype:a,invokeConstructors:f,allowCircularReferences:u}=i;s.set(t,t);let p=null;if(p=f?new t.constructor:a?Object.create(Object.getPrototypeOf(t)):{},t instanceof Array&&(p=[]),t instanceof Map){p=new Map;const o=[...t.entries()];r(p,{mapEntries:o},i,c,s,e,n)}else if(t instanceof Set){p=new Set;const o=[...t.values()];r(p,{setEntries:o},i,c,s,e,n)}else{const o=Object.getOwnPropertyDescriptors(t);r(p,{ownPropsDcps:o},i,c,s,e,n)}return s.set(t,p),u&&c===t&&o(p,s),p}(e,t,new WeakMap,e)}},function(e,t,n){const o=n(10),r=n(11),i=n(12);e.exports=function(e,t){const n=new WeakMap;n.set(e),function e(t,n,s){return t instanceof Map?o(t,n,s,e):t instanceof Set?r(t,n,s,e):i(t,n,s,e)}(e,t,n)}},function(e,t){e.exports=((e,t,n,o)=>{const r=[...e.entries()];for(const[i,s]of r)if(s&&"object"==typeof s)if(t.has(s))e.set(i,t.get(s));else{if(n.has(s))continue;n.set(s),o(s,t,n)}})},function(e,t){e.exports=((e,t,n,o)=>{const r=[...e.values()];for(const i of r)if(i&&"object"==typeof i)if(t.has(i))e.delete(i),e.add(t.get(i));else{if(n.has(i))continue;n.set(i),o(i,t,n)}})},function(e,t){e.exports=((e,t,n,o)=>{const r=Object.getOwnPropertyDescriptors(e);Object.entries(r).forEach(([r,i])=>{if(i.set||i.get)return;const{value:s}=i;if(s&&"object"==typeof s)if(t.has(s))e[r]=t.get(s);else{if(n.has(s))return;n.set(s),o(s,t,n)}})})},function(e,t,n){const o=n(14),r=n(15),i=n(16);e.exports=function(e,t,n,s,c,a,f){return function(e,t,n,c){const{mapEntries:u,setEntries:p,ownPropsDcps:y}=t;if(u)return o(e,u,n,s,c,a,f);if(p)return r(e,p,n,s,c,a,f);if(y)return i(e,y,n,s,c,a,f);throw new Error("wrong data parameter for innerPropsHandler function")}(e,t,n,c)}},function(e,t,n){const o=n(0),r=n(1),i=n(2),s=n(5),c=n(6),a=n(3),f=n(4);e.exports=((e,t,n,u,p,y,l)=>{const d=t,{discardErrorObjects:b}=n;for(const[t,E]of d)if(E&&"object"==typeof E){const d=c(p,E);if(d){e.set(t,d);continue}if(E instanceof Error){o(b);continue}if(E instanceof Number||E instanceof Boolean||E instanceof String){e.set(t,s(E));continue}if(E instanceof Date){const n=i(E);e.set(t,n),p.set(E,n);continue}if(E instanceof RegExp){const n=r(E);e.set(t,n),p.set(E,n);continue}if(E instanceof Promise){e.set(t,E);continue}if(E instanceof WeakMap){e.set(t,E);continue}if(E instanceof WeakSet){e.set(t,E);continue}if(E instanceof Map||E instanceof Set){e.set(t,y(E,n,p,u));continue}const v=l(E,{...n});if(void 0!==v){e.set(t,v),p.set(E,v);continue}if(E instanceof DataView){e.set(t,E);continue}if(E instanceof ArrayBuffer){const n=f(E);e.set(t,n),p.set(E,n);continue}if(E instanceof Int8Array||E instanceof Uint8Array||E instanceof Uint8ClampedArray||E instanceof Int16Array||E instanceof Uint16Array||E instanceof Int32Array||E instanceof Uint32Array||E instanceof Float32Array||E instanceof Float64Array){const n=a(E);e.set(t,n),p.set(E,n);continue}e.set(t,y(E,n,p,u))}else e.set(t,E)})},function(e,t,n){const o=n(0),r=n(1),i=n(2),s=n(5),c=n(3),a=n(6),f=n(4);e.exports=((e,t,n,u,p,y,l)=>{const d=t,{discardErrorObjects:b}=n;for(const t of d)if(t&&"object"==typeof t){const d=a(p,t);if(d){e.add(d);continue}if(t instanceof Error){o(b);continue}if(t instanceof Number||t instanceof Boolean||t instanceof String){e.add(s(t));continue}if(t instanceof Date){const n=i(t);e.add(n),p.set(t,n);continue}if(t instanceof RegExp){const n=r(t);e.add(n),p.set(t,n);continue}if(t instanceof Promise){e.add(t);continue}if(t instanceof WeakMap){e.add(t);continue}if(t instanceof WeakSet){e.add(t);continue}if(t instanceof Map||t instanceof Set){e.add(y(t,n,p,u));continue}const E=l(t,{...n});if(void 0!==E){e.add(E),p.set(t,E);continue}if(t instanceof DataView){e.add(t);continue}if(t instanceof ArrayBuffer){const n=f(t);e.add(n),p.set(t,n);continue}if(t instanceof Int8Array||t instanceof Uint8Array||t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array){const n=c(t);e.add(n),p.set(t,n);continue}e.add(y(t,n,p,u))}else e.add(t)})},function(e,t,n){const o=n(0),r=n(1),i=n(2),s=n(4),c=n(3),a=n(5),f=n(6);e.exports=((e,t,n,u,p,y,l)=>{const d=t,{copyNonEnumerables:b,copySymbols:E,copyGettersSetters:v,discardErrorObjects:w}=n;Object.entries(d).forEach(([t,d])=>{const{value:j,enumerable:A}=d;if((b||A)&&(E||"symbol"!=typeof t)&&(v||!d.get&&!d.set))if(j&&"object"==typeof j){const b=f(p,j);if(b)return void(e[t]=b);if(j instanceof Error)return void o(w);if(j instanceof Number||j instanceof Boolean||j instanceof String){const n=a(j);return void Object.defineProperty(e,t,{...d,...{value:n}})}if(j instanceof Date){const n=i(j);return Object.defineProperty(e,t,{...d,...{value:n}}),void p.set(j,n)}if(j instanceof RegExp){const n=r(j);return Object.defineProperty(e,t,{...d,...{value:n}}),void p.set(j,n)}if(j instanceof Promise)return void Object.defineProperty(e,t,d);if(j instanceof WeakMap)return void Object.defineProperty(e,t,d);if(j instanceof WeakSet)return void Object.defineProperty(e,t,d);if(j instanceof Map||j instanceof Set)return void(e[t]=y(j,n,p,u));const E=l(j,{...n});if(void 0!==E)return e[t]=E,void p.set(j,E);if(j instanceof DataView)return void Object.defineProperty(e,t,d);if(j instanceof ArrayBuffer){const n=s(j);return Object.defineProperty(e,t,{...d,...{value:n}}),void p.set(j,n)}if(j instanceof Int8Array||j instanceof Uint8Array||j instanceof Uint8ClampedArray||j instanceof Int16Array||j instanceof Uint16Array||j instanceof Int32Array||j instanceof Uint32Array||j instanceof Float32Array||j instanceof Float64Array){const n=c(j);return Object.defineProperty(e,t,{...d,...{value:n}}),void p.set(j,n)}e[t]=y(j,n,p,u)}else{const n=Object.getOwnPropertyDescriptor(e,t);n&&!n.configurable||Object.defineProperty(e,t,d)}})})},function(e,t){e.exports=((e,t=!1,n=!1)=>{const o=new Set,r=new Map;return function e(i){o.add(i);const s=new Set;if(i instanceof Map)[...i.entries()].forEach(([,t])=>{"object"==typeof t&&(s.add(t),o.has(t)||e(t))});else if(i instanceof Set)[...i.values()].forEach(t=>{"object"==typeof t&&(s.add(t),o.has(t)||e(t))});else if(t||n){const r=Object.getOwnPropertyDescriptors(i);Object.entries(r).forEach(([r,i])=>{if(i.set||i.get)return;if(!1===i.enumerable&&!1===t)return;if("symbol"==typeof r&&!1===n)return;const{value:c}=i;c&&"object"==typeof c&&(s.add(c),o.has(c)||e(c))})}else Object.values(i).forEach(t=>{"object"==typeof t&&(s.add(t),o.has(t)||e(t))});r.set(i,s)}(e),r})},function(e,t){e.exports=function(e){return function e(t){if(0===t.size)return t;const n=[...t.entries()].find(([,e])=>0===e.size);if(!n)return t;const[o]=n;return function(e,t){[...t.entries()].forEach(([,t])=>{t.has(e)&&t.delete(e)})}(o,t),t.delete(o),e(t)}(e).size}}]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "omniclone", 3 | "version": "0.8.0", 4 | "description": "deep cloning function for js objects", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "test": "jest --watchAll --coverage", 8 | "dev": "webpack --mode development src/omniclone.js", 9 | "build": "webpack --mode production src/omniclone.js", 10 | "lint": "eslint" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/jfet97/omniclone.git" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "keywords": [ 20 | "javascript", 21 | "clone", 22 | "cloning", 23 | "object", 24 | "objetcs", 25 | "circular", 26 | "reference", 27 | "references" 28 | ], 29 | "author": "Andrea Simone Costa ", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/jfet97/omniclone/issues" 33 | }, 34 | "homepage": "https://github.com/jfet97/omniclone#readme", 35 | "devDependencies": { 36 | "babel-jest": "^23.6.0", 37 | "babel-preset-env": "^1.7.0", 38 | "eslint": "^5.11.1", 39 | "eslint-config-airbnb-base": "^13.1.0", 40 | "eslint-config-prettier": "^3.3.0", 41 | "eslint-plugin-import": "^2.14.0", 42 | "eslint-plugin-jest": "^22.1.2", 43 | "eslint-plugin-prettier": "^3.0.1", 44 | "jest": "^23.6.0", 45 | "prettier": "^1.15.3", 46 | "webpack": "^4.28.3", 47 | "webpack-cli": "^3.2.0" 48 | }, 49 | "jest": { 50 | "transform": { 51 | "^.+\\.js$": "babel-jest" 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/deepClone.js: -------------------------------------------------------------------------------- 1 | const updateReferences = require("./updateReferences"); 2 | const propsHandler = require("./propsHandler"); 3 | 4 | function deepClone(source, config, customHandler) { 5 | // already visited references map 6 | // each analized object will store its reference here 7 | // so we can check each of its chilren object to see if there are 8 | // references to already analized objects 9 | const references = new WeakMap(); 10 | 11 | // A reference to the initial source object 12 | const start = source; 13 | 14 | return (function innerDeepClone(source, config, references, start) { 15 | const { 16 | setPrototype, 17 | invokeConstructors, 18 | allowCircularReferences 19 | } = config; 20 | 21 | // set a reference for the current obj into the guard 22 | // the value stored does not matter if the allowCircularReferences is not enabled 23 | // is the reference the fulcrum of the control in this case 24 | // otherwise it's essential for the final circ references update 25 | references.set(source, source); 26 | 27 | // result value 28 | let res = null; 29 | 30 | if (invokeConstructors) { 31 | // invokeConstructors flag indicates if the source constructor 32 | // must be invocated. 33 | res = new source.constructor(); 34 | // if so, the [[Prototype]] prop is set to constructor.protoype 35 | // so it could be different from the source [[Prototype]] 36 | } else if (setPrototype) { 37 | // if not, we have to choose what to do with the [[Prototype]] prop 38 | // setPrototype flag indicates if we have to set up the same [[Prototype]] prop 39 | // as the source object or not 40 | // so the constructor property will be setted like in the previous case 41 | // but without invoking the constructor 42 | res = Object.create(Object.getPrototypeOf(source)); 43 | } else { 44 | res = {}; 45 | } 46 | 47 | // special case: Array 48 | // to properly create arrays even when invokeConstructors flag is false 49 | // and/or when setPrototype flag is false too 50 | if (source instanceof Array) { 51 | res = []; 52 | } 53 | 54 | if (source instanceof Map) { 55 | // special case: Map 56 | // to properly create maps even when invokeConstructors flag is false 57 | // and/or when setPrototype flag is false too 58 | res = new Map(); 59 | 60 | // get the entries array 61 | const mapEntries = [...source.entries()]; 62 | 63 | // deep copy each entries from the source map to the map res 64 | propsHandler( 65 | res, 66 | { mapEntries }, 67 | config, 68 | start, 69 | references, 70 | innerDeepClone, 71 | customHandler 72 | ); 73 | } else if (source instanceof Set) { 74 | // special case: Set 75 | // to properly create sets even when invokeConstructors flag is false 76 | // and/or when setPrototype flag is false too 77 | res = new Set(); 78 | 79 | // get the values array 80 | const setEntries = [...source.values()]; 81 | 82 | // deep copy each entries from the source map to the map res 83 | propsHandler( 84 | res, 85 | { setEntries }, 86 | config, 87 | start, 88 | references, 89 | innerDeepClone, 90 | customHandler 91 | ); 92 | } else { 93 | // get all the property descriptors from the source object (ownPropsDcps is an object) 94 | const ownPropsDcps = Object.getOwnPropertyDescriptors(source); 95 | 96 | // deep copy each prop from the source object to the res object 97 | propsHandler( 98 | res, 99 | { ownPropsDcps }, 100 | config, 101 | start, 102 | references, 103 | innerDeepClone, 104 | customHandler 105 | ); 106 | } 107 | 108 | // each time an object is cloned I update the references map 109 | // with its new reference. The object could still have some old circ refs 110 | // but I'll handle this later 111 | references.set(source, res); 112 | 113 | // circular references update from temp old values to new ones 114 | // we don't it if allowCircularReferences is false because the previous check 115 | // in omniclone.js would have trown an error 116 | if (allowCircularReferences && start === source) { 117 | // if I've recursively handled all 'virtual' children 118 | // I've completely updated the references map 119 | // Now I have to recursively update all old circ refs to the new ones 120 | updateReferences(res, references); 121 | } 122 | 123 | // return the result 124 | return res; 125 | })(source, config, references, start); 126 | } 127 | 128 | module.exports = deepClone; 129 | -------------------------------------------------------------------------------- /src/handlers/arrayBufferObjectsHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = arrayBuffer => arrayBuffer.slice(); 2 | -------------------------------------------------------------------------------- /src/handlers/dateObjectsHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = date => new Date(date.getTime()); 2 | -------------------------------------------------------------------------------- /src/handlers/errorsObjectsHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = discardErrorObjectsFlag => { 2 | if (discardErrorObjectsFlag) { 3 | return null; 4 | } 5 | throw new TypeError("TypeError: cannot copy Error objects"); 6 | }; 7 | -------------------------------------------------------------------------------- /src/handlers/mapEntriesHandler.js: -------------------------------------------------------------------------------- 1 | const errorObjectsHandler = require("./errorsObjectsHandler"); 2 | const regexpObjectsHandler = require("./regexpObjectsHanlder"); 3 | const dateObjectsHandler = require("./dateObjectsHandler"); 4 | const primitiveObjectsHandler = require("./primitiveObjectsHandler"); 5 | const prevReferencesHelper = require("./../utility/prevReferencesHelper"); 6 | const typedArraysObjectsHandler = require("./typedArraysObjectsHandler"); 7 | const arrayBufferObjectsHandler = require("./arrayBufferObjectsHandler"); 8 | 9 | module.exports = ( 10 | res, 11 | data, 12 | config, 13 | start, 14 | references, 15 | recursiveDeepCloning, 16 | customHandler 17 | ) => { 18 | const mapEntries = data; 19 | 20 | const { discardErrorObjects } = config; 21 | 22 | for (const [key, value] of mapEntries) { 23 | if (value && typeof value === "object") { 24 | // check if I've already found this object 25 | const prevRef = prevReferencesHelper(references, value); 26 | if (prevRef) { 27 | res.set(key, prevRef); 28 | continue; 29 | } 30 | 31 | // check discardErrorObjects flag to see how to handle Error objects 32 | if (value instanceof Error) { 33 | errorObjectsHandler(discardErrorObjects); 34 | continue; 35 | } 36 | 37 | // The Boolean, Number, and String objects are converted 38 | // to the corresponding primitive values 39 | if ( 40 | value instanceof Number || 41 | value instanceof Boolean || 42 | value instanceof String 43 | ) { 44 | res.set(key, primitiveObjectsHandler(value)); 45 | continue; 46 | } 47 | 48 | // Date prop objects are cloned mantaining the same Date 49 | if (value instanceof Date) { 50 | const newDate = dateObjectsHandler(value); 51 | 52 | res.set(key, newDate); 53 | 54 | // set the object reference to speedup in case of duplicates 55 | references.set(value, newDate); 56 | continue; 57 | } 58 | 59 | // RegExp cloning is automatically supported 60 | if (value instanceof RegExp) { 61 | const clonedRegexp = regexpObjectsHandler(value); 62 | res.set(key, clonedRegexp); 63 | 64 | // set the object reference to speedup in case of duplicates 65 | references.set(value, clonedRegexp); 66 | continue; 67 | } 68 | 69 | // Promises are cloned by reference 70 | if (value instanceof Promise) { 71 | res.set(key, value); 72 | continue; 73 | } 74 | 75 | // WeakMaps are cloned by reference 76 | if (value instanceof WeakMap) { 77 | res.set(key, value); 78 | continue; 79 | } 80 | 81 | // WeakSets are cloned by reference 82 | if (value instanceof WeakSet) { 83 | res.set(key, value); 84 | continue; 85 | } 86 | 87 | if (value instanceof Map || value instanceof Set) { 88 | res.set(key, recursiveDeepCloning(value, config, references, start)); 89 | continue; 90 | } 91 | 92 | // the custom Handler has more priority than ArrayBuffer and TypedArray and DataView objects but less tham Maps and Sets 93 | 94 | // custom Handler 95 | const customHandlerReturnValue = customHandler(value, { ...config }); 96 | if (customHandlerReturnValue !== undefined) { 97 | res.set(key, customHandlerReturnValue); 98 | // set the object reference to speedup in case of duplicates somewhere else 99 | references.set(value, customHandlerReturnValue); 100 | continue; 101 | } 102 | 103 | // copy by reference for DataView objects 104 | if (value instanceof DataView) { 105 | res.set(key, value); 106 | continue; 107 | } 108 | 109 | // deep copy of ArrayBuffer objects 110 | if (value instanceof ArrayBuffer) { 111 | const clonedArrayBuffer = arrayBufferObjectsHandler(value); 112 | 113 | res.set(key, clonedArrayBuffer); 114 | 115 | // set the object reference to speedup in case of duplicates somewhere else 116 | references.set(value, clonedArrayBuffer); 117 | 118 | continue; 119 | } 120 | 121 | // deep copy of TypedArray objects 122 | if ( 123 | value instanceof Int8Array || 124 | value instanceof Uint8Array || 125 | value instanceof Uint8ClampedArray || 126 | value instanceof Int16Array || 127 | value instanceof Uint16Array || 128 | value instanceof Int32Array || 129 | value instanceof Uint32Array || 130 | value instanceof Float32Array || 131 | value instanceof Float64Array 132 | ) { 133 | const clonedTypedArray = typedArraysObjectsHandler(value); 134 | 135 | res.set(key, clonedTypedArray); 136 | 137 | // set the object reference to speedup in case of duplicates 138 | references.set(value, clonedTypedArray); 139 | continue; 140 | } 141 | 142 | // recursive deep copy for the others object props 143 | res.set(key, recursiveDeepCloning(value, config, references, start)); 144 | } else { 145 | // not an object (numeric values, functions, symbols) 146 | res.set(key, value); 147 | } 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /src/handlers/otherObjectsDescriptorsHandler.js: -------------------------------------------------------------------------------- 1 | const errorObjectsHandler = require("./errorsObjectsHandler"); 2 | const regexpObjectsHandler = require("./regexpObjectsHanlder"); 3 | const dateObjectsHandler = require("./dateObjectsHandler"); 4 | const arrayBufferObjectsHandler = require("./arrayBufferObjectsHandler"); 5 | const typedArraysObjectsHandler = require("./typedArraysObjectsHandler"); 6 | const primitiveObjectsHandler = require("./primitiveObjectsHandler"); 7 | const prevReferencesHelper = require("./../utility/prevReferencesHelper"); 8 | 9 | module.exports = ( 10 | res, 11 | data, 12 | config, 13 | start, 14 | references, 15 | recursiveDeepCloning, 16 | customHandler 17 | ) => { 18 | const descriptors = data; 19 | const { 20 | copyNonEnumerables, 21 | copySymbols, 22 | copyGettersSetters, 23 | discardErrorObjects 24 | } = config; 25 | 26 | Object.entries(descriptors).forEach(([prop, descriptor]) => { 27 | const { value, enumerable } = descriptor; 28 | 29 | // the copyNonEnumerables setted to true indicates that 30 | // we can copy non enumerable props 31 | // if we mustn't copy non enumerables and the current prop is no enumerable we return 32 | if (!copyNonEnumerables && !enumerable) return; 33 | 34 | // the copySymbols setted to true indicates that 35 | // we can copy symbol props 36 | // if we mustn't copy symbols and the current prop is a symbol we return 37 | if (!copySymbols && typeof prop === "symbol") return; 38 | 39 | // copyGettersSetters setted to true indicates that 40 | // we can copy getters and setters 41 | // if we mustn't copy g||s and the current prop has g||s we return 42 | 43 | if (!copyGettersSetters && (descriptor.get || descriptor.set)) return; 44 | 45 | if (value && typeof value === "object") { 46 | // check if I've already found this object 47 | const prevRef = prevReferencesHelper(references, value); 48 | if (prevRef) { 49 | res[prop] = prevRef; 50 | return; 51 | } 52 | 53 | // check discardErrorObjects flag to see how to handle Error objects 54 | if (value instanceof Error) { 55 | errorObjectsHandler(discardErrorObjects); 56 | return; 57 | } 58 | 59 | // The Boolean, Number, and String objects are converted 60 | // to the corresponding primitive values 61 | if ( 62 | value instanceof Number || 63 | value instanceof Boolean || 64 | value instanceof String 65 | ) { 66 | const primitiveValue = primitiveObjectsHandler(value); 67 | Object.defineProperty(res, prop, { 68 | ...descriptor, 69 | ...{ value: primitiveValue } 70 | }); 71 | return; 72 | } 73 | 74 | // Date prop objects are cloned mantaining the same Date 75 | if (value instanceof Date) { 76 | const newDate = dateObjectsHandler(value); 77 | 78 | Object.defineProperty(res, prop, { 79 | ...descriptor, 80 | ...{ value: newDate } 81 | }); 82 | 83 | // set the object reference to speedup in case of duplicates 84 | references.set(value, newDate); 85 | 86 | return; 87 | } 88 | 89 | // RegExp cloning is automatically supported 90 | if (value instanceof RegExp) { 91 | const clonedRegexp = regexpObjectsHandler(value); 92 | 93 | Object.defineProperty(res, prop, { 94 | ...descriptor, 95 | ...{ value: clonedRegexp } 96 | }); 97 | 98 | // set the object reference to speedup in case of duplicates 99 | references.set(value, clonedRegexp); 100 | 101 | return; 102 | } 103 | 104 | // Promises are cloned by reference 105 | if (value instanceof Promise) { 106 | Object.defineProperty(res, prop, descriptor); 107 | return; 108 | } 109 | 110 | // WeakMaps are cloned by reference 111 | if (value instanceof WeakMap) { 112 | Object.defineProperty(res, prop, descriptor); 113 | return; 114 | } 115 | 116 | // WeakSets are cloned by reference 117 | if (value instanceof WeakSet) { 118 | Object.defineProperty(res, prop, descriptor); 119 | return; 120 | } 121 | 122 | if (value instanceof Map || value instanceof Set) { 123 | res[prop] = recursiveDeepCloning(value, config, references, start); 124 | return; 125 | } 126 | 127 | // the custom Handler has more priority than ArrayBuffer and TypedArray and DataView objects but less tham Maps and Sets 128 | 129 | // custom Handler 130 | const customHandlerReturnValue = customHandler(value, { ...config }); 131 | if (customHandlerReturnValue !== undefined) { 132 | res[prop] = customHandlerReturnValue; 133 | // set the object reference to speedup in case of duplicates somewhere else 134 | references.set(value, customHandlerReturnValue); 135 | return; 136 | } 137 | 138 | // copy by reference for DataView objects 139 | if (value instanceof DataView) { 140 | Object.defineProperty(res, prop, descriptor); 141 | return; 142 | } 143 | 144 | // deep copy of ArrayBuffer objects 145 | if (value instanceof ArrayBuffer) { 146 | const clonedArrayBuffer = arrayBufferObjectsHandler(value); 147 | 148 | Object.defineProperty(res, prop, { 149 | ...descriptor, 150 | ...{ value: clonedArrayBuffer } 151 | }); 152 | 153 | // set the object reference to speedup in case of duplicates 154 | references.set(value, clonedArrayBuffer); 155 | 156 | return; 157 | } 158 | 159 | // deep copy of TypedArray objects 160 | if ( 161 | value instanceof Int8Array || 162 | value instanceof Uint8Array || 163 | value instanceof Uint8ClampedArray || 164 | value instanceof Int16Array || 165 | value instanceof Uint16Array || 166 | value instanceof Int32Array || 167 | value instanceof Uint32Array || 168 | value instanceof Float32Array || 169 | value instanceof Float64Array 170 | ) { 171 | const clonedTypedArray = typedArraysObjectsHandler(value); 172 | 173 | Object.defineProperty(res, prop, { 174 | ...descriptor, 175 | ...{ value: clonedTypedArray } 176 | }); 177 | 178 | // set the object reference to speedup in case of duplicates 179 | references.set(value, clonedTypedArray); 180 | return; 181 | } 182 | 183 | // recursive deep copy for the others object props 184 | res[prop] = recursiveDeepCloning(value, config, references, start); 185 | } else { 186 | const propDesc = Object.getOwnPropertyDescriptor(res, prop); 187 | if (!propDesc || propDesc.configurable) { 188 | // shallow copy for others props only if the previously invoked constructor has not already insert 189 | // a corresponding non config prop 190 | Object.defineProperty(res, prop, descriptor); 191 | } 192 | } 193 | }); 194 | }; 195 | -------------------------------------------------------------------------------- /src/handlers/primitiveObjectsHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = primitive => primitive.valueOf(); 2 | -------------------------------------------------------------------------------- /src/handlers/regexpObjectsHanlder.js: -------------------------------------------------------------------------------- 1 | module.exports = regexp => { 2 | const { source, flags, lastIndex } = regexp; 3 | const newReg = new RegExp(source, flags); 4 | newReg.lastIndex = lastIndex; 5 | return newReg; 6 | }; 7 | -------------------------------------------------------------------------------- /src/handlers/setEntriesHandler.js: -------------------------------------------------------------------------------- 1 | const errorObjectsHandler = require("./errorsObjectsHandler"); 2 | const regexpObjectsHandler = require("./regexpObjectsHanlder"); 3 | const dateObjectsHandler = require("./dateObjectsHandler"); 4 | const primitiveObjectsHandler = require("./primitiveObjectsHandler"); 5 | const typedArraysObjectsHandler = require("./typedArraysObjectsHandler"); 6 | const prevReferencesHelper = require("./../utility/prevReferencesHelper"); 7 | const arrayBufferObjectsHandler = require("./arrayBufferObjectsHandler"); 8 | 9 | module.exports = ( 10 | res, 11 | data, 12 | config, 13 | start, 14 | references, 15 | recursiveDeepCloning, 16 | customHandler 17 | ) => { 18 | const setEntries = data; 19 | 20 | const { discardErrorObjects } = config; 21 | 22 | // for set key == value 23 | 24 | for (const value of setEntries) { 25 | if (value && typeof value === "object") { 26 | // check if I've already found this object 27 | const prevRef = prevReferencesHelper(references, value); 28 | if (prevRef) { 29 | res.add(prevRef); 30 | continue; 31 | } 32 | 33 | // check discardErrorObjects flag to see how to handle Error objects 34 | if (value instanceof Error) { 35 | errorObjectsHandler(discardErrorObjects); 36 | continue; 37 | } 38 | 39 | // The Boolean, Number, and String objects are converted 40 | // to the corresponding primitive values 41 | if ( 42 | value instanceof Number || 43 | value instanceof Boolean || 44 | value instanceof String 45 | ) { 46 | res.add(primitiveObjectsHandler(value)); 47 | continue; 48 | } 49 | 50 | // Date prop objects are cloned mantaining the same Date 51 | if (value instanceof Date) { 52 | const newDate = dateObjectsHandler(value); 53 | res.add(newDate); 54 | 55 | // set the object reference to speedup in case of duplicates somewhere else 56 | references.set(value, newDate); 57 | 58 | continue; 59 | } 60 | 61 | // RegExp cloning is automatically supported 62 | if (value instanceof RegExp) { 63 | const clonedRegexp = regexpObjectsHandler(value); 64 | res.add(clonedRegexp); 65 | 66 | // set the object reference to speedup in case of duplicates somewhere else 67 | references.set(value, clonedRegexp); 68 | 69 | continue; 70 | } 71 | 72 | // Promises are cloned by reference 73 | if (value instanceof Promise) { 74 | res.add(value); 75 | continue; 76 | } 77 | 78 | // WeakMaps are cloned by reference 79 | if (value instanceof WeakMap) { 80 | res.add(value); 81 | continue; 82 | } 83 | 84 | // WeakSets are cloned by reference 85 | if (value instanceof WeakSet) { 86 | res.add(value); 87 | continue; 88 | } 89 | 90 | if (value instanceof Map || value instanceof Set) { 91 | res.add(recursiveDeepCloning(value, config, references, start)); 92 | continue; 93 | } 94 | 95 | // the custom Handler has more priority than ArrayBuffer and TypedArray and DataView objects but less tham Maps and Sets 96 | 97 | // custom Handler 98 | const customHandlerReturnValue = customHandler(value, { ...config }); 99 | if (customHandlerReturnValue !== undefined) { 100 | res.add(customHandlerReturnValue); 101 | // set the object reference to speedup in case of duplicates somewhere else 102 | references.set(value, customHandlerReturnValue); 103 | continue; 104 | } 105 | 106 | // copy by reference for DataView objects 107 | if (value instanceof DataView) { 108 | res.add(value); 109 | continue; 110 | } 111 | 112 | // deep copy of ArrayBuffer objects 113 | if (value instanceof ArrayBuffer) { 114 | const clonedArrayBuffer = arrayBufferObjectsHandler(value); 115 | 116 | res.add(clonedArrayBuffer); 117 | 118 | // set the object reference to speedup in case of duplicates somewhere else 119 | references.set(value, clonedArrayBuffer); 120 | continue; 121 | } 122 | 123 | // deep copy of TypedArray objects 124 | if ( 125 | value instanceof Int8Array || 126 | value instanceof Uint8Array || 127 | value instanceof Uint8ClampedArray || 128 | value instanceof Int16Array || 129 | value instanceof Uint16Array || 130 | value instanceof Int32Array || 131 | value instanceof Uint32Array || 132 | value instanceof Float32Array || 133 | value instanceof Float64Array 134 | ) { 135 | const clonedTypedArray = typedArraysObjectsHandler(value); 136 | 137 | res.add(clonedTypedArray); 138 | 139 | // set the object reference to speedup in case of duplicates 140 | references.set(value, clonedTypedArray); 141 | continue; 142 | } 143 | 144 | // recursive deep copy for the others object props 145 | res.add(recursiveDeepCloning(value, config, references, start)); 146 | } else { 147 | // not an object (numeric values, functions, symbols) 148 | res.add(value); 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /src/handlers/typedArraysObjectsHandler.js: -------------------------------------------------------------------------------- 1 | module.exports = typedArray => typedArray.slice(); 2 | -------------------------------------------------------------------------------- /src/omniclone.js: -------------------------------------------------------------------------------- 1 | const deepClone = require("./deepClone"); 2 | const errorObjectsHandler = require("./handlers/errorsObjectsHandler"); 3 | const regexpObjectsHandler = require("./handlers/regexpObjectsHanlder"); 4 | const arrayBufferObjectsHandler = require("./handlers/arrayBufferObjectsHandler"); 5 | const typedArraysObjectsHandler = require("./handlers/typedArraysObjectsHandler"); 6 | const dateObjectsHandler = require("./handlers/dateObjectsHandler"); 7 | const createDependenciesMap = require("./utility/createDependenciesMap"); 8 | const checkCircRef = require("./utility/dependenciesMapHandler"); 9 | 10 | function omniclone( 11 | obj = {}, 12 | { 13 | setPrototype = false, 14 | invokeConstructors = true, 15 | copyNonEnumerables = false, 16 | copySymbols = false, 17 | copyGettersSetters = false, 18 | allowCircularReferences = true, 19 | discardErrorObjects = true 20 | } = {}, 21 | customHandler = () => {} 22 | ) { 23 | if (!obj || typeof obj !== "object") { 24 | throw new TypeError("TypeError: invalid 'obj' argument's type"); 25 | } 26 | 27 | if (typeof setPrototype !== "boolean") { 28 | throw new TypeError("TypeError: invalid 'setPrototype' flag's type"); 29 | } 30 | 31 | if (typeof invokeConstructors !== "boolean") { 32 | throw new TypeError("TypeError: invalid 'invokeConstructors' flag's type"); 33 | } 34 | 35 | if (typeof copyNonEnumerables !== "boolean") { 36 | throw new TypeError("TypeError: invalid 'copyNonEnumerables' flag's type"); 37 | } 38 | 39 | if (typeof copySymbols !== "boolean") { 40 | throw new TypeError("TypeError: invalid 'copySymbols' flag's type"); 41 | } 42 | 43 | if (typeof copyGettersSetters !== "boolean") { 44 | throw new TypeError("TypeError: invalid 'copyGettersSetters' flag's type"); 45 | } 46 | 47 | if (typeof allowCircularReferences !== "boolean") { 48 | throw new TypeError( 49 | "TypeError: invalid 'allowCircularReferences' flag's type" 50 | ); 51 | } 52 | 53 | if (typeof discardErrorObjects !== "boolean") { 54 | throw new TypeError("TypeError: invalid 'discardErrorObjects' flag's type"); 55 | } 56 | 57 | // eslint-disable-next-line valid-typeof 58 | if (typeof customHandler !== "function") { 59 | throw new TypeError("TypeError: invalid 'customHandler' arguments's type"); 60 | } 61 | 62 | const config = { 63 | setPrototype, 64 | invokeConstructors, 65 | copyNonEnumerables, 66 | copySymbols, 67 | copyGettersSetters, 68 | allowCircularReferences, 69 | discardErrorObjects 70 | }; 71 | 72 | if ( 73 | obj instanceof Number || 74 | obj instanceof String || 75 | obj instanceof Boolean 76 | ) { 77 | return null; 78 | } 79 | 80 | if ( 81 | obj instanceof Promise || 82 | obj instanceof WeakMap || 83 | obj instanceof WeakSet 84 | ) { 85 | return obj; 86 | } 87 | 88 | if (obj instanceof Error) { 89 | return errorObjectsHandler(discardErrorObjects); 90 | } 91 | 92 | if (obj instanceof RegExp) { 93 | return regexpObjectsHandler(obj); 94 | } 95 | 96 | // Date objects are cloned mantaining the same Date 97 | if (obj instanceof Date) { 98 | return dateObjectsHandler(obj); 99 | } 100 | 101 | if (!allowCircularReferences) { 102 | // the internal algorithm is too semplicistic, it search only back references 103 | // so we have to force the allowCircularReferences if there are not 104 | const depsMap = createDependenciesMap(obj, copyNonEnumerables, copySymbols); 105 | // eslint-disable-next-line no-param-reassign 106 | if (checkCircRef(depsMap)) 107 | throw new TypeError("TypeError: circular reference found"); 108 | } 109 | 110 | if (obj instanceof Map || obj instanceof Set) 111 | return deepClone(obj, config, customHandler); 112 | 113 | // the custom Handler has more priority than ArrayBuffer and TypedArray and DataView objects but less tham Maps and Sets 114 | 115 | // custom Handler 116 | const customHandlerReturnValue = customHandler(obj, { ...config }); 117 | if (customHandlerReturnValue !== undefined) { 118 | return customHandlerReturnValue; 119 | } 120 | 121 | // copy by reference for DataView objects 122 | if (obj instanceof DataView) { 123 | return obj; 124 | } 125 | 126 | // deep copy of ArrayBuffer objects 127 | if (obj instanceof ArrayBuffer) { 128 | return arrayBufferObjectsHandler(obj); 129 | } 130 | 131 | // deep copy of TypedArray objects 132 | if ( 133 | obj instanceof Int8Array || 134 | obj instanceof Uint8Array || 135 | obj instanceof Uint8ClampedArray || 136 | obj instanceof Int16Array || 137 | obj instanceof Uint16Array || 138 | obj instanceof Int32Array || 139 | obj instanceof Uint32Array || 140 | obj instanceof Float32Array || 141 | obj instanceof Float64Array 142 | ) { 143 | return typedArraysObjectsHandler(obj); 144 | } 145 | 146 | // deep clone the obj props 147 | return deepClone(obj, config, customHandler); 148 | } 149 | 150 | module.exports = omniclone; 151 | -------------------------------------------------------------------------------- /src/propsHandler.js: -------------------------------------------------------------------------------- 1 | const mapEntriesHandler = require("./handlers/mapEntriesHandler"); 2 | const setEntriesHandler = require("./handlers/setEntriesHandler"); 3 | const otherObjectsDescriptorsHandler = require("./handlers/otherObjectsDescriptorsHandler"); 4 | 5 | module.exports = function propsHandler( 6 | res, 7 | data, 8 | config, 9 | start, 10 | references, 11 | deepClone, 12 | customHandler 13 | ) { 14 | return (function innerPropsHandler(res, data, config, references) { 15 | const { mapEntries, setEntries, ownPropsDcps: descriptors } = data; 16 | 17 | if (mapEntries) { 18 | // we are dealing with map entries 19 | return mapEntriesHandler( 20 | res, 21 | mapEntries, 22 | config, 23 | start, 24 | references, 25 | // eslint-disable-next-line no-use-before-define 26 | deepClone, 27 | customHandler 28 | ); 29 | } 30 | 31 | if (setEntries) { 32 | // we are dealing with set entries 33 | return setEntriesHandler( 34 | res, 35 | setEntries, 36 | config, 37 | start, 38 | references, 39 | // eslint-disable-next-line no-use-before-define 40 | deepClone, 41 | customHandler 42 | ); 43 | } 44 | 45 | if (descriptors) { 46 | return otherObjectsDescriptorsHandler( 47 | res, 48 | descriptors, 49 | config, 50 | start, 51 | references, 52 | // eslint-disable-next-line no-use-before-define 53 | deepClone, 54 | customHandler 55 | ); 56 | } 57 | 58 | throw new Error("wrong data parameter for innerPropsHandler function"); 59 | })(res, data, config, references); 60 | }; 61 | -------------------------------------------------------------------------------- /src/updateReferences.js: -------------------------------------------------------------------------------- 1 | const updateCircReferencesIntoMapObjects = require("./utility/updateCircReferencesIntoMapObjects"); 2 | const updateCircReferencesIntoSetObjects = require("./utility/updateCircReferencesIntoSetObjects"); 3 | const updateCircReferencesIntoOtherObjects = require("./utility/updateCircReferencesIntoOtherObjects"); 4 | 5 | module.exports = function updateReferences(res, references) { 6 | // res is an object 7 | 8 | const alreadyVisitedMap = new WeakMap(); 9 | // we start from res, so it is already visited 10 | alreadyVisitedMap.set(res); 11 | 12 | (function innerUpdateReferences(res, references, alreadyVisitedMap) { 13 | if (res instanceof Map) { 14 | return updateCircReferencesIntoMapObjects( 15 | res, 16 | references, 17 | alreadyVisitedMap, 18 | innerUpdateReferences 19 | ); 20 | } 21 | if (res instanceof Set) { 22 | return updateCircReferencesIntoSetObjects( 23 | res, 24 | references, 25 | alreadyVisitedMap, 26 | innerUpdateReferences 27 | ); 28 | } 29 | return updateCircReferencesIntoOtherObjects( 30 | res, 31 | references, 32 | alreadyVisitedMap, 33 | innerUpdateReferences 34 | ); 35 | })(res, references, alreadyVisitedMap); 36 | }; 37 | -------------------------------------------------------------------------------- /src/utility/createDependenciesMap.js: -------------------------------------------------------------------------------- 1 | // it create a Map of set 2 | // each set cointain a list of all the "dependencies" of a node N 3 | // a dependence is simply a node referred by N 4 | // a set because we do not care about same sibiling object references 5 | // one reference to a node is equal to 10 reference to the same node if the reference are stored in the same object 6 | // because we are looking for circ references. If the one reference was a circ reference it would have been the same with 10 7 | 8 | module.exports = ( 9 | entryPoint, 10 | copyNonEnumerables = false, 11 | copySymbols = false 12 | ) => { 13 | const alreadyVisited = new Set(); 14 | const allDependenciesMap = new Map(); 15 | 16 | (function createDependenciesMap(obj) { 17 | // add the current obj to the alreadyVisited set guard 18 | alreadyVisited.add(obj); 19 | 20 | const depsSet = new Set(); 21 | 22 | if (obj instanceof Map) { 23 | [...obj.entries()].forEach(([, value]) => { 24 | // we do not care about type of key because 25 | // a map will be completely cloned, always 26 | 27 | // discard functions (always entirely copied by reference) 28 | if (typeof value === "object") { 29 | // we have to worry only about object prop 30 | depsSet.add(value); 31 | 32 | // if the object value has not already been visited, we analize it 33 | if (!alreadyVisited.has(value)) { 34 | createDependenciesMap(value); 35 | } 36 | } 37 | }); 38 | } else if (obj instanceof Set) { 39 | [...obj.values()].forEach(value => { 40 | // we do not care about type of values because 41 | // a set will be completely cloned, always 42 | 43 | // discard functions (always entirely copied by reference) 44 | if (typeof value === "object") { 45 | // we have to worry only about object prop 46 | depsSet.add(value); 47 | 48 | // if the object value has not already been visited, we analize it 49 | if (!alreadyVisited.has(value)) { 50 | createDependenciesMap(value); 51 | } 52 | } 53 | }); 54 | } else if (copyNonEnumerables || copySymbols) { 55 | // normal object case 56 | // if we have to care about symbols or non enum props... 57 | 58 | const descriptors = Object.getOwnPropertyDescriptors(obj); 59 | 60 | Object.entries(descriptors).forEach(([prop, descriptor]) => { 61 | // descriptor.value == value of each prop == potential reference to a node 62 | 63 | if (descriptor.set || descriptor.get) { 64 | // don't care about getters and setters 65 | // them are functions that will be copied by reference 66 | return; 67 | } 68 | 69 | if (descriptor.enumerable === false && copyNonEnumerables === false) { 70 | // if so we have to don't care about non-enum props because 71 | // them will be not copied 72 | return; 73 | } 74 | 75 | if (typeof prop === "symbol" && copySymbols === false) { 76 | // if so we have to don't care about symbol props because 77 | // them will be not copied 78 | return; 79 | } 80 | 81 | // if we get here the prop (/)that refers to a value or to any type of object) could be 82 | // an enum prop non symbol prop so we have to worry about it 83 | // or a non-enum prop non symbol but the flag copyNonEnumerables is true so we have to worry about it 84 | // or an enum symbol prop (the key is a symbol) but the flag copySymbol is true so we have to worry about it 85 | // or an non enum symbol prop (the key is a symbol) but both fag are set to true so we have to worry about it 86 | 87 | // in short words we have already discarded what we don't want 88 | 89 | const { value } = descriptor; 90 | 91 | // discard functions 92 | if (value && typeof value === "object") { 93 | // we have to worry only about object prop 94 | depsSet.add(value); 95 | 96 | // if the object value has not already been visited, we analize it 97 | if (!alreadyVisited.has(value)) { 98 | createDependenciesMap(value, copyNonEnumerables, copySymbols); 99 | } 100 | } 101 | }); 102 | } else { 103 | // normal object case 104 | // if we care only about enum non symbol own props 105 | Object.values(obj).forEach(value => { 106 | // value == value of each prop == potential reference to a node 107 | 108 | // discard functions 109 | if (typeof value === "object") { 110 | // we have to worry only about object prop 111 | depsSet.add(value); 112 | 113 | // if the object value has not already been visited,, we analize it 114 | if (!alreadyVisited.has(value)) { 115 | createDependenciesMap(value, copyNonEnumerables, copySymbols); 116 | } 117 | } 118 | }); 119 | } 120 | 121 | // here the node is completely analized 122 | allDependenciesMap.set(obj, depsSet); 123 | })(entryPoint); 124 | 125 | return allDependenciesMap; 126 | }; 127 | -------------------------------------------------------------------------------- /src/utility/dependenciesMapHandler.js: -------------------------------------------------------------------------------- 1 | // check if a graph is topologically sortable 2 | // analizyng the dependencies Map 3 | // 4 | // if the result of the process is not an empty map there are 5 | // circ referencies 6 | // 7 | // https://stackoverflow.com/questions/1347533/how-do-i-detect-circular-logic-or-recursion-in-a-multi-levels-references-and-dep 8 | // 1 I check if at least one entry in the map is an empty array. If not the structure contains circ references because 9 | // 2 there are no nodes without dependencies 10 | // 3 If I found one empty array, the corresponding node (the key in the map) is an edge node 11 | // 4 I remove its reference from all the other arrays then I remove the entry 12 | // Then I Repeat from line 1 13 | 14 | function removeReference(referenceToRemove, map) { 15 | [...map.entries()].forEach(([, set]) => { 16 | if (set.has(referenceToRemove)) { 17 | set.delete(referenceToRemove); 18 | } 19 | }); 20 | } 21 | 22 | function dependenciesMapHandler(map) { 23 | // all the map analized, return it 24 | if (map.size === 0) return map; 25 | 26 | const keySetPair = [...map.entries()].find(([, set]) => set.size === 0); 27 | 28 | if (!keySetPair) { 29 | // no more empty array found, return the map at the current state 30 | return map; 31 | } 32 | 33 | const [ref] = keySetPair; 34 | 35 | // remove the reference from all the other arrays 36 | removeReference(ref, map); 37 | 38 | // now I can remove the key (ref) 39 | map.delete(ref); 40 | 41 | // recursively analize the rest of the map 42 | return dependenciesMapHandler(map); 43 | } 44 | 45 | function checkCircRefs(map) { 46 | return dependenciesMapHandler(map).size; 47 | } 48 | 49 | module.exports = checkCircRefs; 50 | -------------------------------------------------------------------------------- /src/utility/prevReferencesHelper.js: -------------------------------------------------------------------------------- 1 | module.exports = (references, value) => { 2 | if (references.has(value)) { 3 | // if I've previously found thid object value 4 | // I can return it because I've stored it in the references map 5 | return references.get(value); // is an object, that is always a truthy value 6 | } 7 | return null; 8 | }; 9 | -------------------------------------------------------------------------------- /src/utility/safeReferencesHelper.js: -------------------------------------------------------------------------------- 1 | module.exports = (safeReferences, value) => 2 | safeReferences.has(value) ? safeReferences.get(value) : null; 3 | 4 | // const duplicatedObj = {}; 5 | // const map = { 6 | // key: duplicatedObj 7 | // anotherKey: duplicatedObj 8 | // } 9 | 10 | // if there is a safeReference, it would be an object and also emtpy objects are truthy values 11 | -------------------------------------------------------------------------------- /src/utility/updateCircReferencesIntoMapObjects.js: -------------------------------------------------------------------------------- 1 | module.exports = ( 2 | res, 3 | references, 4 | alreadyVisitedMap, 5 | recursiveInnerPropsUpdate 6 | ) => { 7 | // get the entries array 8 | const entries = [...res.entries()]; 9 | 10 | for (const [key, value] of entries) { 11 | // only if the value is an object 12 | if (value && typeof value === "object") { 13 | // if the references map has a field corresponding to the current value 14 | // it means that the value is an old circ reference 15 | // but now the map has an up to date corresponding value (a new circ ref) 16 | // so we update the prop 17 | if (references.has(value)) { 18 | // is essential here that the value was 19 | // the reference to the old object 20 | res.set(key, references.get(value)); 21 | } else { 22 | // if not, 'res[key]' it is a new copied object that might 23 | // have some old circ references in it 24 | // vut it will be not visited if we have already 25 | // updated it 26 | if (alreadyVisitedMap.has(value)) { 27 | continue; 28 | } 29 | alreadyVisitedMap.set(value); 30 | recursiveInnerPropsUpdate(value, references, alreadyVisitedMap); 31 | } 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/utility/updateCircReferencesIntoOtherObjects.js: -------------------------------------------------------------------------------- 1 | module.exports = ( 2 | res, 3 | references, 4 | alreadyVisitedMap, 5 | recursiveInnerPropsUpdate 6 | ) => { 7 | // we have to update alle the references in the res object (non-enum and symbol prop included) 8 | // if non-enum and symbol prop had to be discarded won't be present at all in the res object, so no checks to do 9 | 10 | // we discard non object, function and setters&getters 11 | 12 | const descriptors = Object.getOwnPropertyDescriptors(res); 13 | 14 | Object.entries(descriptors).forEach(([key, descriptor]) => { 15 | // descriptor.value == value of each prop == potential reference to a node 16 | 17 | if (descriptor.set || descriptor.get) { 18 | // don't care about getters and setters 19 | // them are functions that will be copied by reference 20 | return; 21 | } 22 | 23 | const { value } = descriptor; 24 | 25 | // discard functions and non object 26 | if (value && typeof value === "object") { 27 | // if the references map has a field corresponding to the current value 28 | // it means that the value is an old circ reference 29 | // but now the map has an up to date corresponding value (a new circ ref) 30 | // so we update the prop 31 | if (references.has(value)) { 32 | // is essential here that the value was 33 | // the reference to the old object 34 | res[key] = references.get(value); 35 | } else { 36 | // if not, res[key] it is a new copied object that might 37 | // have some old circ references in it 38 | // vut it will be not visited if we have already 39 | // updated it 40 | if (alreadyVisitedMap.has(value)) { 41 | return; 42 | } 43 | alreadyVisitedMap.set(value); 44 | recursiveInnerPropsUpdate(value, references, alreadyVisitedMap); 45 | } 46 | } 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /src/utility/updateCircReferencesIntoSetObjects.js: -------------------------------------------------------------------------------- 1 | module.exports = ( 2 | res, 3 | references, 4 | alreadyVisitedMap, 5 | recursiveInnerPropsUpdate 6 | ) => { 7 | // get the values array 8 | const entries = [...res.values()]; 9 | 10 | for (const value of entries) { 11 | // only if the value is an object 12 | if (value && typeof value === "object") { 13 | // if the references map has a field corresponding to the current value 14 | // it means that the value is an old circ reference 15 | // but now the map has an up to date corresponding value (a new circ ref) 16 | // so we update the prop 17 | if (references.has(value)) { 18 | // is essential here that the value was 19 | // the reference to the old object 20 | 21 | // remove the previous stored entries (is theonly way with set objects) 22 | res.delete(value); 23 | // add the new one 24 | res.add(references.get(value)); 25 | } else { 26 | // if not, 'res[value]' it is a new copied object that might 27 | // have some old circ references in it 28 | // vut it will be not visited if we have already 29 | // updated it 30 | if (alreadyVisitedMap.has(value)) { 31 | continue; 32 | } 33 | alreadyVisitedMap.set(value); 34 | recursiveInnerPropsUpdate(value, references, alreadyVisitedMap); 35 | } 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/omniclone.js", 5 | output: { 6 | filename: "main.js", 7 | library: "omnicode", 8 | // libraryExport: 'default', for es6 export 9 | libraryTarget: "commonjs2", 10 | path: path.resolve(__dirname, "dist") 11 | } 12 | }; 13 | --------------------------------------------------------------------------------