• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mobxjs / mobx / 6725053580

01 Nov 2023 08:33PM UTC coverage: 91.343% (-0.3%) from 91.676%
6725053580

Pull #3790

github

mweststrate
Merge branch 'main' into Matchlighter-decorators2022-2
Pull Request #3790: [Local fork] TC 39 decorators

1788 of 2213 branches covered (0.0%)

96 of 96 new or added lines in 13 files covered. (100.0%)

3313 of 3627 relevant lines covered (91.34%)

6655.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.84
/packages/mobx/src/types/observableobject.ts
1
import {
48✔
2
    CreateObservableOptions,
3
    getAnnotationFromOptions,
4
    propagateChanged,
5
    isAnnotation,
6
    $mobx,
7
    Atom,
8
    Annotation,
9
    ComputedValue,
10
    IAtom,
11
    IComputedValueOptions,
12
    IEnhancer,
13
    IInterceptable,
14
    IListenable,
15
    Lambda,
16
    ObservableValue,
17
    addHiddenProp,
18
    createInstanceofPredicate,
19
    endBatch,
20
    getNextId,
21
    hasInterceptors,
22
    hasListeners,
23
    interceptChange,
24
    isObject,
25
    isPlainObject,
26
    isSpyEnabled,
27
    notifyListeners,
28
    referenceEnhancer,
29
    registerInterceptor,
30
    registerListener,
31
    spyReportEnd,
32
    spyReportStart,
33
    startBatch,
34
    stringifyKey,
35
    globalState,
36
    ADD,
37
    UPDATE,
38
    die,
39
    hasProp,
40
    getDescriptor,
41
    storedAnnotationsSymbol,
42
    ownKeys,
43
    isOverride,
44
    defineProperty,
45
    autoAnnotation,
46
    getAdministration,
47
    getDebugName,
48
    objectPrototype,
49
    MakeResult,
50
    checkIfStateModificationsAreAllowed
51
} from "../internal"
52

53
const descriptorCache = Object.create(null)
48✔
54

55
export type IObjectDidChange<T = any> = {
56
    observableKind: "object"
57
    name: PropertyKey
58
    object: T
59
    debugObjectName: string
60
} & (
61
    | {
62
          type: "add"
63
          newValue: any
64
      }
65
    | {
66
          type: "update"
67
          oldValue: any
68
          newValue: any
69
      }
70
    | {
71
          type: "remove"
72
          oldValue: any
73
      }
74
)
75

76
export type IObjectWillChange<T = any> =
77
    | {
78
          object: T
79
          type: "update" | "add"
80
          name: PropertyKey
81
          newValue: any
82
      }
83
    | {
84
          object: T
85
          type: "remove"
86
          name: PropertyKey
87
      }
88

89
const REMOVE = "remove"
48✔
90

91
export class ObservableObjectAdministration
48✔
92
    implements IInterceptable<IObjectWillChange>, IListenable
