forked from javascript-obfuscator/javascript-obfuscator
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInitializable.ts
More file actions
179 lines (148 loc) · 6.58 KB
/
Copy pathInitializable.ts
File metadata and controls
179 lines (148 loc) · 6.58 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* tslint:disable:no-invalid-this */
import { IInitializable } from '../interfaces/IInitializable';
const defaultDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: true
};
const initializedTargetMetadataKey: string = '_initialized';
const initializablePropertiesSetMetadataKey: string = '_initializablePropertiesSet';
const wrappedMethodsSetMetadataKey: string = '_wrappedMethodsSet';
const constructorMethodName: string = 'constructor';
/**
* @param {string} initializeMethodName
* @returns {(target: IInitializable, propertyKey: (string | symbol)) => any}
*/
export function initializable (
initializeMethodName: string = 'initialize'
): (target: IInitializable, propertyKey: string | symbol) => any {
const decoratorName: string = Object.keys(this)[0];
return (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor => {
const initializeMethod: Function = target[initializeMethodName];
if (!initializeMethod || typeof initializeMethod !== 'function') {
throw new Error(`\`${initializeMethodName}\` method with initialization logic not ` +
`found. \`@${decoratorName}\` decorator requires \`${initializeMethodName}\` method`);
}
/**
* Stage #1: initialize target metadata
*/
initializeTargetMetadata(initializedTargetMetadataKey, false, target);
initializeTargetMetadata(initializablePropertiesSetMetadataKey, new Set(), target);
initializeTargetMetadata(wrappedMethodsSetMetadataKey, new Set(), target);
/**
* Stage #2: wrap target methods
*/
wrapTargetMethodsInInitializedCheck(target, initializeMethodName);
wrapInitializeMethodInInitializeCheck(target, initializeMethodName, propertyKey);
/**
* Stage #3: wrap target properties
*/
return wrapInitializableProperty(target, propertyKey);
};
}
/**
* @param {string} metadataKey
* @param metadataValue
* @param {IInitializable} target
*/
function initializeTargetMetadata (metadataKey: string, metadataValue: any, target: IInitializable): void {
const hasInitializedMetadata: boolean = Reflect.hasMetadata(metadataKey, target);
if (!hasInitializedMetadata) {
Reflect.defineMetadata(metadataKey, metadataValue, target);
}
}
/**
* Wraps all target methods with additional logic that check that this methods will called after `initialize` method
*
* @param {IInitializable} target
* @param {string} initializeMethodName
*/
function wrapTargetMethodsInInitializedCheck (target: IInitializable, initializeMethodName: string): void {
const ownPropertyNames: string[] = Object.getOwnPropertyNames(target);
const prohibitedPropertyNames: string[] = [initializeMethodName, constructorMethodName];
ownPropertyNames.forEach((propertyName: string) => {
const initializablePropertiesSet: Set <string | symbol> = Reflect
.getMetadata(initializablePropertiesSetMetadataKey, target);
const wrappedMethodsSet: Set <string | symbol> = Reflect
.getMetadata(wrappedMethodsSetMetadataKey, target);
const isProhibitedPropertyName: boolean = prohibitedPropertyNames.includes(propertyName)
|| initializablePropertiesSet.has(propertyName)
|| wrappedMethodsSet.has(propertyName);
if (isProhibitedPropertyName) {
return;
}
const targetProperty: IInitializable[keyof IInitializable] = target[propertyName];
if (typeof targetProperty !== 'function') {
return;
}
const methodDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, propertyName) || defaultDescriptor;
const originalMethod: Function = methodDescriptor.value;
Object.defineProperty(target, propertyName, {
...methodDescriptor,
value: function (): void {
if (!Reflect.getMetadata(initializedTargetMetadataKey, this)) {
throw new Error(`Class should be initialized with \`${initializeMethodName}()\` method`);
}
return originalMethod.apply(this, arguments);
}
});
wrappedMethodsSet.add(propertyName);
});
}
/**
* Wraps `initialize` method with additional logic to check that `initialized` properties will set
*
* @param {IInitializable} target
* @param {string} initializeMethodName
* @param {string | symbol} propertyKey
*/
function wrapInitializeMethodInInitializeCheck (
target: IInitializable,
initializeMethodName: string,
propertyKey: string | symbol
): void {
const methodDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, initializeMethodName) || defaultDescriptor;
const originalMethod: Function = methodDescriptor.value;
Object.defineProperty(target, initializeMethodName, {
...methodDescriptor,
value: function (): typeof originalMethod {
/**
* should define metadata before `initialize` method call,
* because of cases when other methods will called inside `initialize` method
*/
Reflect.defineMetadata(initializedTargetMetadataKey, true, this);
const result: typeof originalMethod = originalMethod.apply(this, arguments);
if (this[propertyKey]) {}
return result;
}
});
}
/**
* Wraps initializable property in additional checks
*
* @param {IInitializable} target
* @param {string | symbol} propertyKey
* @returns {PropertyDescriptor}
*/
function wrapInitializableProperty (target: IInitializable, propertyKey: string | symbol): PropertyDescriptor {
const initializablePropertiesSet: Set <string | symbol> = Reflect
.getMetadata(initializablePropertiesSetMetadataKey, target);
initializablePropertiesSet.add(propertyKey);
const initializablePropertyMetadataKey: string = `_${propertyKey.toString()}`;
const propertyDescriptor: PropertyDescriptor = Object
.getOwnPropertyDescriptor(target, initializablePropertyMetadataKey) || defaultDescriptor;
Object.defineProperty(target, propertyKey, {
...propertyDescriptor,
get: function (): any {
if (this[initializablePropertyMetadataKey] === undefined) {
throw new Error(`Property \`${propertyKey.toString()}\` is not initialized! Initialize it first!`);
}
return this[initializablePropertyMetadataKey];
},
set: function (newVal: any): void {
this[initializablePropertyMetadataKey] = newVal;
}
});
return propertyDescriptor;
}