Upstream davidmarkclements/rfdc state of the touched files before 38fddd8c01
This commit is contained in:
commit
078fa09ff4
2 files changed
+504
No files matched your search
@@ -0,0 +1,198 @@
|
||||
'use strict'
|
||||
module.exports = rfdc
|
||||
|
||||
function copyBuffer (cur) {
|
||||
if (cur instanceof Buffer) {
|
||||
return Buffer.from(cur)
|
||||
}
|
||||
|
||||
return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length)
|
||||
}
|
||||
|
||||
function rfdc (opts) {
|
||||
opts = opts || {}
|
||||
if (opts.circles) return rfdcCircles(opts)
|
||||
|
||||
const constructorHandlers = new Map()
|
||||
constructorHandlers.set(Date, (o) => new Date(o))
|
||||
constructorHandlers.set(Map, (o, fn) => new Map(cloneArray(Array.from(o), fn)))
|
||||
constructorHandlers.set(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn)))
|
||||
if (opts.constructorHandlers) {
|
||||
for (const handler of opts.constructorHandlers) {
|
||||
constructorHandlers.set(handler[0], handler[1])
|
||||
}
|
||||
}
|
||||
|
||||
let handler = null
|
||||
|
||||
return opts.proto ? cloneProto : clone
|
||||
|
||||
function cloneArray (a, fn) {
|
||||
const keys = Object.keys(a)
|
||||
const a2 = new Array(keys.length)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const cur = a[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
a2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
a2[k] = handler(cur, fn)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
a2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
a2[k] = fn(cur)
|
||||
}
|
||||
}
|
||||
return a2
|
||||
}
|
||||
|
||||
function clone (o) {
|
||||
if (typeof o !== 'object' || o === null) return o
|
||||
if (Array.isArray(o)) return cloneArray(o, clone)
|
||||
if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) {
|
||||
return handler(o, clone)
|
||||
}
|
||||
const o2 = {}
|
||||
for (const k in o) {
|
||||
if (Object.hasOwnProperty.call(o, k) === false) continue
|
||||
const cur = o[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
o2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
o2[k] = handler(cur, clone)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
o2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
o2[k] = clone(cur)
|
||||
}
|
||||
}
|
||||
return o2
|
||||
}
|
||||
|
||||
function cloneProto (o) {
|
||||
if (typeof o !== 'object' || o === null) return o
|
||||
if (Array.isArray(o)) return cloneArray(o, cloneProto)
|
||||
if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) {
|
||||
return handler(o, cloneProto)
|
||||
}
|
||||
const o2 = {}
|
||||
for (const k in o) {
|
||||
const cur = o[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
o2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
o2[k] = handler(cur, cloneProto)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
o2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
o2[k] = cloneProto(cur)
|
||||
}
|
||||
}
|
||||
return o2
|
||||
}
|
||||
}
|
||||
|
||||
function rfdcCircles (opts) {
|
||||
const refs = []
|
||||
const refsNew = []
|
||||
|
||||
const constructorHandlers = new Map()
|
||||
constructorHandlers.set(Date, (o) => new Date(o))
|
||||
constructorHandlers.set(Map, (o, fn) => new Map(cloneArray(Array.from(o), fn)))
|
||||
constructorHandlers.set(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn)))
|
||||
if (opts.constructorHandlers) {
|
||||
for (const handler of opts.constructorHandlers) {
|
||||
constructorHandlers.set(handler[0], handler[1])
|
||||
}
|
||||
}
|
||||
|
||||
let handler = null
|
||||
return opts.proto ? cloneProto : clone
|
||||
|
||||
function cloneArray (a, fn) {
|
||||
const keys = Object.keys(a)
|
||||
const a2 = new Array(keys.length)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const cur = a[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
a2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
a2[k] = handler(cur, fn)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
a2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
const index = refs.indexOf(cur)
|
||||
if (index !== -1) {
|
||||
a2[k] = refsNew[index]
|
||||
} else {
|
||||
a2[k] = fn(cur)
|
||||
}
|
||||
}
|
||||
}
|
||||
return a2
|
||||
}
|
||||
|
||||
function clone (o) {
|
||||
if (typeof o !== 'object' || o === null) return o
|
||||
if (Array.isArray(o)) return cloneArray(o, clone)
|
||||
if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) {
|
||||
return handler(o, clone)
|
||||
}
|
||||
const o2 = {}
|
||||
refs.push(o)
|
||||
refsNew.push(o2)
|
||||
for (const k in o) {
|
||||
if (Object.hasOwnProperty.call(o, k) === false) continue
|
||||
const cur = o[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
o2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
o2[k] = handler(cur, clone)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
o2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
const i = refs.indexOf(cur)
|
||||
if (i !== -1) {
|
||||
o2[k] = refsNew[i]
|
||||
} else {
|
||||
o2[k] = clone(cur)
|
||||
}
|
||||
}
|
||||
}
|
||||
refs.pop()
|
||||
refsNew.pop()
|
||||
return o2
|
||||
}
|
||||
|
||||
function cloneProto (o) {
|
||||
if (typeof o !== 'object' || o === null) return o
|
||||
if (Array.isArray(o)) return cloneArray(o, cloneProto)
|
||||
if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) {
|
||||
return handler(o, cloneProto)
|
||||
}
|
||||
const o2 = {}
|
||||
refs.push(o)
|
||||
refsNew.push(o2)
|
||||
for (const k in o) {
|
||||
const cur = o[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
o2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
o2[k] = handler(cur, cloneProto)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
o2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
const i = refs.indexOf(cur)
|
||||
if (i !== -1) {
|
||||
o2[k] = refsNew[i]
|
||||
} else {
|
||||
o2[k] = cloneProto(cur)
|
||||
}
|
||||
}
|
||||
}
|
||||
refs.pop()
|
||||
refsNew.pop()
|
||||
return o2
|
||||
}
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const rfdc = require('..')
|
||||
const cloneDefault = require('../default')
|
||||
const clone = rfdc()
|
||||
const cloneProto = rfdc({ proto: true })
|
||||
const cloneCircles = rfdc({ circles: true })
|
||||
const cloneCirclesProto = rfdc({ circles: true, proto: true })
|
||||
|
||||
const rnd = (max) => Math.round(Math.random() * max)
|
||||
|
||||
types(clone, 'default')
|
||||
types(cloneProto, 'proto option')
|
||||
types(cloneCircles, 'circles option')
|
||||
types(cloneCirclesProto, 'circles and proto option')
|
||||
|
||||
test('default – does not copy proto properties', async ({ is }) => {
|
||||
is(clone(Object.create({ a: 1 })).a, undefined, 'value not copied')
|
||||
})
|
||||
test('default – shorthand import', async ({ same }) => {
|
||||
same(
|
||||
clone(Object.create({ a: 1 })),
|
||||
cloneDefault(Object.create({ a: 1 })),
|
||||
'import equals clone with default options'
|
||||
)
|
||||
})
|
||||
test('proto option – copies enumerable proto properties', async ({ is }) => {
|
||||
is(cloneProto(Object.create({ a: 1 })).a, 1, 'value copied')
|
||||
})
|
||||
test('circles option - circular object', async ({ same, is, isNot }) => {
|
||||
const o = { nest: { a: 1, b: 2 } }
|
||||
o.circular = o
|
||||
same(cloneCircles(o), o, 'same values')
|
||||
isNot(cloneCircles(o), o, 'different objects')
|
||||
isNot(cloneCircles(o).nest, o.nest, 'different nested objects')
|
||||
const c = cloneCircles(o)
|
||||
is(c.circular, c, 'circular references point to copied parent')
|
||||
isNot(c.circular, o, 'circular references do not point to original parent')
|
||||
})
|
||||
test('circles option – deep circular object', async ({ same, is, isNot }) => {
|
||||
const o = { nest: { a: 1, b: 2 } }
|
||||
o.nest.circular = o
|
||||
same(cloneCircles(o), o, 'same values')
|
||||
isNot(cloneCircles(o), o, 'different objects')
|
||||
isNot(cloneCircles(o).nest, o.nest, 'different nested objects')
|
||||
const c = cloneCircles(o)
|
||||
is(c.nest.circular, c, 'circular references point to copied parent')
|
||||
isNot(
|
||||
c.nest.circular,
|
||||
o,
|
||||
'circular references do not point to original parent'
|
||||
)
|
||||
})
|
||||
test('circles option alone – does not copy proto properties', async ({
|
||||
is
|
||||
}) => {
|
||||
is(cloneCircles(Object.create({ a: 1 })).a, undefined, 'value not copied')
|
||||
})
|
||||
test('circles and proto option – copies enumerable proto properties', async ({
|
||||
is
|
||||
}) => {
|
||||
is(cloneCirclesProto(Object.create({ a: 1 })).a, 1, 'value copied')
|
||||
})
|
||||
test('circles and proto option - circular object', async ({
|
||||
same,
|
||||
is,
|
||||
isNot
|
||||
}) => {
|
||||
const o = { nest: { a: 1, b: 2 } }
|
||||
o.circular = o
|
||||
same(cloneCirclesProto(o), o, 'same values')
|
||||
isNot(cloneCirclesProto(o), o, 'different objects')
|
||||
isNot(cloneCirclesProto(o).nest, o.nest, 'different nested objects')
|
||||
const c = cloneCirclesProto(o)
|
||||
is(c.circular, c, 'circular references point to copied parent')
|
||||
isNot(c.circular, o, 'circular references do not point to original parent')
|
||||
})
|
||||
test('circles and proto option – deep circular object', async ({
|
||||
same,
|
||||
is,
|
||||
isNot
|
||||
}) => {
|
||||
const o = { nest: { a: 1, b: 2 } }
|
||||
o.nest.circular = o
|
||||
same(cloneCirclesProto(o), o, 'same values')
|
||||
isNot(cloneCirclesProto(o), o, 'different objects')
|
||||
isNot(cloneCirclesProto(o).nest, o.nest, 'different nested objects')
|
||||
const c = cloneCirclesProto(o)
|
||||
is(c.nest.circular, c, 'circular references point to copied parent')
|
||||
isNot(
|
||||
c.nest.circular,
|
||||
o,
|
||||
'circular references do not point to original parent'
|
||||
)
|
||||
})
|
||||
test('circles and proto option – deep circular array', async ({
|
||||
same,
|
||||
is,
|
||||
isNot
|
||||
}) => {
|
||||
const o = { nest: [1, 2] }
|
||||
o.nest.push(o)
|
||||
same(cloneCirclesProto(o), o, 'same values')
|
||||
isNot(cloneCirclesProto(o), o, 'different objects')
|
||||
isNot(cloneCirclesProto(o).nest, o.nest, 'different nested objects')
|
||||
const c = cloneCirclesProto(o)
|
||||
is(c.nest[2], c, 'circular references point to copied parent')
|
||||
isNot(c.nest[2], o, 'circular references do not point to original parent')
|
||||
})
|
||||
test('custom constructor handler', async ({ same, ok, isNot }) => {
|
||||
class Foo {
|
||||
constructor (s) {
|
||||
this.s = s
|
||||
}
|
||||
}
|
||||
const data = { foo: new Foo('foo') }
|
||||
const cloned = rfdc({ constructorHandlers: [[Foo, (o) => new Foo(o.s)]] })(data)
|
||||
ok(cloned.foo instanceof Foo)
|
||||
same(cloned.foo.s, data.foo.s, 'same values')
|
||||
isNot(cloned.foo, data.foo, 'different objects')
|
||||
})
|
||||
test('custom RegExp handler', async ({ same, ok, isNot }) => {
|
||||
const data = { regex: /foo/ }
|
||||
const cloned = rfdc({ constructorHandlers: [[RegExp, (o) => new RegExp(o)]] })(data)
|
||||
isNot(cloned.regex, data.regex, 'different objects')
|
||||
ok(cloned.regex.test('foo'))
|
||||
})
|
||||
|
||||
function types (clone, label) {
|
||||
test(label + ' – number', async ({ is }) => {
|
||||
is(clone(42), 42, 'same value')
|
||||
})
|
||||
test(label + ' – string', async ({ is }) => {
|
||||
is(clone('str'), 'str', 'same value')
|
||||
})
|
||||
test(label + ' – boolean', async ({ is }) => {
|
||||
is(clone(true), true, 'same value')
|
||||
})
|
||||
test(label + ' – function', async ({ is }) => {
|
||||
const fn = () => {}
|
||||
is(clone(fn), fn, 'same function')
|
||||
})
|
||||
test(label + ' – async function', async ({ is }) => {
|
||||
const fn = async () => {}
|
||||
is(clone(fn), fn, 'same function')
|
||||
})
|
||||
test(label + ' – generator function', async ({ is }) => {
|
||||
const fn = function * () {}
|
||||
is(clone(fn), fn, 'same function')
|
||||
})
|
||||
test(label + ' – date', async ({ is, isNot }) => {
|
||||
const date = new Date()
|
||||
is(+clone(date), +date, 'same value')
|
||||
isNot(clone(date), date, 'different object')
|
||||
})
|
||||
test(label + ' – null', async ({ is }) => {
|
||||
is(clone(null), null, 'same value')
|
||||
})
|
||||
test(label + ' – shallow object', async ({ same, isNot }) => {
|
||||
const o = { a: 1, b: 2 }
|
||||
same(clone(o), o, 'same values')
|
||||
isNot(clone(o), o, 'different object')
|
||||
})
|
||||
test(label + ' – shallow array', async ({ same, isNot }) => {
|
||||
const o = [1, 2]
|
||||
same(clone(o), o, 'same values')
|
||||
isNot(clone(o), o, 'different arrays')
|
||||
})
|
||||
test(label + ' – deep object', async ({ same, isNot }) => {
|
||||
const o = { nest: { a: 1, b: 2 } }
|
||||
same(clone(o), o, 'same values')
|
||||
isNot(clone(o), o, 'different objects')
|
||||
isNot(clone(o).nest, o.nest, 'different nested objects')
|
||||
})
|
||||
test(label + ' – deep array', async ({ same, isNot }) => {
|
||||
const o = [{ a: 1, b: 2 }, [3]]
|
||||
same(clone(o), o, 'same values')
|
||||
isNot(clone(o), o, 'different arrays')
|
||||
isNot(clone(o)[0], o[0], 'different array elements')
|
||||
isNot(clone(o)[1], o[1], 'different array elements')
|
||||
})
|
||||
test(label + ' – nested number', async ({ is }) => {
|
||||
is(clone({ a: 1 }).a, 1, 'same value')
|
||||
})
|
||||
test(label + ' – nested string', async ({ is }) => {
|
||||
is(clone({ s: 'str' }).s, 'str', 'same value')
|
||||
})
|
||||
test(label + ' – nested boolean', async ({ is }) => {
|
||||
is(clone({ b: true }).b, true, 'same value')
|
||||
})
|
||||
test(label + ' – nested function', async ({ is }) => {
|
||||
const fn = () => {}
|
||||
is(clone({ fn }).fn, fn, 'same function')
|
||||
})
|
||||
test(label + ' – nested async function', async ({ is }) => {
|
||||
const fn = async () => {}
|
||||
is(clone({ fn }).fn, fn, 'same function')
|
||||
})
|
||||
test(label + ' – nested generator function', async ({ is }) => {
|
||||
const fn = function * () {}
|
||||
is(clone({ fn }).fn, fn, 'same function')
|
||||
})
|
||||
test(label + ' – nested date', async ({ is, isNot }) => {
|
||||
const date = new Date()
|
||||
is(+clone({ d: date }).d, +date, 'same value')
|
||||
isNot(clone({ d: date }).d, date, 'different object')
|
||||
})
|
||||
test(label + ' – nested date in array', async ({ is, isNot }) => {
|
||||
const date = new Date()
|
||||
is(+clone({ d: [date] }).d[0], +date, 'same value')
|
||||
isNot(clone({ d: [date] }).d[0], date, 'different object')
|
||||
is(+cloneCircles({ d: [date] }).d[0], +date, 'same value')
|
||||
isNot(cloneCircles({ d: [date] }).d, date, 'different object')
|
||||
})
|
||||
test(label + ' – nested null', async ({ is }) => {
|
||||
is(clone({ n: null }).n, null, 'same value')
|
||||
})
|
||||
test(label + ' – arguments', async ({ isNot, same }) => {
|
||||
function fn (...args) {
|
||||
same(clone(arguments), args, 'same values')
|
||||
isNot(clone(arguments), arguments, 'different object')
|
||||
}
|
||||
fn(1, 2, 3)
|
||||
})
|
||||
test(`${label} copies buffers from object correctly`, async ({ ok, is, isNot }) => {
|
||||
const input = Date.now().toString(36)
|
||||
const inputBuffer = Buffer.from(input)
|
||||
const clonedBuffer = clone({ a: inputBuffer }).a
|
||||
ok(Buffer.isBuffer(clonedBuffer), 'cloned value is buffer')
|
||||
isNot(clonedBuffer, inputBuffer, 'cloned buffer is not same as input buffer')
|
||||
is(clonedBuffer.toString(), input, 'cloned buffer content is correct')
|
||||
})
|
||||
test(`${label} copies buffers from arrays correctly`, async ({ ok, is, isNot }) => {
|
||||
const input = Date.now().toString(36)
|
||||
const inputBuffer = Buffer.from(input)
|
||||
const [clonedBuffer] = clone([inputBuffer])
|
||||
ok(Buffer.isBuffer(clonedBuffer), 'cloned value is buffer')
|
||||
isNot(clonedBuffer, inputBuffer, 'cloned buffer is not same as input buffer')
|
||||
is(clonedBuffer.toString(), input, 'cloned buffer content is correct')
|
||||
})
|
||||
test(`${label} copies TypedArrays from object correctly`, async ({ ok, is, isNot }) => {
|
||||
const [input1, input2] = [rnd(10), rnd(10)]
|
||||
const buffer = new ArrayBuffer(8)
|
||||
const int32View = new Int32Array(buffer)
|
||||
int32View[0] = input1
|
||||
int32View[1] = input2
|
||||
const cloned = clone({ a: int32View }).a
|
||||
ok(cloned instanceof Int32Array, 'cloned value is instance of class')
|
||||
isNot(cloned, int32View, 'cloned value is not same as input value')
|
||||
is(cloned[0], input1, 'cloned value content is correct')
|
||||
is(cloned[1], input2, 'cloned value content is correct')
|
||||
})
|
||||
test(`${label} copies TypedArrays from array correctly`, async ({ ok, is, isNot }) => {
|
||||
const [input1, input2] = [rnd(10), rnd(10)]
|
||||
const buffer = new ArrayBuffer(16)
|
||||
const int32View = new Int32Array(buffer)
|
||||
int32View[0] = input1
|
||||
int32View[1] = input2
|
||||
const [cloned] = clone([int32View])
|
||||
ok(cloned instanceof Int32Array, 'cloned value is instance of class')
|
||||
isNot(cloned, int32View, 'cloned value is not same as input value')
|
||||
is(cloned[0], input1, 'cloned value content is correct')
|
||||
is(cloned[1], input2, 'cloned value content is correct')
|
||||
})
|
||||
test(`${label} copies complex TypedArrays`, async ({ ok, deepEqual, is, isNot }) => {
|
||||
const [input1, input2, input3] = [rnd(10), rnd(10), rnd(10)]
|
||||
const buffer = new ArrayBuffer(4)
|
||||
const view1 = new Int8Array(buffer, 0, 2)
|
||||
const view2 = new Int8Array(buffer, 2, 2)
|
||||
const view3 = new Int8Array(buffer)
|
||||
view1[0] = input1
|
||||
view2[0] = input2
|
||||
view3[3] = input3
|
||||
const cloned = clone({ view1, view2, view3 })
|
||||
ok(cloned.view1 instanceof Int8Array, 'cloned value is instance of class')
|
||||
ok(cloned.view2 instanceof Int8Array, 'cloned value is instance of class')
|
||||
ok(cloned.view3 instanceof Int8Array, 'cloned value is instance of class')
|
||||
isNot(cloned.view1, view1, 'cloned value is not same as input value')
|
||||
isNot(cloned.view2, view2, 'cloned value is not same as input value')
|
||||
isNot(cloned.view3, view3, 'cloned value is not same as input value')
|
||||
deepEqual(Array.from(cloned.view1), [input1, 0], 'cloned value content is correct')
|
||||
deepEqual(Array.from(cloned.view2), [input2, input3], 'cloned value content is correct')
|
||||
deepEqual(Array.from(cloned.view3), [input1, 0, input2, input3], 'cloned value content is correct')
|
||||
})
|
||||
test(`${label} - maps`, async ({ same, isNot }) => {
|
||||
const map = new Map([['a', 1]])
|
||||
same(Array.from(clone(map)), [['a', 1]], 'same value')
|
||||
isNot(clone(map), map, 'different object')
|
||||
})
|
||||
test(`${label} - sets`, async ({ same, isNot }) => {
|
||||
const set = new Set([1])
|
||||
same(Array.from(clone(set)), [1])
|
||||
isNot(clone(set), set, 'different object')
|
||||
})
|
||||
test(`${label} - nested maps`, async ({ same, isNot }) => {
|
||||
const data = { m: new Map([['a', 1]]) }
|
||||
same(Array.from(clone(data).m), [['a', 1]], 'same value')
|
||||
isNot(clone(data).m, data.m, 'different object')
|
||||
})
|
||||
test(`${label} - nested sets`, async ({ same, isNot }) => {
|
||||
const data = { s: new Set([1]) }
|
||||
same(Array.from(clone(data).s), [1], 'same value')
|
||||
isNot(clone(data).s, data.s, 'different object')
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user