93
{
94
    keysAtom_: IAtom
23,015✔
95
    changeListeners_
23,015✔
96
    interceptors_
23,015✔
97
    proxy_: any
23,015✔
98
    isPlainObject_: boolean
23,015✔
99
    appliedAnnotations_?: object
23,015✔
100
    private pendingKeys_: undefined | Map<PropertyKey, ObservableValue<boolean>>
23,015✔
101

102
    constructor(
103
        public target_: any,
23,015✔
104
        public values_ = new Map<PropertyKey, ObservableValue<any> | ComputedValue<any>>(),
23,015!
105
        public name_: string,
23,015✔
106
        // Used anytime annotation is not explicitely provided
107
        public defaultAnnotation_: Annotation = autoAnnotation
23,015✔
108
    ) {
109
        this.keysAtom_ = new Atom(__DEV__ ? `${this.name_}.keys` : "ObservableObject.keys")
23,015!
110
        // Optimization: we use this frequently
111
        this.isPlainObject_ = isPlainObject(this.target_)
23,015✔
112
        if (__DEV__ && !isAnnotation(this.defaultAnnotation_)) {
23,015!
113
            die(`defaultAnnotation must be valid annotation`)
×
114
        }
115
        if (__DEV__) {
23,015✔
116
            // Prepare structure for tracking which fields were already annotated
117
            this.appliedAnnotations_ = {}
23,015✔
118
        }
119
    }
120

121
    getObservablePropValue_(key: PropertyKey): any {
122
        return this.values_.get(key)!.get()
3,904✔
123
    }
124

125
    setObservablePropValue_(key: PropertyKey, newValue): boolean | null {
126
        const observable = this.values_.get(key)
832✔
127
        if (observable instanceof ComputedValue) {
832✔
128
            observable.set(newValue)
24✔
129
            return true
18✔
130
        }
131

132
        // intercept
133
        if (hasInterceptors(this)) {
808✔
134
            const change = interceptChange<IObjectWillChange>(this, {
12✔
135
                type: UPDATE,
136
                object: this.proxy_ || this.target_,
18✔
137
                name: key,
138
                newValue
139
            })
140
            if (!change) {
12✔
141
                return null
2✔
142
            }
143
            newValue = (change as any).newValue
10✔
144
        }
145
        newValue = (observable as any).prepareNewValue_(newValue)
806✔
146

147
        // notify spy & observers
148
        if (newValue !== globalState.UNCHANGED) {
804✔
149
            const notify = hasListeners(this)
739✔
150
            const notifySpy = __DEV__ && isSpyEnabled()
739✔
151
            const change: IObjectDidChange | null =
152
                notify || notifySpy
739✔
153
                    ? {
154
                          type: UPDATE,
155
                          observableKind: "object",
156
                          debugObjectName: this.name_,
157
                          object: this.proxy_ || this.target_,
76✔
158
                          oldValue: (observable as any).value_,
159
                          name: key,
160
                          newValue
161
                      }
162
                    : null
163

164
            if (__DEV__ && notifySpy) {
739✔
165
                spyReportStart(change!)
37✔
166
            }
167
            ;(observable as ObservableValue<any>).setNewValue_(newValue)
739✔
168
            if (notify) {
739✔
169
                notifyListeners(this, change)
16✔
170
            }
171
            if (__DEV__ && notifySpy) {
739✔
172
                spyReportEnd()
37✔
173
            }
174
        }
175
        return true
804✔
176
    }
177

178
    get_(key: PropertyKey): any {
179
        if (globalState.trackingDerivation && !hasProp(this.target_, key)) {
115,961✔
180
            // Key doesn't exist yet, subscribe for it in case it's added later
181
            this.has_(key)
103✔
182
        }
183
        return this.target_[key]
115,961✔
184
    }
185

186
    /**
187
     * @param {PropertyKey} key
188
     * @param {any} value
189
     * @param {Annotation|boolean} annotation true - use default annotation, false - copy as is
190
     * @param {boolean} proxyTrap whether it's called from proxy trap
191
     * @returns {boolean|null} true on success, false on failure (proxyTrap + non-configurable), null when cancelled by interceptor
192
     */
193
    set_(key: PropertyKey, value: any, proxyTrap: boolean = false): boolean | null {
50✔
194
        // Don't use .has(key) - we care about own
195
        if (hasProp(this.target_, key)) {
412✔
196
            // Existing prop
197
            if (this.values_.has(key)) {
341✔
198
                // Observable (can be intercepted)
199
                return this.setObservablePropValue_(key, value)
336✔
200
            } else if (proxyTrap) {
5!
201
                // Non-observable - proxy
202
                return Reflect.set(this.target_, key, value)
5✔
203
            } else {
204
                // Non-observable
205
                this.target_[key] = value
×
206
                return true
×
207
            }
208
        } else {
209
            // New prop
210
            return this.extend_(
71✔
211
                key,
212
                { value, enumerable: true, writable: true, configurable: true },
213
                this.defaultAnnotation_,
214
                proxyTrap
215
            )
216
        }
217
    }
218

219
    // Trap for "in"
220
    has_(key: PropertyKey): boolean {
221
        if (!globalState.trackingDerivation) {
145✔
222
            // Skip key subscription outside derivation
223
            return key in this.target_
14✔
224
        }
225
        this.pendingKeys_ ||= new Map()
131✔
226
        let entry = this.pendingKeys_.get(key)
131✔
227
        if (!entry) {
131✔
228
            entry = new ObservableValue(
48✔
229
                key in this.target_,
230
                referenceEnhancer,
231
                __DEV__ ? `${this.name_}.${stringifyKey(key)}?` : "ObservableObject.key?",
48!
232
                false
233
            )
234
            this.pendingKeys_.set(key, entry)
48✔
235
        }
236
        return entry.get()
131✔
237
    }
238

239
    /**
240
     * @param {PropertyKey} key
241
     * @param {Annotation|boolean} annotation true - use default annotation, false - ignore prop
242
     */
243
    make_(key: PropertyKey, annotation: Annotation | boolean): void {
244
        if (annotation === true) {
711✔
245
            annotation = this.defaultAnnotation_
58✔
246
        }
247
        if (annotation === false) {
711✔
248
            return
2✔
249
        }
250
        assertAnnotable(this, annotation, key)
709✔
251
        if (!(key in this.target_)) {
703✔
252
            // Throw on missing key, except for decorators:
253
            // Decorator annotations are collected from whole prototype chain.
254
            // When called from super() some props may not exist yet.
255
            // However we don't have to worry about missing prop,
256
            // because the decorator must have been applied to something.
257
            if (this.target_[storedAnnotationsSymbol]?.[key]) {
12✔
258
                return // will be annotated by subclass constructor
10✔
259
            } else {
260
                die(1, annotation.annotationType_, `${this.name_}.${key.toString()}`)
2✔
261
            }
262
        }
263
        let source = this.target_
691✔
264
        while (source && source !== objectPrototype) {
691✔
265
            const descriptor = getDescriptor(source, key)
1,089✔
266
            if (descriptor) {
1,089✔
267
                const outcome = annotation.make_(this, key, descriptor, source)
714✔
268
                if (outcome === MakeResult.Cancel) {
705✔
269
                    return
1✔
270
                }
271
                if (outcome === MakeResult.Break) {
704✔
272
                    break
556✔
273
                }
274
            }
275
            source = Object.getPrototypeOf(source)
523✔
276
        }
277
        recordAnnotationApplied(this, annotation, key)
681✔
278
    }
279

280
    /**
281
     * @param {PropertyKey} key
282
     * @param {PropertyDescriptor} descriptor
283
     * @param {Annotation|boolean} annotation true - use default annotation, false - copy as is
284
     * @param {boolean} proxyTrap whether it's called from proxy trap
285
     * @returns {boolean|null} true on success, false on failure (proxyTrap + non-configurable), null when cancelled by interceptor
286
     */
287
    extend_(
288
        key: PropertyKey,
289
        descriptor: PropertyDescriptor,
290
        annotation: Annotation | boolean,
291
        proxyTrap: boolean = false
905✔
292
    ): boolean | null {
293
        if (annotation === true) {
976✔
294
            annotation = this.defaultAnnotation_
855✔
295
        }
296
        if (annotation === false) {
976✔
297
            return this.defineProperty_(key, descriptor, proxyTrap)
3✔
298
        }
299
        assertAnnotable(this, annotation, key)
973✔
300
        const outcome = annotation.extend_(this, key, descriptor, proxyTrap)
971✔
301
        if (outcome) {
968✔
302
            recordAnnotationApplied(this, annotation, key)
964✔
303
        }
304
        return outcome
968✔
305
    }
306

307
    /**
308
     * @param {PropertyKey} key
309
     * @param {PropertyDescriptor} descriptor
310
     * @param {boolean} proxyTrap whether it's called from proxy trap
311
     * @returns {boolean|null} true on success, false on failure (proxyTrap + non-configurable), null when cancelled by interceptor
312
     */
313
    defineProperty_(
314
        key: PropertyKey,
315
        descriptor: PropertyDescriptor,
316
        proxyTrap: boolean = false
15✔
317
    ): boolean | null {
318
        checkIfStateModificationsAreAllowed(this.keysAtom_)
122✔
319
        try {
122✔
320
            startBatch()
122✔
321

322
            // Delete
323
            const deleteOutcome = this.delete_(key)
122✔
324
            if (!deleteOutcome) {
119!
325
                // Failure or intercepted
326
                return deleteOutcome
×
327
            }
328

329
            // ADD interceptor
330
            if (hasInterceptors(this)) {
119!
331
                const change = interceptChange<IObjectWillChange>(this, {
×
332
                    object: this.proxy_ || this.target_,
×
333
                    name: key,
334
                    type: ADD,
335
                    newValue: descriptor.value
336
                })
337
                if (!change) {
×
338
                    return null
×
339
                }
340
                const { newValue } = change as any
×
341
                if (descriptor.value !== newValue) {
×
342
                    descriptor = {
×
343
                        ...descriptor,
344
                        value: newValue
345
                    }
346
                }
347
            }
348

349
            // Define
350
            if (proxyTrap) {
119!
351
                if (!Reflect.defineProperty(this.target_, key, descriptor)) {
×
352
                    return false
×
353
                }
354
            } else {
355
                defineProperty(this.target_, key, descriptor)
119✔
356
            }
357

358
            // Notify
359
            this.notifyPropertyAddition_(key, descriptor.value)
119✔
360
        } finally {
361
            endBatch()
122✔
362
        }
363
        return true
119✔
364
    }
365

366
    // If original descriptor becomes relevant, move this to annotation directly
367
    defineObservableProperty_(
368
        key: PropertyKey,
369
        value: any,
370
        enhancer: IEnhancer<any>,
371
        proxyTrap: boolean = false
×
372
    ): boolean | null {
373
        checkIfStateModificationsAreAllowed(this.keysAtom_)
1,166✔
374
        try {
1,166✔
375
            startBatch()
1,166✔
376

377
            // Delete
378
            const deleteOutcome = this.delete_(key)
1,166✔
379
            if (!deleteOutcome) {
1,160!
380
                // Failure or intercepted
381
                return deleteOutcome
×
382
            }
383

384
            // ADD interceptor
385
            if (hasInterceptors(this)) {
1,160✔
386
                const change = interceptChange<IObjectWillChange>(this, {
6✔
387
                    object: this.proxy_ || this.target_,
9✔
388
                    name: key,
389
                    type: ADD,
390
                    newValue: value
391
                })
392
                if (!change) {
6✔
393
                    return null
4✔
394
                }
395
                value = (change as any).newValue
2✔
396
            }
397

398
            const cachedDescriptor = getCachedObservablePropDescriptor(key)
1,156✔
399
            const descriptor = {
1,156✔
400
                configurable: globalState.safeDescriptors ? this.isPlainObject_ : true,
1,156✔
401
                enumerable: true,
402
                get: cachedDescriptor.get,
403
                set: cachedDescriptor.set
404
            }
405

406
            // Define
407
            if (proxyTrap) {
1,156✔
408
                if (!Reflect.defineProperty(this.target_, key, descriptor)) {
33!
409
                    return false
×
410
                }
411
            } else {
412
                defineProperty(this.target_, key, descriptor)
1,123✔
413
            }
414

415
            const observable = new ObservableValue(
1,156✔
416
                value,
417
                enhancer,
418
                __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
1,156!
419
                false
420
            )
421

422
            this.values_.set(key, observable)
1,156✔
423

424
            // Notify (value possibly changed by ObservableValue)
425
            this.notifyPropertyAddition_(key, observable.value_)
1,156✔
426
        } finally {
427
            endBatch()
1,166✔
428
        }
429
        return true
1,156✔
430
    }
431

432
    // If original descriptor becomes relevant, move this to annotation directly
433
    defineComputedProperty_(
434
        key: PropertyKey,
435
        options: IComputedValueOptions<any>,
436
        proxyTrap: boolean = false
×
437
    ): boolean | null {
438
        checkIfStateModificationsAreAllowed(this.keysAtom_)
281✔
439
        try {
281✔
440
            startBatch()
281✔
441

442
            // Delete
443
            const deleteOutcome = this.delete_(key)
281✔
444
            if (!deleteOutcome) {
281!
445
                // Failure or intercepted
446
                return deleteOutcome
×
447
            }
448

449
            // ADD interceptor
450
            if (hasInterceptors(this)) {
281!
451
                const change = interceptChange<IObjectWillChange>(this, {
×
452
                    object: this.proxy_ || this.target_,
×
453
                    name: key,
454
                    type: ADD,
455
                    newValue: undefined
456
                })
457
                if (!change) {
×
458
                    return null
×
459
                }
460
            }
461
            options.name ||= __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key"
281!
462
            options.context = this.proxy_ || this.target_
281✔
463
            const cachedDescriptor = getCachedObservablePropDescriptor(key)
281✔
464
            const descriptor = {
281✔
465
                configurable: globalState.safeDescriptors ? this.isPlainObject_ : true,
281✔
466
                enumerable: false,
467
                get: cachedDescriptor.get,
468
                set: cachedDescriptor.set
469
            }
470

471
            // Define
472
            if (proxyTrap) {
281!
473
                if (!Reflect.defineProperty(this.target_, key, descriptor)) {
×
474
                    return false
×
475
                }
476
            } else {
477
                defineProperty(this.target_, key, descriptor)
281✔
478
            }
479

480
            this.values_.set(key, new ComputedValue(options))
281✔
481

482
            // Notify
483
            this.notifyPropertyAddition_(key, undefined)
281✔
484
        } finally {
485
            endBatch()
281✔
486
        }
487
        return true
281✔
488
    }
489

490
    /**
491
     * @param {PropertyKey} key
492
     * @param {PropertyDescriptor} descriptor
493
     * @param {boolean} proxyTrap whether it's called from proxy trap
494
     * @returns {boolean|null} true on success, false on failure (proxyTrap + non-configurable), null when cancelled by interceptor
495
     */
496
    delete_(key: PropertyKey, proxyTrap: boolean = false): boolean | null {
1,587✔
497
        checkIfStateModificationsAreAllowed(this.keysAtom_)
1,605✔
498
        // No such prop
499
        if (!hasProp(this.target_, key)) {
1,605✔
500
            return true
1,138✔
501
        }
502

503
        // Intercept
504
        if (hasInterceptors(this)) {
467✔
505
            const change = interceptChange<IObjectWillChange>(this, {
2✔
506
                object: this.proxy_ || this.target_,
3✔
507
                name: key,
508
                type: REMOVE
509
            })
510
            // Cancelled
511
            if (!change) {
2✔
512
                return null
2✔
513
            }
514
        }
515

516
        // Delete
517
        try {
465✔
518
            startBatch()
465✔
519
            const notify = hasListeners(this)
465✔
520
            const notifySpy = __DEV__ && isSpyEnabled()
465✔
521
            const observable = this.values_.get(key)
465✔
522
            // Value needed for spies/listeners
523
            let value = undefined
465✔
524
            // Optimization: don't pull the value unless we will need it
525
            if (!observable && (notify || notifySpy)) {
465!
526
                value = getDescriptor(this.target_, key)?.value
×
527
            }
528
            // delete prop (do first, may fail)
529
            if (proxyTrap) {
465✔
530
                if (!Reflect.deleteProperty(this.target_, key)) {
15!
531
                    return false
×
532
                }
533
            } else {
534
                delete this.target_[key]
450✔
535
            }
536
            // Allow re-annotating this field
537
            if (__DEV__) {
456✔
538
                delete this.appliedAnnotations_![key]
456✔
539
            }
540
            // Clear observable
541
            if (observable) {
456✔
542
                this.values_.delete(key)
27✔
543
                // for computed, value is undefined
544
                if (observable instanceof ObservableValue) {
27✔
545
                    value = observable.value_
26✔
546
                }
547
                // Notify: autorun(() => obj[key]), see #1796
548
                propagateChanged(observable)
27✔
549
            }
550
            // Notify "keys/entries/values" observers
551
            this.keysAtom_.reportChanged()
456✔
552

553
            // Notify "has" observers
554
            // "in" as it may still exist in proto
555
            this.pendingKeys_?.get(key)?.set(key in this.target_)
456✔
556

557
            // Notify spies/listeners
558
            if (notify || notifySpy) {
456✔
559
                const change: IObjectDidChange = {
2✔
560
                    type: REMOVE,
561
                    observableKind: "object",
562
                    object: this.proxy_ || this.target_,
3✔
563
                    debugObjectName: this.name_,
564
                    oldValue: value,
565
                    name: key
566
                }
567
                if (__DEV__ && notifySpy) {
2!
568
                    spyReportStart(change!)
×
569
                }
570
                if (notify) {
2✔
571
                    notifyListeners(this, change)
2✔
572
                }
573
                if (__DEV__ && notifySpy) {
2!
574
                    spyReportEnd()
×
575
                }
576
            }
577
        } finally {
578
            endBatch()
465✔
579
        }
580
        return true
456✔
581
    }
582

583
    /**
584
     * Observes this object. Triggers for the events 'add', 'update' and 'delete'.
585
     * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe
586
     * for callback details
587
     */
588
    observe_(callback: (changes: IObjectDidChange) => void, fireImmediately?: boolean): Lambda {
589
        if (__DEV__ && fireImmediately === true) {
14!
590
            die("`observe` doesn't support the fire immediately property for observable objects.")
×
591
        }
592
        return registerListener(this, callback)
14✔
593
    }
594

595
    intercept_(handler): Lambda {
596
        return registerInterceptor(this, handler)
12✔
597
    }
598

599
    notifyPropertyAddition_(key: PropertyKey, value: any) {
600
        const notify = hasListeners(this)
1,556✔
601
        const notifySpy = __DEV__ && isSpyEnabled()
1,556✔
602
        if (notify || notifySpy) {
1,556✔
603
            const change: IObjectDidChange | null =
604
                notify || notifySpy
18!
605
                    ? ({
606
                          type: ADD,
607
                          observableKind: "object",
608
                          debugObjectName: this.name_,
609
                          object: this.proxy_ || this.target_,
27✔
610
                          name: key,
611
                          newValue: value
612
                      } as const)
613
                    : null
614

615
            if (__DEV__ && notifySpy) {
18✔
616
                spyReportStart(change!)
12✔
617
            }
618
            if (notify) {
18✔
619
                notifyListeners(this, change)
6✔
620
            }
621
            if (__DEV__ && notifySpy) {
18✔
622
                spyReportEnd()
12✔
623
            }
624
        }
625

626
        this.pendingKeys_?.get(key)?.set(true)
1,556✔
627

628
        // Notify "keys/entries/values" observers
629
        this.keysAtom_.reportChanged()
1,556✔
630
    }
631

632
    ownKeys_(): ArrayLike<string | symbol> {
633
        this.keysAtom_.reportObserved()
318✔
634
        return ownKeys(this.target_)
318✔
635
    }
636

637
    keys_(): PropertyKey[] {
638
        // Returns enumerable && own, but unfortunately keysAtom will report on ANY key change.
639
        // There is no way to distinguish between Object.keys(object) and Reflect.ownKeys(object) - both are handled by ownKeys trap.
640
        // We can either over-report in Object.keys(object) or under-report in Reflect.ownKeys(object)
641
        // We choose to over-report in Object.keys(object), because:
642
        // - typically it's used with simple data objects
643
        // - when symbolic/non-enumerable keys are relevant Reflect.ownKeys works as expected
644
        this.keysAtom_.reportObserved()
70✔
645
        return Object.keys(this.target_)
70✔
646
    }
647
}
648

