source: frontend/node_modules/immer/src/plugins/patches.ts

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.2 KB
Line 
1import {immerable} from "../immer"
2import {
3 ImmerState,
4 Patch,
5 SetState,
6 ES5ArrayState,
7 ProxyArrayState,
8 MapState,
9 ES5ObjectState,
10 ProxyObjectState,
11 PatchPath,
12 get,
13 each,
14 has,
15 getArchtype,
16 isSet,
17 isMap,
18 loadPlugin,
19 ProxyType,
20 Archtype,
21 die,
22 isDraft,
23 isDraftable,
24 NOTHING
25} from "../internal"
26
27export function enablePatches() {
28 const REPLACE = "replace"
29 const ADD = "add"
30 const REMOVE = "remove"
31
32 function generatePatches_(
33 state: ImmerState,
34 basePath: PatchPath,
35 patches: Patch[],
36 inversePatches: Patch[]
37 ): void {
38 switch (state.type_) {
39 case ProxyType.ProxyObject:
40 case ProxyType.ES5Object:
41 case ProxyType.Map:
42 return generatePatchesFromAssigned(
43 state,
44 basePath,
45 patches,
46 inversePatches
47 )
48 case ProxyType.ES5Array:
49 case ProxyType.ProxyArray:
50 return generateArrayPatches(state, basePath, patches, inversePatches)
51 case ProxyType.Set:
52 return generateSetPatches(
53 (state as any) as SetState,
54 basePath,
55 patches,
56 inversePatches
57 )
58 }
59 }
60
61 function generateArrayPatches(
62 state: ES5ArrayState | ProxyArrayState,
63 basePath: PatchPath,
64 patches: Patch[],
65 inversePatches: Patch[]
66 ) {
67 let {base_, assigned_} = state
68 let copy_ = state.copy_!
69
70 // Reduce complexity by ensuring `base` is never longer.
71 if (copy_.length < base_.length) {
72 // @ts-ignore
73 ;[base_, copy_] = [copy_, base_]
74 ;[patches, inversePatches] = [inversePatches, patches]
75 }
76
77 // Process replaced indices.
78 for (let i = 0; i < base_.length; i++) {
79 if (assigned_[i] && copy_[i] !== base_[i]) {
80 const path = basePath.concat([i])
81 patches.push({
82 op: REPLACE,
83 path,
84 // Need to maybe clone it, as it can in fact be the original value
85 // due to the base/copy inversion at the start of this function
86 value: clonePatchValueIfNeeded(copy_[i])
87 })
88 inversePatches.push({
89 op: REPLACE,
90 path,
91 value: clonePatchValueIfNeeded(base_[i])
92 })
93 }
94 }
95
96 // Process added indices.
97 for (let i = base_.length; i < copy_.length; i++) {
98 const path = basePath.concat([i])
99 patches.push({
100 op: ADD,
101 path,
102 // Need to maybe clone it, as it can in fact be the original value
103 // due to the base/copy inversion at the start of this function
104 value: clonePatchValueIfNeeded(copy_[i])
105 })
106 }
107 if (base_.length < copy_.length) {
108 inversePatches.push({
109 op: REPLACE,
110 path: basePath.concat(["length"]),
111 value: base_.length
112 })
113 }
114 }
115
116 // This is used for both Map objects and normal objects.
117 function generatePatchesFromAssigned(
118 state: MapState | ES5ObjectState | ProxyObjectState,
119 basePath: PatchPath,
120 patches: Patch[],
121 inversePatches: Patch[]
122 ) {
123 const {base_, copy_} = state
124 each(state.assigned_!, (key, assignedValue) => {
125 const origValue = get(base_, key)
126 const value = get(copy_!, key)
127 const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD
128 if (origValue === value && op === REPLACE) return
129 const path = basePath.concat(key as any)
130 patches.push(op === REMOVE ? {op, path} : {op, path, value})
131 inversePatches.push(
132 op === ADD
133 ? {op: REMOVE, path}
134 : op === REMOVE
135 ? {op: ADD, path, value: clonePatchValueIfNeeded(origValue)}
136 : {op: REPLACE, path, value: clonePatchValueIfNeeded(origValue)}
137 )
138 })
139 }
140
141 function generateSetPatches(
142 state: SetState,
143 basePath: PatchPath,
144 patches: Patch[],
145 inversePatches: Patch[]
146 ) {
147 let {base_, copy_} = state
148
149 let i = 0
150 base_.forEach((value: any) => {
151 if (!copy_!.has(value)) {
152 const path = basePath.concat([i])
153 patches.push({
154 op: REMOVE,
155 path,
156 value
157 })
158 inversePatches.unshift({
159 op: ADD,
160 path,
161 value
162 })
163 }
164 i++
165 })
166 i = 0
167 copy_!.forEach((value: any) => {
168 if (!base_.has(value)) {
169 const path = basePath.concat([i])
170 patches.push({
171 op: ADD,
172 path,
173 value
174 })
175 inversePatches.unshift({
176 op: REMOVE,
177 path,
178 value
179 })
180 }
181 i++
182 })
183 }
184
185 function generateReplacementPatches_(
186 baseValue: any,
187 replacement: any,
188 patches: Patch[],
189 inversePatches: Patch[]
190 ): void {
191 patches.push({
192 op: REPLACE,
193 path: [],
194 value: replacement === NOTHING ? undefined : replacement
195 })
196 inversePatches.push({
197 op: REPLACE,
198 path: [],
199 value: baseValue
200 })
201 }
202
203 function applyPatches_<T>(draft: T, patches: Patch[]): T {
204 patches.forEach(patch => {
205 const {path, op} = patch
206
207 let base: any = draft
208 for (let i = 0; i < path.length - 1; i++) {
209 const parentType = getArchtype(base)
210 let p = path[i]
211 if (typeof p !== "string" && typeof p !== "number") {
212 p = "" + p
213 }
214
215 // See #738, avoid prototype pollution
216 if (
217 (parentType === Archtype.Object || parentType === Archtype.Array) &&
218 (p === "__proto__" || p === "constructor")
219 )
220 die(24)
221 if (typeof base === "function" && p === "prototype") die(24)
222 base = get(base, p)
223 if (typeof base !== "object") die(15, path.join("/"))
224 }
225
226 const type = getArchtype(base)
227 const value = deepClonePatchValue(patch.value) // used to clone patch to ensure original patch is not modified, see #411
228 const key = path[path.length - 1]
229 switch (op) {
230 case REPLACE:
231 switch (type) {
232 case Archtype.Map:
233 return base.set(key, value)
234 /* istanbul ignore next */
235 case Archtype.Set:
236 die(16)
237 default:
238 // if value is an object, then it's assigned by reference
239 // in the following add or remove ops, the value field inside the patch will also be modifyed
240 // so we use value from the cloned patch
241 // @ts-ignore
242 return (base[key] = value)
243 }
244 case ADD:
245 switch (type) {
246 case Archtype.Array:
247 return key === "-"
248 ? base.push(value)
249 : base.splice(key as any, 0, value)
250 case Archtype.Map:
251 return base.set(key, value)
252 case Archtype.Set:
253 return base.add(value)
254 default:
255 return (base[key] = value)
256 }
257 case REMOVE:
258 switch (type) {
259 case Archtype.Array:
260 return base.splice(key as any, 1)
261 case Archtype.Map:
262 return base.delete(key)
263 case Archtype.Set:
264 return base.delete(patch.value)
265 default:
266 return delete base[key]
267 }
268 default:
269 die(17, op)
270 }
271 })
272
273 return draft
274 }
275
276 // optimize: this is quite a performance hit, can we detect intelligently when it is needed?
277 // E.g. auto-draft when new objects from outside are assigned and modified?
278 // (See failing test when deepClone just returns obj)
279 function deepClonePatchValue<T>(obj: T): T
280 function deepClonePatchValue(obj: any) {
281 if (!isDraftable(obj)) return obj
282 if (Array.isArray(obj)) return obj.map(deepClonePatchValue)
283 if (isMap(obj))
284 return new Map(
285 Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)])
286 )
287 if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue))
288 const cloned = Object.create(Object.getPrototypeOf(obj))
289 for (const key in obj) cloned[key] = deepClonePatchValue(obj[key])
290 if (has(obj, immerable)) cloned[immerable] = obj[immerable]
291 return cloned
292 }
293
294 function clonePatchValueIfNeeded<T>(obj: T): T {
295 if (isDraft(obj)) {
296 return deepClonePatchValue(obj)
297 } else return obj
298 }
299
300 loadPlugin("Patches", {
301 applyPatches_,
302 generatePatches_,
303 generateReplacementPatches_
304 })
305}
Note: See TracBrowser for help on using the repository browser.