forked from paradoxuum/centurion
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reflect.ts
253 lines (218 loc) · 5.81 KB
/
reflect.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
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/**
* Reflection/metadata API
*
* @see https://github.com/rbxts-flamework/core/blob/20683a7f7eb1f8844f7f75e643d764222d06ef24/src/reflect.ts
*/
export namespace MetadataReflect {
// object -> property -> key -> value
export const metadata = new WeakMap<
object,
Map<string | typeof NO_PROP_MARKER, Map<string, unknown>>
>();
export const decorators = new Map<string, Array<object>>();
export const idToObj = new Map<string, object>();
export const objToId = new Map<object, string>();
const NO_PROP_MARKER = {} as { _nominal_Marker: never };
function getObjMetadata(
obj: object,
prop: string | undefined,
create: true,
): Map<string, unknown>;
function getObjMetadata(
obj: object,
prop?: string,
): Map<string, unknown> | undefined;
function getObjMetadata(obj: object, prop?: string, create?: boolean) {
const realProp = prop ?? NO_PROP_MARKER;
if (create) {
let objMetadata = metadata.get(obj);
if (!objMetadata) {
objMetadata = new Map();
metadata.set(obj, objMetadata);
}
let propMetadata = objMetadata.get(realProp);
if (!propMetadata) {
propMetadata = new Map();
objMetadata.set(realProp, propMetadata);
}
return propMetadata;
}
return metadata.get(obj)?.get(realProp);
}
function getParentConstructor(obj: object) {
const metatable = getmetatable(obj) as { __index?: object };
if (metatable && typeIs(metatable, "table")) {
return rawget(metatable, "__index") as object;
}
}
/**
* Apply metadata onto this object.
*/
export function defineMetadata(
obj: object,
key: string,
value: unknown,
property?: string,
) {
// 'identifier' is a special, unique ID across all metadata classes.
if (key === "identifier") {
assert(typeIs(value, "string"), "identifier must be a string.");
assert(!objToId.has(obj), "obj is already registered.");
assert(!idToObj.has(value as never), "id is already registered.");
objToId.set(obj, value);
idToObj.set(value, obj);
}
const metadata = getObjMetadata(obj, property, true);
metadata.set(key, value);
}
/**
* Apply metadata in batch onto this object.
*/
export function defineMetadataBatch(
obj: object,
list: { [key: string]: unknown },
property?: string,
) {
const metadata = getObjMetadata(obj, property, true);
for (const [key, value] of pairs(list)) {
metadata.set(key as string, value);
}
}
/**
* Delete metadata from this object.
*/
export function deleteMetadata(obj: object, key: string, property?: string) {
const metadata = getObjMetadata(obj, property);
metadata?.delete(key);
}
/**
* Get metadata from this object.
* Type parameter is an assertion.
*/
export function getOwnMetadata<T>(
obj: object,
key: string,
property?: string,
): T | undefined {
const metadata = getObjMetadata(obj, property);
return metadata?.get(key) as T;
}
/**
* Check if this object has the specified metadata key.
*/
export function hasOwnMetadata(obj: object, key: string, property?: string) {
const metadata = getObjMetadata(obj, property);
return metadata?.has(key) ?? false;
}
/**
* Retrieve all metadata keys for this object.
*/
export function getOwnMetadataKeys(obj: object, property?: string) {
const metadata = getObjMetadata(obj, property);
const keys = new Array<string>();
if (metadata !== undefined) {
for (const [key] of metadata) {
keys.push(key);
}
}
return keys;
}
/**
* Retrieves all properties (that contain metadata) on this object.
*/
export function getOwnProperties(obj: object) {
const properties = metadata.get(obj);
if (!properties) return [];
const keys = new Array<string>();
for (const [key] of properties) {
if (key !== NO_PROP_MARKER) {
keys.push(key as string);
}
}
return keys;
}
/**
* Retrieve all values for the specified key from the object and its parents.
* Type parameter is an assertion.
*/
export function getMetadatas<T extends defined>(
obj: object,
key: string,
property?: string,
): T[] {
const values = new Array<T>();
const value = getOwnMetadata(obj, key, property);
if (value !== undefined) {
values.push(value as T);
}
const parent = getParentConstructor(obj);
if (parent) {
for (const value of getMetadatas<T>(parent, key, property)) {
values.push(value);
}
}
return values;
}
/**
* Get metadata from this object or its parents.
* Type parameter is an assertion.
*/
export function getMetadata<T>(
obj: object,
key: string,
property?: string,
): T | undefined {
const value = getOwnMetadata(obj, key, property);
if (value !== undefined) {
return value as T;
}
const parent = getParentConstructor(obj);
if (parent) {
return getMetadata(parent, key, property);
}
}
/**
* Check if this object or any of its parents has the specified metadata key.
*/
export function hasMetadata(
obj: object,
key: string,
property?: string,
): boolean {
const value = hasOwnMetadata(obj, key, property);
if (value) {
return value;
}
const parent = getParentConstructor(obj);
if (parent) {
return hasMetadata(parent, key, property);
}
return false;
}
/**
* Retrieve all metadata keys for this object and its parents.
*/
export function getMetadataKeys(obj: object, property?: string): string[] {
const keys = new Set<string>(getOwnMetadataKeys(obj, property));
const parent = getParentConstructor(obj);
if (parent) {
for (const key of getMetadataKeys(parent, property)) {
keys.add(key);
}
}
return [...keys];
}
/**
* Retrieves all properties (that contain metadata) on this object and its parents.
*/
export function getProperties(obj: object) {
const keys = new Set<string>(getOwnProperties(obj));
const parent = getParentConstructor(obj);
if (parent) {
for (const key of getProperties(parent)) {
keys.add(key);
}
}
return [...keys];
}
}