mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Object.freeze, Object.seal and Object.preventExtensions accept a class instance whose static type names the class, and Object.isFrozen, Object.isSealed and Object.isExtensible answer for it as Node does. Object.defineProperty with a string-literal key changes the value and the writable, enumerable and configurable attributes of a declared field, with Node's validation and errors. Only hierarchies passed to these calls pay for them: they get a per-instance integrity word and an attribute word per redefined field, and each write to a declared field checks them first. A write to a read-only field throws Node's TypeError in strict mode code and leaves the field unchanged in sloppy mode code; a class field initializer on a non-extensible instance throws "Cannot define property x, object is not extensible". The instance's checked view carries the attributes, so Object.keys, JSON.stringify, util.inspect and getOwnPropertyDescriptor follow them. Readonly<C> over a class maps to the class. Through an untyped value, freezing, sealing or preventing extensions of a class instance throws SC2020 instead of doing nothing. The runtime's "Cannot redefine property" and "Cannot add property" errors name the property, as Node does.
170 lines
4.3 KiB
TypeScript
170 lines
4.3 KiB
TypeScript
export {};
|
|
import { inspect } from "node:util";
|
|
|
|
const message = (run: () => void): string => {
|
|
try {
|
|
run();
|
|
return "no error";
|
|
} catch (e) {
|
|
return `${e instanceof TypeError ? "TypeError" : "Error"}: ${(e as Error).message}`;
|
|
}
|
|
};
|
|
|
|
class Point {
|
|
x = 1;
|
|
y = 2;
|
|
label = "p";
|
|
move(dx: number): void {
|
|
this.x += dx;
|
|
}
|
|
}
|
|
|
|
const p = new Point();
|
|
console.log(Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p));
|
|
const same = Object.freeze(p);
|
|
console.log(same === p, Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p));
|
|
console.log(message(() => {
|
|
p.x = 5;
|
|
}));
|
|
console.log(message(() => p.move(1)));
|
|
console.log(p.x, Object.keys(p));
|
|
const q = new Point();
|
|
q.x = 9;
|
|
console.log(q.x, Object.isFrozen(q));
|
|
|
|
class Point3 extends Point {
|
|
z = 3;
|
|
}
|
|
const p3 = new Point3();
|
|
Object.freeze(p3);
|
|
console.log(message(() => {
|
|
p3.z = 1;
|
|
}));
|
|
const asBase: Point = new Point3();
|
|
Object.freeze(asBase);
|
|
console.log(message(() => {
|
|
asBase.y = 0;
|
|
}));
|
|
console.log(asBase.y);
|
|
|
|
class Counter {
|
|
n = 0;
|
|
}
|
|
const c = Object.seal(new Counter());
|
|
c.n = 5;
|
|
console.log(c.n, Object.isSealed(c), Object.isFrozen(c), Object.isExtensible(c));
|
|
const d = new Counter();
|
|
Object.preventExtensions(d);
|
|
d.n = 2;
|
|
console.log(d.n, Object.isExtensible(d), Object.isSealed(d), Object.isFrozen(d));
|
|
const loose: any = d;
|
|
console.log(message(() => {
|
|
loose.extra = 1;
|
|
}));
|
|
|
|
class Config {
|
|
mode = "dev";
|
|
level = 1;
|
|
}
|
|
const cfg = new Config();
|
|
Object.defineProperty(cfg, "mode", { value: "prod", writable: false });
|
|
console.log(cfg.mode, Object.keys(cfg), Object.values(cfg));
|
|
console.log(message(() => {
|
|
cfg.mode = "x";
|
|
}));
|
|
Object.defineProperty(cfg, "level", { enumerable: false });
|
|
console.log(Object.keys(cfg), Object.entries(cfg), cfg.level);
|
|
Object.defineProperty(cfg, "level", { configurable: false });
|
|
console.log(message(() => Object.defineProperty(cfg, "level", { enumerable: true })));
|
|
console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "mode")));
|
|
console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "level")));
|
|
cfg.level = 7;
|
|
console.log(cfg.level);
|
|
|
|
const fz = Object.freeze(new Config());
|
|
console.log(message(() => Object.defineProperty(fz, "level", { value: 2 })));
|
|
console.log(message(() => Object.defineProperty(fz, "level", { value: 1 })));
|
|
console.log(fz.level, JSON.stringify(Object.getOwnPropertyDescriptor(fz, "level")));
|
|
|
|
class Frozen {
|
|
a = 1;
|
|
constructor() {
|
|
Object.freeze(this);
|
|
}
|
|
}
|
|
const frozenInCtor = new Frozen();
|
|
console.log(frozenInCtor.a, Object.isFrozen(frozenInCtor));
|
|
console.log(message(() => {
|
|
frozenInCtor.a = 2;
|
|
}));
|
|
|
|
class Extended extends Frozen {
|
|
b = 2;
|
|
}
|
|
console.log(message(() => new Extended()));
|
|
|
|
class Empty {}
|
|
const empty = new Empty();
|
|
console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty));
|
|
Object.preventExtensions(empty);
|
|
console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty));
|
|
|
|
class Totals {
|
|
count = 1;
|
|
items: string[] = [];
|
|
}
|
|
const t = new Totals();
|
|
Object.freeze(t);
|
|
console.log(message(() => {
|
|
t.count += 1;
|
|
}));
|
|
console.log(message(() => {
|
|
t.count++;
|
|
}));
|
|
console.log(message(() => {
|
|
({ value: t.count } = { value: 5 });
|
|
}));
|
|
t.items.push("still mutable");
|
|
console.log(t.count, t.items, Object.isFrozen(t.items));
|
|
console.log(JSON.stringify(Object.getOwnPropertyDescriptor(t, "count")));
|
|
|
|
class Holder<T> {
|
|
value: T;
|
|
constructor(value: T) {
|
|
this.value = value;
|
|
}
|
|
}
|
|
const h = Object.freeze(new Holder("text"));
|
|
console.log(h.value, Object.isFrozen(h));
|
|
const h2 = new Holder(4);
|
|
Object.seal(h2);
|
|
h2.value = 5;
|
|
console.log(h2.value, Object.isSealed(h2));
|
|
|
|
class Settings {
|
|
mode = "dev";
|
|
level = 1;
|
|
}
|
|
const settings = new Settings();
|
|
console.log(settings);
|
|
Object.defineProperty(settings, "level", { enumerable: false });
|
|
console.log(settings, inspect(settings));
|
|
console.log([settings], { settings });
|
|
|
|
class Pair {
|
|
x = 1;
|
|
y = 2;
|
|
}
|
|
const pair = new Pair();
|
|
Object.freeze(pair);
|
|
console.log(pair, Object.isFrozen(pair));
|
|
const sealed = new Pair();
|
|
(sealed as any).extra = "added";
|
|
Object.seal(sealed);
|
|
console.log(sealed, Object.isSealed(sealed));
|
|
|
|
// JSON.stringify reads the same view: non-enumerable fields are skipped.
|
|
console.log(JSON.stringify(cfg), JSON.stringify(settings));
|
|
console.log(JSON.stringify({ settings, pair, sealed }));
|
|
console.log(JSON.stringify([fz, t]));
|