Skip to content

Commit

Permalink
fix: skip props from prototype when cloning (#1287)
Browse files Browse the repository at this point in the history
  • Loading branch information
nieyuyao committed May 11, 2022
1 parent 2bc2620 commit 574d072
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 9 deletions.
32 changes: 24 additions & 8 deletions packages/vitest/src/utils/base.ts
@@ -1,18 +1,25 @@
import type { Arrayable, DeepMerge, Nullable } from '../types'

function isFinalObj(obj: any) {
return obj === Object.prototype || obj === Function.prototype || obj === RegExp.prototype
}

function collectOwnProperties(obj: any, collector: Set<string | symbol>) {
const props = Object.getOwnPropertyNames(obj)
const symbs = Object.getOwnPropertySymbols(obj)

props.forEach(prop => collector.add(prop))
symbs.forEach(symb => collector.add(symb))
}

export function getAllProperties(obj: any) {
const allProps = new Set<string | symbol>()
let curr = obj
do {
// we don't need propterties from these
if (curr === Object.prototype || curr === Function.prototype || curr === RegExp.prototype)
if (isFinalObj(curr))
break
const props = Object.getOwnPropertyNames(curr)
const symbs = Object.getOwnPropertySymbols(curr)

props.forEach(prop => allProps.add(prop))
symbs.forEach(symb => allProps.add(symb))

collectOwnProperties(curr, allProps)
// eslint-disable-next-line no-cond-assign
} while (curr = Object.getPrototypeOf(curr))
return Array.from(allProps)
Expand All @@ -36,6 +43,14 @@ export function getType(value: unknown): string {
return Object.prototype.toString.apply(value).slice(8, -1)
}

function getOwnProperties(obj: any) {
const ownProps = new Set<string | symbol>()
if (isFinalObj(obj))
return []
collectOwnProperties(obj, ownProps)
return Array.from(ownProps)
}

export function clone<T>(val: T): T {
let k: any, out: any, tmp: any

Expand All @@ -49,7 +64,8 @@ export function clone<T>(val: T): T {

if (Object.prototype.toString.call(val) === '[object Object]') {
out = Object.create(Object.getPrototypeOf(val))
const props = getAllProperties(val)
// we don't need properties from prototype
const props = getOwnProperties(val)
for (const k of props) {
// eslint-disable-next-line no-cond-assign
out[k] = (tmp = (val as any)[k]) && typeof tmp === 'object' ? clone(tmp) : tmp
Expand Down
25 changes: 24 additions & 1 deletion test/core/test/utils.spec.ts
@@ -1,5 +1,5 @@
import { describe, expect, test } from 'vitest'
import { assertTypes, deepMerge, toArray } from '../../../packages/vitest/src/utils'
import { assertTypes, clone, deepMerge, toArray } from '../../../packages/vitest/src/utils'
import { deepMergeSnapshot } from '../../../packages/vitest/src/integrations/snapshot/port/utils'

describe('assertTypes', () => {
Expand Down Expand Up @@ -121,3 +121,26 @@ describe('toArray', () => {
expect(toArray({ a: 1, b: 1, expected: 2 })).toEqual([{ a: 1, b: 1, expected: 2 }])
})
})

describe('clone', () => {
test('various types should be cloned correctly', () => {
expect(clone(1)).toBe(1)
expect(clone(true)).toBe(true)
expect(clone(undefined)).toBe(undefined)
expect(clone(null)).toBe(null)
expect(clone({ a: 1 })).toEqual({ a: 1 })
expect(clone([1, 2])).toEqual([1, 2])
const symbolA = Symbol('a')
expect(clone(symbolA)).toBe(symbolA)
const objB: any = {}
Object.defineProperty(objB, 'value', {
configurable: false,
enumerable: false,
value: 1,
writable: false,
})
expect(clone(objB).value).toEqual(objB.value)
const objC = Object.create(objB)
expect(clone(objC).value).toEqual(objC.value)
})
})

0 comments on commit 574d072

Please sign in to comment.