Last active
November 1, 2021 22:17
-
-
Save jcorbin/8f7ed25df4ac9ae6fa4dc0819314083f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import test from 'ava'; | |
/** | |
* deep object freeze that avoids the Readonly<{set foo()}> trap, and avoids | |
* needing to pull in something heavier like harden() | |
* | |
* @template {object} O | |
* @param {O} o | |
* @returns {O} | |
*/ | |
function freeze(o) { | |
// freeze the object first to break any cycles if we re-encounter this | |
// object in the loop below (or any recursive sub-loop thereof) | |
Object.freeze(o); | |
for (const [_key, {value}] of Object.entries(Object.getOwnPropertyDescriptors(o))) { | |
// skip accessor properties | |
if (value === undefined) continue; | |
// skip primitives | |
if (typeof value !== 'object') continue; | |
// skip already frozen objects | |
if (Object.isFrozen(value)) continue; | |
freeze(value); | |
} | |
// NOTE: this intentionally discards the Readonly<T> wrapper | |
return o; | |
} | |
test('freeze', t => { | |
let cv = 7; | |
const o = { | |
a: 3, | |
bs: [4, 5, 6], | |
get c() { return cv }, | |
set c(v) { cv = v }, | |
/** @type {any} this field is just a hack to prove cycle safety */ | |
d: null, | |
}; | |
o.d = o; | |
freeze(o); | |
// nope to regular property change | |
t.is(o.a, 3); | |
t.throws(() => { o.a = 9 }, {message: /Cannot assign to read only property/}); | |
t.is(o.a, 3); | |
// nope to array element change, frozen | |
t.is(o.bs[1], 5); | |
t.throws(() => { o.bs[1] = 9 }, {message: /Cannot assign to read only property/}); | |
t.is(o.bs[1], 5); | |
// nope to array push, frozen | |
t.deepEqual(o.bs, [4, 5, 6]); | |
t.throws(() => o.bs.push(9), {message: /Cannot add property 3/}) | |
t.deepEqual(o.bs, [4, 5, 6]); | |
// nope to array pop, frozen | |
t.deepEqual(o.bs, [4, 5, 6]); | |
t.throws(() => o.bs.pop(), {message: /Cannot delete property '2'/}) | |
t.deepEqual(o.bs, [4, 5, 6]); | |
// explicitly defined property change is okay tho | |
t.is(o.c, 7); | |
o.c = 9; | |
t.is(o.c, 9); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment