Skip to content

Commit

Permalink
feat: implement useStrictShallowCopy
Browse files Browse the repository at this point in the history
  • Loading branch information
hrsh7th committed May 18, 2022
1 parent d30d219 commit d693e8f
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 9 deletions.
39 changes: 39 additions & 0 deletions __tests__/not-strict-copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import produce, {enableAllPlugins, setUseStrictShallowCopy} from "../src/immer"

enableAllPlugins()

describe("setUseStrictShallowCopy(true)", () => {
test("keep descriptors", () => {
setUseStrictShallowCopy(true)

const base: Record<string, unknown> = {}
Object.defineProperty(base, "foo", {
value: "foo",
writable: false,
configurable: false
})
const copy = produce(base, (draft: any) => {
draft.bar = "bar"
})
expect(Object.getOwnPropertyDescriptor(copy, "foo")).toStrictEqual(
Object.getOwnPropertyDescriptor(base, "foo")
)
})
})

describe("setUseStrictShallowCopy(false)", () => {
test("ignore descriptors", () => {
setUseStrictShallowCopy(false)

const base: Record<string, unknown> = {}
Object.defineProperty(base, "foo", {
value: "foo",
writable: false,
configurable: false
})
const copy = produce(base, (draft: any) => {
draft.bar = "bar"
})
expect(Object.getOwnPropertyDescriptor(copy, "foo")).toBeUndefined()
})
})
12 changes: 8 additions & 4 deletions src/core/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ function currentImpl(value: any): any {
return state.base_
// Optimization: avoid generating new drafts during copying
state.finalized_ = true
copy = copyHelper(value, archType)
copy = copyHelper(
value,
archType,
state.scope_.immer_.useStrictShallowCopy_
)
state.finalized_ = false
} else {
copy = copyHelper(value, archType)
copy = copyHelper(value, archType, true)
}

each(copy, (key, childValue) => {
Expand All @@ -47,7 +51,7 @@ function currentImpl(value: any): any {
return archType === Archtype.Set ? new Set(copy) : copy
}

function copyHelper(value: any, archType: number): any {
function copyHelper(value: any, archType: number, strict: boolean): any {
// creates a shallow copy, even if it is a map or set
switch (archType) {
case Archtype.Map:
Expand All @@ -56,5 +60,5 @@ function copyHelper(value: any, archType: number): any {
// Set will be cloned as array temporarily, so that we can replace individual items
return Array.from(value)
}
return shallowCopy(value)
return shallowCopy(value, strict)
}
5 changes: 4 additions & 1 deletion src/core/finalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
const result =
// For ES5, create a good copy from the draft first, with added keys and without deleted keys.
state.type_ === ProxyType.ES5Object || state.type_ === ProxyType.ES5Array
? (state.copy_ = shallowCopy(state.draft_))
? (state.copy_ = shallowCopy(
state.draft_,
rootScope.immer_.useStrictShallowCopy_
))
: state.copy_
// Finalize all children of the copy
// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
Expand Down
19 changes: 18 additions & 1 deletion src/core/immerClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ export class Immer implements ProducersFns {

autoFreeze_: boolean = true

constructor(config?: {useProxies?: boolean; autoFreeze?: boolean}) {
useStrictShallowCopy_: boolean = true

constructor(config?: {
useProxies?: boolean
autoFreeze?: boolean
useStrictShallowCopy?: boolean
}) {
if (typeof config?.useProxies === "boolean")
this.setUseProxies(config!.useProxies)
if (typeof config?.autoFreeze === "boolean")
this.setAutoFreeze(config!.autoFreeze)
if (typeof config?.useStrictShallowCopy === "boolean")
this.setUseStrictShallowCopy(config!.useStrictShallowCopy)
}

/**
Expand Down Expand Up @@ -195,6 +203,15 @@ export class Immer implements ProducersFns {
this.useProxies_ = value
}

/**
* Pass false to disable strict shallow copy.
*
* By default, immer copies the object descriptors on creating new object.
*/
setUseStrictShallowCopy(value: boolean) {
this.useStrictShallowCopy_ = value
}

applyPatches<T extends Objectish>(base: T, patches: Patch[]): T {
// If a patch replaces the entire state, take that replacement as base
// before applying patches
Expand Down
12 changes: 10 additions & 2 deletions src/core/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
createProxy,
ProxyType
} from "../internal"
import {ImmerScope} from "./scope"

interface ProxyBaseState extends ImmerBaseState {
assigned_: {
Expand Down Expand Up @@ -273,8 +274,15 @@ export function markChanged(state: ImmerState) {
}
}

export function prepareCopy(state: {base_: any; copy_: any}) {
export function prepareCopy(state: {
base_: any
copy_: any
scope_: ImmerScope
}) {
if (!state.copy_) {
state.copy_ = shallowCopy(state.base_)
state.copy_ = shallowCopy(
state.base_,
state.scope_.immer_.useStrictShallowCopy_
)
}
}
7 changes: 7 additions & 0 deletions src/immer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
*/
export const setUseProxies = immer.setUseProxies.bind(immer)

/**
* Pass false to disable strict shallow copy.
*
* By default, immer copies the object descriptors on creating new object.
*/
export const setUseStrictShallowCopy = immer.setUseStrictShallowCopy.bind(immer)

/**
* Apply an array of Immer patches to the first argument.
*
Expand Down
7 changes: 7 additions & 0 deletions src/types/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ declare export function setAutoFreeze(autoFreeze: boolean): void
*/
declare export function setUseProxies(useProxies: boolean): void

/**
* Pass false to disable strict shallow copy.
*
* By default, immer copies the object descriptors on creating new object.
*/
declare export function setUseStrictShallowCopy(useStrictShallowCopy: boolean): void

declare export function applyPatches<S>(state: S, patches: Patch[]): S

declare export function original<S>(value: S): S
Expand Down
13 changes: 12 additions & 1 deletion src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,19 @@ export function latest(state: ImmerState): any {
}

/*#__PURE__*/
export function shallowCopy(base: any) {
export function shallowCopy(base: any, strict: boolean) {
if (Array.isArray(base)) return Array.prototype.slice.call(base)

if (!strict && isPlainObject(base)) {
const keys = Object.keys(base)
const obj: any = Object.create(Object.getPrototypeOf(base))
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
obj[key] = base[key]
}
return obj
}

const descriptors = getOwnPropertyDescriptors(base)
delete descriptors[DRAFT_STATE as any]
let keys = ownKeys(descriptors)
Expand Down

0 comments on commit d693e8f

Please sign in to comment.