-
Notifications
You must be signed in to change notification settings - Fork 727
/
Copy pathreflection_utils.ts
225 lines (173 loc) · 7.52 KB
/
reflection_utils.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
import { LazyServiceIdentifer } from "../annotation/inject";
import * as ERROR_MSGS from "../constants/error_msgs";
import { TargetTypeEnum } from "../constants/literal_types";
import * as METADATA_KEY from "../constants/metadata_keys";
import { interfaces } from "../interfaces/interfaces";
import { getFunctionName } from "../utils/serialization";
import { Target } from "./target";
function getDependencies(
metadataReader: interfaces.MetadataReader, func: Function
): interfaces.Target[] {
const constructorName = getFunctionName(func);
const targets: interfaces.Target[] = getTargets(metadataReader, constructorName, func, false);
return targets;
}
function getTargets(
metadataReader: interfaces.MetadataReader, constructorName: string, func: Function, isBaseClass: boolean
): interfaces.Target[] {
const metadata = metadataReader.getConstructorMetadata(func);
// TypeScript compiler generated annotations
const serviceIdentifiers = metadata.compilerGeneratedMetadata;
// All types resolved must be annotated with @injectable
if (serviceIdentifiers === undefined) {
const msg = `${ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION} ${constructorName}.`;
throw new Error(msg);
}
// User generated annotations
const constructorArgsMetadata = metadata.userGeneratedMetadata;
const keys = Object.keys(constructorArgsMetadata);
const hasUserDeclaredUnknownInjections = (func.length === 0 && keys.length > 0);
const iterations = (hasUserDeclaredUnknownInjections) ? keys.length : func.length;
// Target instances that represent constructor arguments to be injected
const constructorTargets = getConstructorArgsAsTargets(
isBaseClass,
constructorName,
serviceIdentifiers,
constructorArgsMetadata,
iterations
);
// Target instances that represent properties to be injected
const propertyTargets = getClassPropsAsTargets(metadataReader, func);
const targets = [
...constructorTargets,
...propertyTargets
];
return targets;
}
function getConstructorArgsAsTarget(
index: number,
isBaseClass: boolean,
constructorName: string,
serviceIdentifiers: any,
constructorArgsMetadata: any
) {
// Create map from array of metadata for faster access to metadata
const targetMetadata = constructorArgsMetadata[index.toString()] || [];
const metadata = formatTargetMetadata(targetMetadata);
const isManaged = metadata.unmanaged !== true;
// Take types to be injected from user-generated metadata
// if not available use compiler-generated metadata
let serviceIdentifier = serviceIdentifiers[index];
const injectIdentifier = (metadata.inject || metadata.multiInject);
serviceIdentifier = (injectIdentifier) ? (injectIdentifier) : serviceIdentifier;
// we unwrap LazyServiceIdentifer wrappers to allow circular dependencies on symbols
if (serviceIdentifier instanceof LazyServiceIdentifer) {
serviceIdentifier = serviceIdentifier.unwrap();
}
// Types Object and Function are too ambiguous to be resolved
// user needs to generate metadata manually for those
if (isManaged) {
const isObject = serviceIdentifier === Object;
const isFunction = serviceIdentifier === Function;
const isUndefined = serviceIdentifier === undefined;
const isUnknownType = (isObject || isFunction || isUndefined);
if (!isBaseClass && isUnknownType) {
const msg = `${ERROR_MSGS.MISSING_INJECT_ANNOTATION} argument ${index} in class ${constructorName}.`;
throw new Error(msg);
}
const target = new Target(TargetTypeEnum.ConstructorArgument, metadata.targetName, serviceIdentifier);
target.metadata = targetMetadata;
return target;
}
return null;
}
function getConstructorArgsAsTargets(
isBaseClass: boolean,
constructorName: string,
serviceIdentifiers: any,
constructorArgsMetadata: any,
iterations: number
) {
const targets: interfaces.Target[] = [];
for (let i = 0; i < iterations; i++) {
const index = i;
const target = getConstructorArgsAsTarget(
index,
isBaseClass,
constructorName,
serviceIdentifiers,
constructorArgsMetadata
);
if (target !== null) {
targets.push(target);
}
}
return targets;
}
function getClassPropsAsTargets(metadataReader: interfaces.MetadataReader, constructorFunc: Function) {
const classPropsMetadata = metadataReader.getPropertiesMetadata(constructorFunc);
let targets: interfaces.Target[] = [];
const keys = Object.keys(classPropsMetadata);
for (const key of keys) {
// the metadata for the property being injected
const targetMetadata = classPropsMetadata[key];
// the metadata formatted for easier access
const metadata = formatTargetMetadata(classPropsMetadata[key]);
// the name of the property being injected
const targetName = metadata.targetName || key;
// Take types to be injected from user-generated metadata
const serviceIdentifier = (metadata.inject || metadata.multiInject);
// The property target
const target = new Target(TargetTypeEnum.ClassProperty, targetName, serviceIdentifier);
target.metadata = targetMetadata;
targets.push(target);
}
// Check if base class has injected properties
const baseConstructor = Object.getPrototypeOf(constructorFunc.prototype).constructor;
if (baseConstructor !== Object) {
const baseTargets = getClassPropsAsTargets(metadataReader, baseConstructor);
targets = [
...targets,
...baseTargets
];
}
return targets;
}
function getBaseClassDependencyCount(metadataReader: interfaces.MetadataReader, func: Function): number {
const baseConstructor = Object.getPrototypeOf(func.prototype).constructor;
if (baseConstructor !== Object) {
// get targets for base class
const baseConstructorName = getFunctionName(baseConstructor);
const targets = getTargets(metadataReader, baseConstructorName, baseConstructor, true);
// get unmanaged metadata
const metadata: any[] = targets.map((t: interfaces.Target) =>
t.metadata.filter((m: interfaces.Metadata) =>
m.key === METADATA_KEY.UNMANAGED_TAG));
// Compare the number of constructor arguments with the number of
// unmanaged dependencies unmanaged dependencies are not required
const unmanagedCount = [].concat.apply([], metadata).length;
const dependencyCount = targets.length - unmanagedCount;
if (dependencyCount > 0) {
return dependencyCount;
} else {
return getBaseClassDependencyCount(metadataReader, baseConstructor);
}
} else {
return 0;
}
}
function formatTargetMetadata(targetMetadata: any[]) {
// Create map from array of metadata for faster access to metadata
const targetMetadataMap: any = {};
targetMetadata.forEach((m: interfaces.Metadata) => {
targetMetadataMap[m.key.toString()] = m.value;
});
// user generated metadata
return {
inject : targetMetadataMap[METADATA_KEY.INJECT_TAG],
multiInject: targetMetadataMap[METADATA_KEY.MULTI_INJECT_TAG],
targetName: targetMetadataMap[METADATA_KEY.NAME_TAG],
unmanaged: targetMetadataMap[METADATA_KEY.UNMANAGED_TAG]
};
}
export { getDependencies, getBaseClassDependencyCount, getFunctionName };