649
export interface IIsObservableObject {
650
    $mobx: ObservableObjectAdministration
651
}
652

653
export function asObservableObject(
48✔
654
    target: any,
655
    options?: CreateObservableOptions
656
): IIsObservableObject {
657
    if (__DEV__ && options && isObservableObject(target)) {
45,722✔
658
        die(`Options can't be provided for already observable objects.`)
2✔
659
    }
660

661
    if (hasProp(target, $mobx)) {
45,720✔
662
        if (__DEV__ && !(getAdministration(target) instanceof ObservableObjectAdministration)) {
22,703✔
663
            die(
2✔
664
                `Cannot convert '${getDebugName(target)}' into observable object:` +
665
                    `\nThe target is already observable of different type.` +
666
                    `\nExtending builtins is not supported.`
667
            )
668
        }
669
        return target
22,701✔
670
    }
671

672
    if (__DEV__ && !Object.isExtensible(target)) {
23,017✔
673
        die("Cannot make the designated object observable; it is not extensible")
2✔
674
    }
675

676
    const name =
677
        options?.name ??
23,015✔
678
        (__DEV__
742!
679
            ? `${
680
                  isPlainObject(target) ? "ObservableObject" : target.constructor.name
742✔
681
              }@${getNextId()}`
682
            : "ObservableObject")
683

684
    const adm = new ObservableObjectAdministration(
23,015✔
685
        target,
686
        new Map(),
687
        String(name),
688
        getAnnotationFromOptions(options)
689
    )
690

691
    addHiddenProp(target, $mobx, adm)
23,015✔
692

693
    return target
23,015✔
694
}
695

