Skip to content

Commit

Permalink
fix: #502
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Jan 10, 2020
1 parent b2f23c4 commit 8d4f723
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 133 deletions.
14 changes: 14 additions & 0 deletions __tests__/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,13 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
expect(entries[0][0]).toBe(key)
expect(entries[0][0].a).toBe(2)
})

it("does support instanceof Map", () => {
const map = new Map()
produce(map, d => {
expect(d instanceof Map).toBeTruthy()
})
})
})

describe("set drafts", () => {
Expand Down Expand Up @@ -849,6 +856,13 @@ function runBaseTest(name, useProxies, autoFreeze, useListener) {
"Cannot use a proxy that has been revoked"
)
})

it("does support instanceof Set", () => {
const set = new Set()
produce(set, d => {
expect(d instanceof Set).toBeTruthy()
})
})
})

it("supports `immerable` symbol on constructor", () => {
Expand Down
1 change: 1 addition & 0 deletions __tests__/empty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe("map set - es5", () => {
setUseProxies(false)

const baseState = new Map([["x", 1]])
debugger
const nextState = produce(baseState, s => {
s.set("x", 2)
})
Expand Down
44 changes: 2 additions & 42 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {DRAFT_STATE, DRAFTABLE, hasSet} from "./env"
import {
Objectish,
Drafted,
Expand All @@ -9,44 +10,7 @@ import {
ProxyType,
Archtype
} from "./types"

/** Use a class type for `nothing` so its type is unique */
export class Nothing {
// This lets us do `Exclude<T, Nothing>`
// @ts-ignore
private _!: unique symbol
}

const hasSymbol = typeof Symbol !== "undefined"
export const hasMap = typeof Map !== "undefined"
export const hasSet = typeof Set !== "undefined"

/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
export const NOTHING: Nothing = hasSymbol
? Symbol("immer-nothing")
: ({["immer-nothing"]: true} as any)

/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
export const DRAFTABLE: unique symbol = hasSymbol
? Symbol("immer-draftable")
: ("__$immer_draftable" as any)

export const DRAFT_STATE: unique symbol = hasSymbol
? Symbol("immer-state")
: ("__$immer_state" as any)

export const iteratorSymbol: typeof Symbol.iterator = hasSymbol
? Symbol.iterator
: ("@@iterator" as any)
import {isMap} from "./map"

/** Returns true if the given value is an Immer draft */
export function isDraft(value: any): boolean {
Expand Down Expand Up @@ -167,10 +131,6 @@ export function is(x: any, y: any): boolean {
}
}

export function isMap(target: any): target is AnyMap {
return hasMap && target instanceof Map
}

export function isSet(target: any): target is AnySet {
return hasSet && target instanceof Set
}
Expand Down
2 changes: 1 addition & 1 deletion src/es5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
isDraftable,
isEnumerable,
shallowCopy,
DRAFT_STATE,
latest,
createHiddenProperty
} from "./common"
Expand All @@ -24,6 +23,7 @@ import {
} from "./types"
import {MapState} from "./map"
import {SetState} from "./set"
import {DRAFT_STATE} from "./env"

interface ES5BaseState extends ImmerBaseState {
finalizing: boolean
Expand Down
14 changes: 2 additions & 12 deletions src/finalize.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import {Immer} from "./immer"
import {ImmerState, Drafted, ProxyType} from "./types"
import {ImmerScope} from "./scope"
import {
isSet,
has,
is,
get,
each,
DRAFT_STATE,
NOTHING,
freeze,
shallowCopy,
set
} from "./common"
import {isSet, has, is, get, each, freeze, shallowCopy, set} from "./common"
import {isDraft, isDraftable} from "./index"
import {SetState} from "./set"
import {generatePatches, PatchPath} from "./patches"
import {DRAFT_STATE, NOTHING} from "./env"

export function processResult(immer: Immer, result: any, scope: ImmerScope) {
const baseDraft = scope.drafts![0]
Expand Down
14 changes: 3 additions & 11 deletions src/immer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@ import {createES5Proxy, willFinalizeES5, markChangedES5} from "./es5"
import {createProxy, markChanged} from "./proxy"

import {applyPatches} from "./patches"
import {
each,
isDraft,
isSet,
isMap,
isDraftable,
DRAFT_STATE,
NOTHING,
die
} from "./common"
import {each, isDraft, isSet, isDraftable, die} from "./common"
import {ImmerScope} from "./scope"
import {
ImmerState,
Expand All @@ -23,9 +14,10 @@ import {
Patch,
Drafted
} from "./types"
import {proxyMap} from "./map"
import {proxyMap, isMap} from "./map"
import {proxySet} from "./set"
import {processResult, maybeFreeze} from "./finalize"
import {NOTHING, DRAFT_STATE} from "./env"

/* istanbul ignore next */
function verifyMinified() {}
Expand Down
79 changes: 50 additions & 29 deletions src/map.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {isDraftable, DRAFT_STATE, latest, iteratorSymbol} from "./common"
import {__extends} from "./extends"
import {isDraftable, latest} from "./common"

import {ImmerScope} from "./scope"
import {AnyMap, Drafted, ImmerState, ImmerBaseState, ProxyType} from "./types"
import {assertUnrevoked} from "./es5"
import {DRAFT_STATE, iteratorSymbol, hasMap} from "./env"

export interface MapState extends ImmerBaseState {
type: ProxyType.Map
Expand All @@ -13,15 +15,14 @@ export interface MapState extends ImmerBaseState {
draft: Drafted<AnyMap, MapState>
}

// Make sure DraftMap declarion doesn't die if Map is not avialable...
/* istanbul ignore next */
const MapBase: MapConstructor =
typeof Map !== "undefined" ? Map : (function FakeMap() {} as any)

export class DraftMap<K, V> extends MapBase implements Map<K, V> {
[DRAFT_STATE]: MapState
constructor(target: AnyMap, parent?: ImmerState) {
super()
const DraftMap = (function(_super) {
if (!_super) {
/* istanbul ignore next */
throw new Error("Map is not polyfilled")
}
__extends(DraftMap, _super)
// Create class manually, cause #502
function DraftMap(this: any, target: AnyMap, parent?: ImmerState): any {
this[DRAFT_STATE] = {
type: ProxyType.Map,
parent,
Expand All @@ -31,21 +32,28 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
copy: undefined,
assigned: undefined,
base: target,
draft: this,
draft: this as any,
isManual: false,
revoked: false
}
return this
}

get size(): number {
return latest(this[DRAFT_STATE]).size
}

has(key: K): boolean {
const p = DraftMap.prototype

// TODO: smaller build size if we create a util for Object.defineProperty
Object.defineProperty(p, "size", {
get: function() {
return latest(this[DRAFT_STATE]).size
},
enumerable: true,
configurable: true
})

p.has = function(key: any): boolean {
return latest(this[DRAFT_STATE]).has(key)
}

set(key: K, value: V): this {
p.set = function(key: any, value: any) {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
if (latest(state).get(key) !== value) {
Expand All @@ -58,7 +66,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return this
}

delete(key: K): boolean {
p.delete = function(key: any): boolean {
if (!this.has(key)) {
return false
}
Expand All @@ -72,7 +80,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return true
}

clear() {
p.clear = function() {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
prepareCopy(state)
Expand All @@ -84,14 +92,17 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return state.copy!.clear()
}

forEach(cb: (value: V, key: K, self: this) => void, thisArg?: any) {
p.forEach = function(
cb: (value: any, key: any, self: any) => void,
thisArg?: any
) {
const state = this[DRAFT_STATE]
latest(state).forEach((_value: V, key: K, _map: this) => {
latest(state).forEach((_value: any, key: any, _map: any) => {
cb.call(thisArg, this.get(key), key, this)
})
}

get(key: K): V {
p.get = function(key: any): any {
const state = this[DRAFT_STATE]
assertUnrevoked(state)
const value = latest(state).get(key)
Expand All @@ -108,11 +119,11 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
return draft
}

keys(): IterableIterator<K> {
p.keys = function(): IterableIterator<any> {
return latest(this[DRAFT_STATE]).keys()
}

values(): IterableIterator<V> {
p.values = function(): IterableIterator<any> {
const iterator = this.keys()
return {
[iteratorSymbol]: () => this.values(),
Expand All @@ -129,7 +140,7 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
} as any
}

entries(): IterableIterator<[K, V]> {
p.entries = function(): IterableIterator<[any, any]> {
const iterator = this.keys()
return {
[iteratorSymbol]: () => this.entries(),
Expand All @@ -146,12 +157,18 @@ export class DraftMap<K, V> extends MapBase implements Map<K, V> {
} as any
}

[iteratorSymbol]() {
p[iteratorSymbol] = function() {
return this.entries()
}
}

export function proxyMap(target: AnyMap, parent?: ImmerState) {
return DraftMap
})(Map)

export function proxyMap<T extends AnyMap>(
target: T,
parent?: ImmerState
): T & {[DRAFT_STATE]: MapState} {
// @ts-ignore
return new DraftMap(target, parent)
}

Expand All @@ -161,3 +178,7 @@ function prepareCopy(state: MapState) {
state.copy = new Map(state.base)
}
}

export function isMap(target: any): target is AnyMap {
return hasMap && (target instanceof Map || target instanceof DraftMap) // TODO: fix
}
4 changes: 2 additions & 2 deletions src/patches.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {get, each, isMap, has, die, getArchtype} from "./common"
import {get, each, has, die, getArchtype} from "./common"
import {Patch, ImmerState, ProxyType, Archtype} from "./types"
import {SetState} from "./set"
import {ES5ArrayState, ES5ObjectState} from "./es5"
import {ProxyArrayState, ProxyObjectState} from "./proxy"
import {MapState} from "./map"
import {MapState, isMap} from "./map"

export type PatchPath = (string | number)[]

Expand Down
11 changes: 2 additions & 9 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
"use strict"
import {
each,
has,
is,
isDraftable,
shallowCopy,
DRAFT_STATE,
latest
} from "./common"
import {each, has, is, isDraftable, shallowCopy, latest} from "./common"
import {ImmerScope} from "./scope"
import {
AnyObject,
Expand All @@ -18,6 +10,7 @@ import {
ImmerBaseState,
ProxyType
} from "./types"
import {DRAFT_STATE} from "./env"

interface ProxyBaseState extends ImmerBaseState {
assigned: {
Expand Down
2 changes: 1 addition & 1 deletion src/scope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {DRAFT_STATE} from "./common"
import {Patch, PatchListener, Drafted, ProxyType} from "./types"
import {Immer} from "./immer"
import {DRAFT_STATE} from "./env"

/** Each scope represents a `produce` call. */
export class ImmerScope {
Expand Down
Loading

0 comments on commit 8d4f723

Please sign in to comment.