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

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

Fix frontend appearance

  • Property mode set to 100644
File size: 8.3 KB
Line 
1import {
2 ImmerState,
3 Drafted,
4 ES5ArrayState,
5 ES5ObjectState,
6 each,
7 has,
8 isDraft,
9 latest,
10 DRAFT_STATE,
11 is,
12 loadPlugin,
13 ImmerScope,
14 ProxyType,
15 getCurrentScope,
16 die,
17 markChanged,
18 objectTraps,
19 ownKeys,
20 getOwnPropertyDescriptors
21} from "../internal"
22
23type ES5State = ES5ArrayState | ES5ObjectState
24
25export function enableES5() {
26 function willFinalizeES5_(
27 scope: ImmerScope,
28 result: any,
29 isReplaced: boolean
30 ) {
31 if (!isReplaced) {
32 if (scope.patches_) {
33 markChangesRecursively(scope.drafts_![0])
34 }
35 // This is faster when we don't care about which attributes changed.
36 markChangesSweep(scope.drafts_)
37 }
38 // When a child draft is returned, look for changes.
39 else if (
40 isDraft(result) &&
41 (result[DRAFT_STATE] as ES5State).scope_ === scope
42 ) {
43 markChangesSweep(scope.drafts_)
44 }
45 }
46
47 function createES5Draft(isArray: boolean, base: any) {
48 if (isArray) {
49 const draft = new Array(base.length)
50 for (let i = 0; i < base.length; i++)
51 Object.defineProperty(draft, "" + i, proxyProperty(i, true))
52 return draft
53 } else {
54 const descriptors = getOwnPropertyDescriptors(base)
55 delete descriptors[DRAFT_STATE as any]
56 const keys = ownKeys(descriptors)
57 for (let i = 0; i < keys.length; i++) {
58 const key: any = keys[i]
59 descriptors[key] = proxyProperty(
60 key,
61 isArray || !!descriptors[key].enumerable
62 )
63 }
64 return Object.create(Object.getPrototypeOf(base), descriptors)
65 }
66 }
67
68 function createES5Proxy_<T>(
69 base: T,
70 parent?: ImmerState
71 ): Drafted<T, ES5ObjectState | ES5ArrayState> {
72 const isArray = Array.isArray(base)
73 const draft = createES5Draft(isArray, base)
74
75 const state: ES5ObjectState | ES5ArrayState = {
76 type_: isArray ? ProxyType.ES5Array : (ProxyType.ES5Object as any),
77 scope_: parent ? parent.scope_ : getCurrentScope(),
78 modified_: false,
79 finalized_: false,
80 assigned_: {},
81 parent_: parent,
82 // base is the object we are drafting
83 base_: base,
84 // draft is the draft object itself, that traps all reads and reads from either the base (if unmodified) or copy (if modified)
85 draft_: draft,
86 copy_: null,
87 revoked_: false,
88 isManual_: false
89 }
90
91 Object.defineProperty(draft, DRAFT_STATE, {
92 value: state,
93 // enumerable: false <- the default
94 writable: true
95 })
96 return draft
97 }
98
99 // property descriptors are recycled to make sure we don't create a get and set closure per property,
100 // but share them all instead
101 const descriptors: {[prop: string]: PropertyDescriptor} = {}
102
103 function proxyProperty(
104 prop: string | number,
105 enumerable: boolean
106 ): PropertyDescriptor {
107 let desc = descriptors[prop]
108 if (desc) {
109 desc.enumerable = enumerable
110 } else {
111 descriptors[prop] = desc = {
112 configurable: true,
113 enumerable,
114 get(this: any) {
115 const state = this[DRAFT_STATE]
116 if (__DEV__) assertUnrevoked(state)
117 // @ts-ignore
118 return objectTraps.get(state, prop)
119 },
120 set(this: any, value) {
121 const state = this[DRAFT_STATE]
122 if (__DEV__) assertUnrevoked(state)
123 // @ts-ignore
124 objectTraps.set(state, prop, value)
125 }
126 }
127 }
128 return desc
129 }
130
131 // This looks expensive, but only proxies are visited, and only objects without known changes are scanned.
132 function markChangesSweep(drafts: Drafted<any, ImmerState>[]) {
133 // The natural order of drafts in the `scope` array is based on when they
134 // were accessed. By processing drafts in reverse natural order, we have a
135 // better chance of processing leaf nodes first. When a leaf node is known to
136 // have changed, we can avoid any traversal of its ancestor nodes.
137 for (let i = drafts.length - 1; i >= 0; i--) {
138 const state: ES5State = drafts[i][DRAFT_STATE]
139 if (!state.modified_) {
140 switch (state.type_) {
141 case ProxyType.ES5Array:
142 if (hasArrayChanges(state)) markChanged(state)
143 break
144 case ProxyType.ES5Object:
145 if (hasObjectChanges(state)) markChanged(state)
146 break
147 }
148 }
149 }
150 }
151
152 function markChangesRecursively(object: any) {
153 if (!object || typeof object !== "object") return
154 const state: ES5State | undefined = object[DRAFT_STATE]
155 if (!state) return
156 const {base_, draft_, assigned_, type_} = state
157 if (type_ === ProxyType.ES5Object) {
158 // Look for added keys.
159 // probably there is a faster way to detect changes, as sweep + recurse seems to do some
160 // unnecessary work.
161 // also: probably we can store the information we detect here, to speed up tree finalization!
162 each(draft_, key => {
163 if ((key as any) === DRAFT_STATE) return
164 // The `undefined` check is a fast path for pre-existing keys.
165 if ((base_ as any)[key] === undefined && !has(base_, key)) {
166 assigned_[key] = true
167 markChanged(state)
168 } else if (!assigned_[key]) {
169 // Only untouched properties trigger recursion.
170 markChangesRecursively(draft_[key])
171 }
172 })
173 // Look for removed keys.
174 each(base_, key => {
175 // The `undefined` check is a fast path for pre-existing keys.
176 if (draft_[key] === undefined && !has(draft_, key)) {
177 assigned_[key] = false
178 markChanged(state)
179 }
180 })
181 } else if (type_ === ProxyType.ES5Array) {
182 if (hasArrayChanges(state as ES5ArrayState)) {
183 markChanged(state)
184 assigned_.length = true
185 }
186
187 if (draft_.length < base_.length) {
188 for (let i = draft_.length; i < base_.length; i++) assigned_[i] = false
189 } else {
190 for (let i = base_.length; i < draft_.length; i++) assigned_[i] = true
191 }
192
193 // Minimum count is enough, the other parts has been processed.
194 const min = Math.min(draft_.length, base_.length)
195
196 for (let i = 0; i < min; i++) {
197 // Only untouched indices trigger recursion.
198 if (!draft_.hasOwnProperty(i)) {
199 assigned_[i] = true
200 }
201 if (assigned_[i] === undefined) markChangesRecursively(draft_[i])
202 }
203 }
204 }
205
206 function hasObjectChanges(state: ES5ObjectState) {
207 const {base_, draft_} = state
208
209 // Search for added keys and changed keys. Start at the back, because
210 // non-numeric keys are ordered by time of definition on the object.
211 const keys = ownKeys(draft_)
212 for (let i = keys.length - 1; i >= 0; i--) {
213 const key: any = keys[i]
214 if (key === DRAFT_STATE) continue
215 const baseValue = base_[key]
216 // The `undefined` check is a fast path for pre-existing keys.
217 if (baseValue === undefined && !has(base_, key)) {
218 return true
219 }
220 // Once a base key is deleted, future changes go undetected, because its
221 // descriptor is erased. This branch detects any missed changes.
222 else {
223 const value = draft_[key]
224 const state: ImmerState = value && value[DRAFT_STATE]
225 if (state ? state.base_ !== baseValue : !is(value, baseValue)) {
226 return true
227 }
228 }
229 }
230
231 // At this point, no keys were added or changed.
232 // Compare key count to determine if keys were deleted.
233 const baseIsDraft = !!base_[DRAFT_STATE as any]
234 return keys.length !== ownKeys(base_).length + (baseIsDraft ? 0 : 1) // + 1 to correct for DRAFT_STATE
235 }
236
237 function hasArrayChanges(state: ES5ArrayState) {
238 const {draft_} = state
239 if (draft_.length !== state.base_.length) return true
240 // See #116
241 // If we first shorten the length, our array interceptors will be removed.
242 // If after that new items are added, result in the same original length,
243 // those last items will have no intercepting property.
244 // So if there is no own descriptor on the last position, we know that items were removed and added
245 // N.B.: splice, unshift, etc only shift values around, but not prop descriptors, so we only have to check
246 // the last one
247 // last descriptor can be not a trap, if the array was extended
248 const descriptor = Object.getOwnPropertyDescriptor(
249 draft_,
250 draft_.length - 1
251 )
252 // descriptor can be null, but only for newly created sparse arrays, eg. new Array(10)
253 if (descriptor && !descriptor.get) return true
254 // if we miss a property, it has been deleted, so array probobaly changed
255 for (let i = 0; i < draft_.length; i++) {
256 if (!draft_.hasOwnProperty(i)) return true
257 }
258 // For all other cases, we don't have to compare, as they would have been picked up by the index setters
259 return false
260 }
261
262 function hasChanges_(state: ES5State) {
263 return state.type_ === ProxyType.ES5Object
264 ? hasObjectChanges(state)
265 : hasArrayChanges(state)
266 }
267
268 function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
269 if (state.revoked_) die(3, JSON.stringify(latest(state)))
270 }
271
272 loadPlugin("ES5", {
273 createES5Proxy_,
274 willFinalizeES5_,
275 hasChanges_
276 })
277}
Note: See TracBrowser for help on using the repository browser.