-
Notifications
You must be signed in to change notification settings - Fork 0
/
decorators.ts
131 lines (122 loc) · 3.38 KB
/
decorators.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { Newable } from './types';
/**
* @hidden
*/
export class FinalTypeError extends TypeError {}
/**
* Marks a class as final, preventing inheritance from this class.
* When applied, any attempt to extend this class will result in a TypeError at runtime.
* @remarks
* This decorator does not prevent instantiation of the final class itself.
* @example
* ```ts
* @Final
* class Foo<T> {
* foo: T;
* bar: string;
*
* constructor(foo: T) {
* this.foo = foo;
* this.bar = 'bar';
* }
*
* someFoo(): T {
* return this.foo;
* }
* }
*
* // No problem with instantiation
* const foo = new Foo<string>('foo');
* // The line below will cause a TypeError: Cannot inherit from the final class Foo
* const sub = new SubFoo('subFoo');
* ```
* @see {@link https://github.com/microsoft/TypeScript/issues/1534| Issue #1}
* @see {@link https://github.com/microsoft/TypeScript/issues/8306| Issue #2}
* @see {@link https://github.com/microsoft/TypeScript/issues/50532| Issue #3}
*/
export const Final = <CST extends Newable>(cst: CST): CST => {
class F extends cst {
constructor(...args: any[]) {
super(...args);
const newTarget = new.target as unknown as typeof F;
if (newTarget !== F) {
throw new FinalTypeError(`Cannot inherit from the final class: `);
}
}
}
Reflect.defineProperty(F, 'name', {
// eslint-disable-next-line
value: (cst as any).name || 'Final',
});
return F as CST;
};
const _freeze = (obj: object) => {
Object.freeze(obj);
};
/**
* When applied to a class it creates a [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) instance of it,
* thus preventing modifications to instance properties after instantiation.
*
* @example
* ```ts
@Frozen
class Foo<T> {
foo: T;
bar?: MaybeUndefined<string>;
constructor(foo: T) {
this.foo = foo;
this.bar = 'bar';
}
someFoo(): T {
return this.foo;
}
}
const foo = new Foo('foo');
// The line below will cause a TypeError: Cannot assign to read only property 'bar'
foo.bar = 'altered bar';
// The line below will cause a TypeError: Cannot delete property 'bar'
delete foo.bar;
* ```
*/
export function Frozen<T extends Newable>(cst: T): T & Newable {
return class Locked extends cst {
constructor(...args: any[]) {
super(...args);
_freeze(this);
}
};
}
const _seal = (obj: object) => {
Object.seal(obj);
};
/**
* When applied to a class, it creates a [sealed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) instance of it,
* preventing extensions and making existing properties non-configurable.
*
* @example
* ```ts
* @Sealed
* class Person {
* constructor(public name: string, public age?: number) {}
* }
*
* const john = new Person('John', 30);
* // Trying to add a new property will throw an error
* (john as any).email = 'john@example.com'; // TypeError: Cannot add property email, object is not extensible
*
* // Existing properties can still be modified
* john.age = 31; // Allowed
*
* // Existing properties cannot be re-configured or deleted
* delete john.age; // TypeError: Cannot delete property 'age'
* }
* ```
* */
export function Sealed<T extends Newable>(cst: T): T & Newable {
return class Locked extends cst {
constructor(...args: any[]) {
super(...args);
_seal(this);
}
};
}