-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
decorators.ts
296 lines (278 loc) Β· 8.15 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
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import {global, Type, isFunction, stringify} from 'angular2/src/facade/lang';
/**
* Declares the interface to be used with {@link Class}.
*/
export interface ClassDefinition {
/**
* Optional argument for specifying the superclass.
*/
extends?: Type;
/**
* Required constructor function for a class.
*
* The function may be optionally wrapped in an `Array`, in which case additional parameter
* annotations may be specified.
* The number of arguments and the number of parameter annotations must match.
*
* See {@link Class} for example of usage.
*/
constructor: (Function | Array<any>);
}
/**
* An interface implemented by all Angular type decorators, which allows them to be used as ES7
* decorators as well as
* Angular DSL syntax.
*
* DSL syntax:
*
* ```
* var MyClass = ng
* .Component({...})
* .View({...})
* .Class({...});
* ```
*
* ES7 syntax:
*
* ```
* @ng.Component({...})
* @ng.View({...})
* class MyClass {...}
* ```
*/
export interface TypeDecorator {
/**
* Invoke as ES7 decorator.
*/
<T extends Type>(type: T): T;
/**
* Storage for the accumulated annotations so far used by the DSL syntax.
*
* Used by {@link Class} to annotate the generated class.
*/
annotations: Array<any>;
/**
* Generate a class from the definition and annotate it with {@link TypeDecorator#annotations}.
*/
Class(obj: ClassDefinition): Type;
}
/**
* An interface implemented by all Angular parameter decorators, which allows them to be used as ES7
* decorators.
*/
export interface ParameterDecorator {
/**
* Invoke as ES7 decorator.
*/
(cls: Type, unusedKey: any, index: number): void;
}
function extractAnnotation(annotation: any): any {
if (isFunction(annotation) && annotation.hasOwnProperty('annotation')) {
// it is a decorator, extract annotation
annotation = annotation.annotation;
}
return annotation;
}
function applyParams(fnOrArray: (Function | Array<any>), key: string): Function {
if (fnOrArray === Object || fnOrArray === String || fnOrArray === Function ||
fnOrArray === Number || fnOrArray === Array) {
throw new Error(`Can not use native ${stringify(fnOrArray)} as constructor`);
}
if (isFunction(fnOrArray)) {
return <Function>fnOrArray;
} else if (fnOrArray instanceof Array) {
var annotations: Array<any> = fnOrArray;
var fn: Function = fnOrArray[fnOrArray.length - 1];
if (!isFunction(fn)) {
throw new Error(
`Last position of Class method array must be Function in key ${key} was '${stringify(fn)}'`);
}
var annoLength = annotations.length - 1;
if (annoLength != fn.length) {
throw new Error(
`Number of annotations (${annoLength}) does not match number of arguments (${fn.length}) in the function: ${stringify(fn)}`);
}
var paramsAnnotations: Array<Array<any>> = [];
for (var i = 0, ii = annotations.length - 1; i < ii; i++) {
var paramAnnotations: Array<any> = [];
paramsAnnotations.push(paramAnnotations);
var annotation = annotations[i];
if (annotation instanceof Array) {
for (var j = 0; j < annotation.length; j++) {
paramAnnotations.push(extractAnnotation(annotation[j]));
}
} else if (isFunction(annotation)) {
paramAnnotations.push(extractAnnotation(annotation));
} else {
paramAnnotations.push(annotation);
}
}
Reflect.defineMetadata('parameters', paramsAnnotations, fn);
return fn;
} else {
throw new Error(
`Only Function or Array is supported in Class definition for key '${key}' is '${stringify(fnOrArray)}'`);
}
}
/**
* Provides a way for expressing ES6 classes with parameter annotations in ES5.
*
* ## Basic Example
*
* ```
* var Greeter = ng.Class({
* constructor: function(name) {
* this.name = name;
* },
*
* greet: function() {
* alert('Hello ' + this.name + '!');
* }
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class Greeter {
* constructor(name) {
* this.name = name;
* }
*
* greet() {
* alert('Hello ' + this.name + '!');
* }
* }
* ```
*
* or equivalent to ES5:
*
* ```
* var Greeter = function (name) {
* this.name = name;
* }
*
* Greeter.prototype.greet = function () {
* alert('Hello ' + this.name + '!');
* }
* ```
*
* ## Example with parameter annotations
*
* ```
* var MyService = neg.Class({
* constructor: [String, [new Query(), QueryList], function(name, queryList) {
* ...
* }];
* });
* ```
*
* is equivalent to ES6:
*
* ```
* class MyService {
* constructor(name: string, @Query() queryList: QueryList) {
* ...
* }
* }
* ```
*
* ## Example with inheritance
*
* ```
* var Shape = ng.Class({
* constructor: (color) {
* this.color = color;
* }
* });
*
* var Square = ng.Class({
* extends: Shape,
* constructor: function(color, size) {
* Shape.call(this, color);
* this.size = size;
* }
* });
* ```
*/
export function Class(clsDef: ClassDefinition): Type {
var constructor = applyParams(
clsDef.hasOwnProperty('constructor') ? clsDef.constructor : undefined, 'constructor');
var proto = constructor.prototype;
if (clsDef.hasOwnProperty('extends')) {
if (isFunction(clsDef.extends)) {
(<Function>constructor).prototype = proto =
Object.create((<Function>clsDef.extends).prototype);
} else {
throw new Error(
`Class definition 'extends' property must be a constructor function was: ${stringify(clsDef.extends)}`);
}
}
for (var key in clsDef) {
if (key != 'extends' && key != 'prototype' && clsDef.hasOwnProperty(key)) {
proto[key] = applyParams(clsDef[key], key);
}
}
if (this && this.annotations instanceof Array) {
Reflect.defineMetadata('annotations', this.annotations, constructor);
}
return <Type>constructor;
}
var Reflect = global.Reflect;
if (!(Reflect && Reflect.getMetadata)) {
throw 'reflect-metadata shim is required when using class decorators';
}
export function makeDecorator(annotationCls, chainFn: (fn: Function) => void = null): (...args) =>
(cls: any) => any {
function DecoratorFactory(objOrType): (cls: any) => any {
var annotationInstance = new (<any>annotationCls)(objOrType);
if (this instanceof annotationCls) {
return annotationInstance;
} else {
var chainAnnotation =
isFunction(this) && this.annotations instanceof Array ? this.annotations : [];
chainAnnotation.push(annotationInstance);
var TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls) {
var annotations = Reflect.getMetadata('annotations', cls);
annotations = annotations || [];
annotations.push(annotationInstance);
Reflect.defineMetadata('annotations', annotations, cls);
return cls;
};
TypeDecorator.annotations = chainAnnotation;
TypeDecorator.Class = Class;
if (chainFn) chainFn(TypeDecorator);
return TypeDecorator;
}
}
DecoratorFactory.prototype = Object.create(annotationCls.prototype);
return DecoratorFactory;
}
export function makeParamDecorator(annotationCls): any {
function ParamDecoratorFactory(...args): any {
var annotationInstance = Object.create(annotationCls.prototype);
annotationCls.apply(annotationInstance, args);
if (this instanceof annotationCls) {
return annotationInstance;
} else {
(<any>ParamDecorator).annotation = annotationInstance;
return ParamDecorator;
}
function ParamDecorator(cls, unusedKey, index): any {
var parameters: Array<Array<any>> = Reflect.getMetadata('parameters', cls);
parameters = parameters || [];
// there might be gaps if some in between parameters do not have annotations.
// we pad with nulls.
while (parameters.length <= index) {
parameters.push(null);
}
parameters[index] = parameters[index] || [];
var annotationsForParam: Array<any> = parameters[index];
annotationsForParam.push(annotationInstance);
Reflect.defineMetadata('parameters', parameters, cls);
return cls;
}
}
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
return ParamDecoratorFactory;
}