Skip to content

Commit

Permalink
fix: applyValue should shallow copy the input
Browse files Browse the repository at this point in the history
  • Loading branch information
pionxzh committed Mar 28, 2023
1 parent 8a8d1cb commit 5c632a4
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 12 deletions.
60 changes: 48 additions & 12 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,64 @@ import type { ComponentType } from 'react'

import type { DataItemProps, EditorProps, Path } from '../type'

export const applyValue = (obj: any, path: (string | number)[], value: any) => {
if (typeof obj !== 'object' || obj === null) {
// reference: https://github.com/immerjs/immer/blob/main/src/utils/common.ts
const objectCtorString = Object.prototype.constructor.toString()
function isPlainObject (value: any): boolean {
if (!value || typeof value !== 'object') return false

const proto = Object.getPrototypeOf(value)
if (proto === null) return true

const Ctor = Object.hasOwnProperty.call(proto, 'constructor') && proto.constructor
if (Ctor === Object) return true

return typeof Ctor === 'function' && Function.toString.call(Ctor) === objectCtorString
}

function shouldShallowCopy (value: any) {
if (!value) return false

return (
isPlainObject(value) ||
Array.isArray(value) ||
value instanceof Map ||
value instanceof Set
)
}

function shallowCopy (value: any) {
if (Array.isArray(value)) return Array.prototype.slice.call(value)
if (value instanceof Set) return new Set(value)
if (value instanceof Map) return new Map(value)
if (typeof value === 'object' && value !== null) {
return Object.assign({}, value)
}
return value
}

export function applyValue (input: any, path: (string | number)[], value: any) {
if (typeof input !== 'object' || input === null) {
if (path.length !== 0) {
throw new Error('path is incorrect')
}
return value
}
const arr: (string | number)[] = [...path]
let key
if (path.length > 0) {
key = arr[0]

const shouldCopy = shouldShallowCopy(input)
if (shouldCopy) input = shallowCopy(input)

const [key, ...restPath] = path
if (key !== undefined) {
if (key === '__proto__') {
throw new TypeError('don\'t modify __proto__!!!')
throw new TypeError('Modification of prototype is not allowed')
}
if (arr.length > 1) {
arr.shift()
obj[key] = applyValue(obj[key], arr, value)
if (restPath.length > 0) {
input[key] = applyValue(input[key], restPath, value)
} else {
obj[key] = value
input[key] = value
}
}
return obj
return input
}

// case 1: you only render with a single component
Expand Down
30 changes: 30 additions & 0 deletions tests/util.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,36 @@ describe('function applyValue', () => {
foo: 2
})
})

test('object nested', () => {
const original = {
foo: {
bar: {
baz: 1
}
}
}
const newValue = applyValue(original, ['foo', 'bar', 'baz'], 2)
expect(newValue).is.deep.eq({
foo: {
bar: {
baz: 2
}
}
})
})

test('array', () => {
const original = [1, 2, 3]
const newValue = applyValue(original, [1], 4)
expect(newValue).is.deep.eq([1, 4, 3])
})

test('array nested', () => {
const original = [1, [2, [3, 4]]]
const newValue = applyValue(original, [1, 1, 1], 5)
expect(newValue).is.deep.eq([1, [2, [3, 5]]])
})
})

describe('function isCycleReference', () => {
Expand Down

0 comments on commit 5c632a4

Please sign in to comment.