-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathdeepFreeze.ts
102 lines (99 loc) · 2.81 KB
/
deepFreeze.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { DraftType } from '../interface';
import { getType, isDraft } from './draft';
function throwFrozenError() {
throw new Error('Cannot modify frozen object');
}
function isFreezable(value: any) {
return (
__DEV__ || (value && typeof value === 'object' && !Object.isFrozen(value))
);
}
export function deepFreeze(
target: any,
subKey?: any,
updatedValues?: WeakMap<any, any>,
stack?: any[],
keys?: any[]
) {
if (__DEV__) {
updatedValues = updatedValues ?? new WeakMap();
stack = stack ?? [];
keys = keys ?? [];
const value = updatedValues.has(target)
? updatedValues.get(target)
: target;
if (stack.length > 0) {
const index = stack.indexOf(value);
if (value && typeof value === 'object' && index !== -1) {
if (stack[0] === value) {
throw new Error(`Forbids circular reference`);
}
throw new Error(
`Forbids circular reference: ~/${keys
.slice(0, index)
.map((key, index) => {
if (typeof key === 'symbol') return `[${key.toString()}]`;
const parent = stack![index];
if (
typeof key === 'object' &&
(parent instanceof Map || parent instanceof Set)
)
return Array.from(parent.keys()).indexOf(key);
return key;
})
.join('/')}`
);
}
stack.push(value);
keys.push(subKey);
} else {
stack.push(value);
}
}
if (Object.isFrozen(target) || isDraft(target)) {
if (__DEV__) {
stack!.pop();
keys!.pop();
}
return;
}
const type = getType(target);
switch (type) {
case DraftType.Map:
for (const [key, value] of target) {
if (isFreezable(key)) deepFreeze(key, key, updatedValues, stack, keys);
if (isFreezable(value))
deepFreeze(value, key, updatedValues, stack, keys);
}
target.set = target.clear = target.delete = throwFrozenError;
break;
case DraftType.Set:
for (const value of target) {
if (isFreezable(value))
deepFreeze(value, value, updatedValues, stack, keys);
}
target.add = target.clear = target.delete = throwFrozenError;
break;
case DraftType.Array:
Object.freeze(target);
let index = 0;
for (const value of target) {
if (isFreezable(value))
deepFreeze(value, index, updatedValues, stack, keys);
index += 1;
}
break;
default:
Object.freeze(target);
// ignore non-enumerable or symbol properties
Object.keys(target).forEach((name) => {
const value = target[name];
if (isFreezable(value))
deepFreeze(value, name, updatedValues, stack, keys);
});
}
if (__DEV__) {
stack!.pop();
keys!.pop();
}
}