696
const isObservableObjectAdministration = createInstanceofPredicate(
48✔
697
    "ObservableObjectAdministration",
698
    ObservableObjectAdministration
699
)
700

701
function getCachedObservablePropDescriptor(key) {
702
    return (
1,437✔
703
        descriptorCache[key] ||
1,962✔
704
        (descriptorCache[key] = {
705
            get() {
706
                return this[$mobx].getObservablePropValue_(key)
3,672✔
707
            },
708
            set(value) {
709
                return this[$mobx].setObservablePropValue_(key, value)
456✔
710
            }
711
        })
712
    )
713
}
714

715
const fallthroughDescriptorCache = Object.create(null)
48✔
716

717
export function getCachedFallthroughPropDescriptor(key) {
48✔
718
    return (
×
719
        fallthroughDescriptorCache[key] ||
×
720
        (fallthroughDescriptorCache[key] = {
721
            get() {
722
                return Reflect.get(Object.getPrototypeOf(this), key, this)
×
723
            },
724
            set(v) {
725
                return Reflect.set(Object.getPrototypeOf(this), key, v, this)
×
726
            }
727
        })
728
    )
729
}
730

731
export function isObservableObject(thing: any): boolean {
48✔
732
    if (isObject(thing)) {
173,964✔
733
        return isObservableObjectAdministration((thing as any)[$mobx])
89,948✔
734
    }
735
    return false
84,016✔
736
}
737

738
export function recordAnnotationApplied(
48✔
739
    adm: ObservableObjectAdministration,
740
    annotation: Annotation,
741
    key: PropertyKey
742
) {
743
    if (__DEV__) {
1,645✔
744
        adm.appliedAnnotations_![key] = annotation
1,645✔
745
    }
746
    // Remove applied decorator annotation so we don't try to apply it again in subclass constructor
747
    delete adm.target_[storedAnnotationsSymbol]?.[key]
1,645✔
748
}
749

750
function assertAnnotable(
751
    adm: ObservableObjectAdministration,
752
    annotation: Annotation,
753
    key: PropertyKey
754
) {
755
    // Valid annotation
756
    if (__DEV__ && !isAnnotation(annotation)) {
1,682✔
757
        die(`Cannot annotate '${adm.name_}.${key.toString()}': Invalid annotation.`)
1✔
758
    }
759

760
    /*
761
    // Configurable, not sealed, not frozen
762
    // Possibly not needed, just a little better error then the one thrown by engine.
763
    // Cases where this would be useful the most (subclass field initializer) are not interceptable by this.
764
    if (__DEV__) {
765
        const configurable = getDescriptor(adm.target_, key)?.configurable
766
        const frozen = Object.isFrozen(adm.target_)
767
        const sealed = Object.isSealed(adm.target_)
768
        if (!configurable || frozen || sealed) {
769
            const fieldName = `${adm.name_}.${key.toString()}`
770
            const requestedAnnotationType = annotation.annotationType_
771
            let error = `Cannot apply '${requestedAnnotationType}' to '${fieldName}':`
772
            if (frozen) {
773
                error += `\nObject is frozen.`
774
            }
775
            if (sealed) {
776
                error += `\nObject is sealed.`
777
            }
778
            if (!configurable) {
779
                error += `\nproperty is not configurable.`
780
                // Mention only if caused by us to avoid confusion
781
                if (hasProp(adm.appliedAnnotations!, key)) {
782
                    error += `\nTo prevent accidental re-definition of a field by a subclass, `
783
                    error += `all annotated fields of non-plain objects (classes) are not configurable.`
784
                }
785
            }
786
            die(error)
787
        }
788
    }
789
    */
790

791
    // Not annotated
792
    if (__DEV__ && !isOverride(annotation) && hasProp(adm.appliedAnnotations_!, key)) {
1,681✔
793
        const fieldName = `${adm.name_}.${key.toString()}`
7✔
794
        const currentAnnotationType = adm.appliedAnnotations_![key].annotationType_
7✔
795
        const requestedAnnotationType = annotation.annotationType_
7✔
796
        die(
7✔
797
            `Cannot apply '${requestedAnnotationType}' to '${fieldName}':` +
798
                `\nThe field is already annotated with '${currentAnnotationType}'.` +
799
                `\nRe-annotating fields is not allowed.` +
800
                `\nUse 'override' annotation for methods overridden by subclass.`
801
        )
802
    }
803
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc