@alterior/annotations 3.4.0 → 3.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,992 @@
1
+ /// <reference types="reflect-metadata" />
2
+
3
+ /**
4
+ * @alterior/annotations
5
+ * A class library for handling Typescript metadata decorators via "annotation" classes
6
+ *
7
+ * (C) 2017-2019 William Lahti
8
+ *
9
+ */
10
+
11
+ import { NotSupportedError } from '@alterior/common';
12
+
13
+ /**
14
+ * Represents an annotation which could be stored in the standard annotation lists
15
+ * on a class.
16
+ */
17
+ export interface IAnnotation {
18
+ $metadataName? : string;
19
+ }
20
+
21
+ // These are the properties on a class where annotation metadata is deposited
22
+ // when annotation decorators are executed. Note that these are intended to
23
+ // be compatible with Angular 6's model
24
+
25
+ export const ANNOTATIONS_KEY = '__annotations__';
26
+ export const CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY = '__parameters__';
27
+ export const PROPERTY_ANNOTATIONS_KEY = '__prop__metadata__';
28
+ export const METHOD_PARAMETER_ANNOTATIONS_KEY = '__parameter__metadata__';
29
+
30
+ /**
31
+ * Represents an Annotation subclass from the perspective of using it to
32
+ * construct itself by passing an options object.
33
+ */
34
+ interface AnnotationConstructor<AnnoT extends Annotation, TS extends any[]> {
35
+ new (...args : TS) : AnnoT;
36
+ getMetadataName();
37
+ }
38
+
39
+ /**
40
+ * Represents a decorator which accepts an Annotation's options object.
41
+ */
42
+ export interface AnnotationDecorator<TS extends any[]> {
43
+ (...args : TS) : (target, ...args) => void;
44
+ (...args : TS) : (target) => void;
45
+ (...args : TS) : (target, propertyKey : string) => void;
46
+ (...args : TS) : (target, propertyKey : string, descriptor : PropertyDescriptor) => void;
47
+ (...args : TS) : (target, propertyKey : string, index : number) => void;
48
+ }
49
+
50
+ export interface DecoratorSite {
51
+ type : 'class' | 'method' | 'property' | 'parameter';
52
+ target : any;
53
+ propertyKey? : string;
54
+ propertyDescriptor? : PropertyDescriptor;
55
+ index? : number;
56
+ }
57
+
58
+ export interface AnnotationDecoratorOptions<AnnoT, TS extends any[] = []> {
59
+ factory? : (target : DecoratorSite, ...args : TS) => AnnoT | void;
60
+ validTargets? : ('class' | 'property' | 'method' | 'parameter')[];
61
+ allowMultiple? : boolean;
62
+ }
63
+
64
+ /**
65
+ * Thrown when a caller attempts to decorate an annotation target when the
66
+ * annotation does not support that target.
67
+ */
68
+ export class AnnotationTargetError extends NotSupportedError {
69
+ constructor(annotationClass, invalidType : string, supportedTypes : string[], message? : string) {
70
+ super(message || `You cannot decorate a ${invalidType} with annotation ${annotationClass.name}. Valid targets: ${supportedTypes.join(', ')}`);
71
+
72
+ this._invalidType = invalidType;
73
+ this._annotationClass = annotationClass;
74
+ this._supportedTypes = supportedTypes;
75
+ }
76
+
77
+ private _invalidType : string;
78
+ private _annotationClass : Function;
79
+ private _supportedTypes : string[];
80
+
81
+ get invalidType() : string {
82
+ return this._invalidType;
83
+ }
84
+
85
+ get supportedTypes(): string[] {
86
+ return this._supportedTypes;
87
+ }
88
+
89
+ get annotationClass(): Function {
90
+ return this._annotationClass;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Create a decorator suitable for use along with an Annotation class.
96
+ * This is the core of the Annotation.decorator() method.
97
+ *
98
+ * @param ctor
99
+ * @param options
100
+ */
101
+ function makeDecorator<AnnoT extends Annotation, TS extends any[]>(
102
+ ctor : AnnotationConstructor<AnnoT, TS>,
103
+ options? : AnnotationDecoratorOptions<AnnoT, TS>
104
+ ): AnnotationDecorator<TS>
105
+ {
106
+ if (!ctor)
107
+ throw new Error(`Cannot create decorator: Passed class reference was undefined/null: This can happen due to circular dependencies.`);
108
+
109
+ let factory : (target : DecoratorSite, ...args : TS) => AnnoT | void = null;
110
+ let validTargets : string[] = null;
111
+ let allowMultiple = false;
112
+
113
+ if (options) {
114
+ if (options.factory)
115
+ factory = options.factory;
116
+ if (options.validTargets)
117
+ validTargets = options.validTargets as any;
118
+ if (options.allowMultiple)
119
+ allowMultiple = options.allowMultiple;
120
+ }
121
+
122
+ if (!factory)
123
+ factory = (target, ...args) => new ctor(...args);
124
+
125
+ if (!validTargets)
126
+ validTargets = ['class', 'method', 'property', 'parameter'];
127
+
128
+ return (...decoratorArgs : TS) => {
129
+ return (target, ...args) => {
130
+
131
+ // Note that checking the length is not enough, because for properties
132
+ // two arguments are passed, but the property descriptor is `undefined`.
133
+ // So we make sure that we have a valid property descriptor (args[1])
134
+
135
+ if (args.length === 2 && args[1] !== undefined) {
136
+ if (typeof args[1] === 'number') {
137
+ // Parameter decorator on a method or a constructor (methodName will be undefined)
138
+ let methodName : string = args[0];
139
+ let index : number = args[1];
140
+
141
+ if (!validTargets.includes('parameter'))
142
+ throw new AnnotationTargetError(ctor, 'parameter', validTargets);
143
+
144
+ if (!allowMultiple) {
145
+ let existingParamDecs = Annotations.getParameterAnnotations(target, methodName, true);
146
+ let existingParamAnnots = existingParamDecs[index] || [];
147
+ if (existingParamAnnots.find(x => x.$metadataName === ctor['$metadataName']))
148
+ throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
149
+ }
150
+
151
+ if (methodName) {
152
+ let annotation = factory({
153
+ type: 'parameter',
154
+ target,
155
+ propertyKey: methodName,
156
+ index
157
+ }, ...decoratorArgs);
158
+
159
+ if (!annotation)
160
+ return;
161
+
162
+ annotation.applyToParameter(target, methodName, index);
163
+ } else {
164
+ let annotation = factory({
165
+ type: 'parameter',
166
+ target,
167
+ index
168
+ }, ...decoratorArgs);
169
+
170
+ if (!annotation)
171
+ return;
172
+
173
+ annotation.applyToConstructorParameter(target, index);
174
+ }
175
+ } else {
176
+ // Method decorator
177
+ let methodName : string = args[0];
178
+ let descriptor : PropertyDescriptor = args[1];
179
+
180
+ if (!validTargets.includes('method'))
181
+ throw new AnnotationTargetError(ctor, 'method', validTargets);
182
+
183
+ if (!allowMultiple) {
184
+ let existingAnnots = Annotations.getMethodAnnotations(target, methodName, true);
185
+ if (existingAnnots.find(x => x.$metadataName === ctor['$metadataName']))
186
+ throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
187
+ }
188
+
189
+ let annotation = factory({
190
+ type: 'method',
191
+ target,
192
+ propertyKey: methodName,
193
+ propertyDescriptor: descriptor
194
+ }, ...decoratorArgs);
195
+
196
+ if (!annotation)
197
+ return;
198
+
199
+ annotation.applyToMethod(target, methodName);
200
+ }
201
+ } else if (args.length >= 1) {
202
+ // Property decorator
203
+ let propertyKey : string = args[0];
204
+
205
+ if (!validTargets.includes('property'))
206
+ throw new AnnotationTargetError(ctor, 'property', validTargets);
207
+
208
+ if (!allowMultiple) {
209
+ let existingAnnots = Annotations.getPropertyAnnotations(target, propertyKey, true);
210
+ if (existingAnnots.find(x => x.$metadataName === ctor['$metadataName']))
211
+ throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
212
+ }
213
+
214
+ let annotation = factory({
215
+ type: 'property',
216
+ target,
217
+ propertyKey
218
+ }, ...decoratorArgs);
219
+
220
+ if (!annotation)
221
+ return;
222
+
223
+ annotation.applyToProperty(target, propertyKey);
224
+
225
+ } else if (args.length === 0) {
226
+ // Class decorator
227
+
228
+ if (!validTargets.includes('class'))
229
+ throw new AnnotationTargetError(ctor, 'class', validTargets);
230
+
231
+ if (!allowMultiple) {
232
+ let existingAnnots = Annotations.getClassAnnotations(target);
233
+ if (existingAnnots.find(x => x.$metadataName === ctor['$metadataName']))
234
+ throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
235
+ }
236
+
237
+ let annotation = factory({
238
+ type: 'class',
239
+ target
240
+ }, ...decoratorArgs);
241
+
242
+ if (!annotation)
243
+ return;
244
+
245
+ annotation.applyToClass(target);
246
+ } else {
247
+ // Invalid, or future decorator types we don't support yet.
248
+ throw new Error(`Encountered unknown decorator invocation with ${args.length + 1} parameters.`);
249
+ }
250
+ };
251
+ }
252
+ }
253
+
254
+ export function MetadataName(name : string) {
255
+ return target => Object.defineProperty(target, '$metadataName', { value: name });
256
+ }
257
+
258
+ export interface MutatorDefinition {
259
+ invoke: (site : DecoratorSite) => void;
260
+ options?: AnnotationDecoratorOptions<void>;
261
+ }
262
+
263
+ /**
264
+ * Mutators are a way to define "mutation decorators" which in some way change the value
265
+ * of the elements they are applied to, as opposed to "annotation decorators", which primarily
266
+ * attach metadata.
267
+ *
268
+ * Create a mutator with Mutator.create().
269
+ */
270
+ export class Mutator {
271
+ /**
272
+ * Low-level method to ceate a new mutation decorator (mutator) based on the given function.
273
+ * Use `Mutator.define()` instead.
274
+ */
275
+ public static create(mutator : (target : DecoratorSite, ...args) => void, options? : AnnotationDecoratorOptions<void>) {
276
+ return Annotation.decorator(Object.assign({}, options || {}, {
277
+ factory: (target, ...args) => {
278
+ mutator(target, ...args);
279
+ }
280
+ }));
281
+ }
282
+
283
+ /**
284
+ * Define a new mutation decorator (mutator).
285
+ * This should be called and returned from a
286
+ * function definition. For example:
287
+ *
288
+ ```
289
+ function Name() {
290
+ return Mutator.define({
291
+ invoke(site) {
292
+ // ...
293
+ }
294
+ })
295
+ }
296
+ ```
297
+ *
298
+ * The `invoke()` function takes a DecoratorSite object which describes the particular
299
+ * invocation that is being run, and importantly, access to the property descriptor
300
+ * for the property being defined. If you wish to completely replace (or wrap) the
301
+ * default value of the property or method you are replacing, set the `value`
302
+ * property of the property descriptor with `site.propertyDescriptor.value`
303
+ *
304
+ * For example:
305
+ * ```
306
+ export function RunTwice() {
307
+ return Mutator.create(
308
+ site => {
309
+ let prop = site.propertyDescriptor;
310
+ let original = prop.value;
311
+ let replacement = function(...args) {
312
+ original.apply(this, args);
313
+ original.apply(this, args);
314
+ }
315
+ prop.value = replacement;
316
+ }
317
+ )
318
+ * ```
319
+ */
320
+ public static define(definition : MutatorDefinition) {
321
+ return this.create(definition.invoke, definition.options)();
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Represents a metadata annotation which can be applied to classes,
327
+ * constructor parameters, methods, properties, or method parameters
328
+ * via decorators.
329
+ *
330
+ * Custom annotations are defined as subclasses of this class.
331
+ * By convention, all custom annotation classes should have a name
332
+ * which ends in "Annotation" such as "NameAnnotation".
333
+ *
334
+ * To create a new annotation create a subclass of `Annotation`
335
+ * with a constructor that takes the parameters you are interested in
336
+ * storing, and save the appropriate information onto fields of the
337
+ * new instance. For your convenience, Annotation provides a default
338
+ * constructor which takes a map object and applies its properties onto
339
+ * the current instance, but you may replace it with a constructor that
340
+ * takes any arguments you wish.
341
+ *
342
+ * You may wish to add type safety to the default constructor parameter.
343
+ * To do so, override the constructor and define it:
344
+ *
345
+ ```
346
+ class XYZ extends Annotation {
347
+ constructor(
348
+ options : MyOptions
349
+ ) {
350
+ super(options);
351
+ }
352
+ }
353
+ ```
354
+ *
355
+ * Annotations are applied by using decorators.
356
+ * When you define a custom annotation, you must also define a
357
+ * custom decorator:
358
+ *
359
+ ```
360
+ const Name =
361
+ NameAnnotation.decorator();
362
+ ```
363
+ * You can then use that decorator:
364
+ ```
365
+ @Name()
366
+ class ABC {
367
+ // ...
368
+ }
369
+ ```
370
+ *
371
+ */
372
+ export class Annotation implements IAnnotation {
373
+ constructor(
374
+ props? : any
375
+ ) {
376
+ this.$metadataName = this.constructor['$metadataName'];
377
+ if (!this.$metadataName || !this.$metadataName.includes(':')) {
378
+ throw new Error(
379
+ `You must specify a metadata name for this annotation in the form of `
380
+ + ` 'mynamespace:myproperty'. You specified: '${this.$metadataName || '<none>'}'`
381
+ );
382
+ }
383
+
384
+ Object.assign(this, props || {});
385
+ }
386
+
387
+ readonly $metadataName : string;
388
+
389
+ toString() {
390
+ return `@${this.constructor.name}`;
391
+ }
392
+
393
+ static getMetadataName(): string {
394
+ if (!this['$metadataName'])
395
+ throw new Error(`Annotation subclass ${this.name} must have @MetadataName()`);
396
+
397
+ return this['$metadataName'];
398
+ }
399
+
400
+ /**
401
+ * Construct a decorator suitable for attaching annotations of the called type
402
+ * onto classes, constructor parameters, methods, properties, and parameters.
403
+ * Must be called while referencing the subclass of Annotation you wish to construct
404
+ * the decorator for. E.g., for FooAnnotation, call FooAnnotation.decorator().
405
+ *
406
+ * @param this The Annotation subclass for which the decorator is being constructed
407
+ * @param options Allows for specifying options which will modify the behavior of the decorator.
408
+ * See the DecoratorOptions documentation for more information.
409
+ */
410
+ public static decorator<T extends Annotation, TS extends any[]>(
411
+ this: AnnotationConstructor<T, TS>,
412
+ options? : AnnotationDecoratorOptions<T, TS>
413
+ ) {
414
+ if ((this as any) === Annotation) {
415
+ if (!options || !options.factory) {
416
+ throw new Error(`When calling Annotation.decorator() to create a mutator, you must specify a factory (or use Mutator.decorator())`);
417
+ }
418
+ }
419
+ return makeDecorator(this, options);
420
+ }
421
+
422
+ /**
423
+ * Clone this annotation instance into a new one. This is not a deep copy.
424
+ */
425
+ public clone(): this {
426
+ return Annotations.clone(this);
427
+ }
428
+
429
+ /**
430
+ * Apply this annotation to a given target.
431
+ * @param target
432
+ */
433
+ public applyToClass(target : any): this {
434
+ return Annotations.applyToClass(this, target);
435
+ }
436
+
437
+ /**
438
+ * Apply this annotation instance to the given property.
439
+ * @param target
440
+ * @param name
441
+ */
442
+ public applyToProperty(target : any, name : string): this {
443
+ return Annotations.applyToProperty(this, target, name);
444
+ }
445
+
446
+ /**
447
+ * Apply this annotation instance to the given method.
448
+ * @param target
449
+ * @param name
450
+ */
451
+ public applyToMethod(target : any, name : string): this {
452
+ return Annotations.applyToMethod(this, target, name);
453
+ }
454
+
455
+ /**
456
+ * Apply this annotation instance to the given method parameter.
457
+ * @param target
458
+ * @param name
459
+ * @param index
460
+ */
461
+ public applyToParameter(target : any, name : string, index : number): this {
462
+ return Annotations.applyToParameter(this, target, name, index);
463
+ }
464
+
465
+ /**
466
+ * Apply this annotation instance to the given constructor parameter.
467
+ * @param target
468
+ * @param name
469
+ * @param index
470
+ */
471
+ public applyToConstructorParameter(target : any, index : number): this {
472
+ return Annotations.applyToConstructorParameter(this, target, index);
473
+ }
474
+
475
+ /**
476
+ * Filter the given list of annotations for the ones which match this annotation class
477
+ * based on matching $metadataName.
478
+ *
479
+ * @param this
480
+ * @param annotations
481
+ */
482
+ public static filter<T extends Annotation, TS extends any[]>(
483
+ this : AnnotationConstructor<T, TS>,
484
+ annotations : IAnnotation[]
485
+ ) : T[] {
486
+ return annotations.filter(
487
+ x => x.$metadataName === this.getMetadataName()
488
+ ) as T[];
489
+ }
490
+
491
+ /**
492
+ * Get all instances of this annotation class attached to the given class.
493
+ * If called on a subclass of Annotation, it returns only annotations that match
494
+ * that subclass.
495
+ * @param this
496
+ * @param type The class to check
497
+ */
498
+ public static getAllForClass<T extends Annotation, TS extends any[]>(
499
+ this : AnnotationConstructor<T, TS>,
500
+ type : any
501
+ ): T[] {
502
+ return (Annotations.getClassAnnotations(type) as T[])
503
+ .filter(x => x.$metadataName === this.getMetadataName())
504
+ ;
505
+ }
506
+
507
+ /**
508
+ * Get a single instance of this annotation class attached to the given class.
509
+ * If called on a subclass of Annotation, it returns only annotations that match
510
+ * that subclass.
511
+ *
512
+ * @param this
513
+ * @param type
514
+ */
515
+ public static getForClass<T extends Annotation, TS extends any[]>(
516
+ this : AnnotationConstructor<T, TS>,
517
+ type : any
518
+ ): T {
519
+ return (this as any).getAllForClass(type)[0];
520
+ }
521
+
522
+ /**
523
+ * Get all instances of this annotation class attached to the given method.
524
+ * If called on a subclass of Annotation, it returns only annotations that match
525
+ * that subclass.
526
+ *
527
+ * @param this
528
+ * @param type The class where the method is defined
529
+ * @param methodName The name of the method to check
530
+ */
531
+ public static getAllForMethod<T extends Annotation, TS extends any[]>(
532
+ this : AnnotationConstructor<T, TS>,
533
+ type : any,
534
+ methodName : string
535
+ ): T[] {
536
+ return (Annotations.getMethodAnnotations(type, methodName) as T[])
537
+ .filter(x => x.$metadataName === this.getMetadataName())
538
+ ;
539
+ }
540
+
541
+ /**
542
+ * Get one instance of this annotation class attached to the given method.
543
+ * If called on a subclass of Annotation, it returns only annotations that match
544
+ * that subclass.
545
+ *
546
+ * @param this
547
+ * @param type The class where the method is defined
548
+ * @param methodName The name of the method to check
549
+ */
550
+ public static getForMethod<T extends Annotation, TS extends any[]>(
551
+ this : AnnotationConstructor<T, TS>,
552
+ type : any,
553
+ methodName : string
554
+ ): T {
555
+ return (this as any).getAllForMethod(type, methodName)[0];
556
+ }
557
+
558
+ /**
559
+ * Get all instances of this annotation class attached to the given property.
560
+ * If called on a subclass of Annotation, it returns only annotations that match
561
+ * that subclass.
562
+ *
563
+ * @param this
564
+ * @param type The class where the property is defined
565
+ * @param propertyName The name of the property to check
566
+ */
567
+ public static getAllForProperty<T extends Annotation, TS extends any[]>(
568
+ this : AnnotationConstructor<T, TS>,
569
+ type : any,
570
+ propertyName : string
571
+ ): T[] {
572
+ return (Annotations.getPropertyAnnotations(type, propertyName) as T[])
573
+ .filter(x => x.$metadataName === this.getMetadataName())
574
+ ;
575
+ }
576
+
577
+ /**
578
+ * Get one instance of this annotation class attached to the given property.
579
+ * If called on a subclass of Annotation, it returns only annotations that match
580
+ * that subclass.
581
+ *
582
+ * @param this
583
+ * @param type The class where the property is defined
584
+ * @param propertyName The name of the property to check
585
+ */
586
+ public static getForProperty<T extends Annotation, TS extends any[]>(
587
+ this : AnnotationConstructor<T, TS>,
588
+ type : any,
589
+ propertyName : string
590
+ ): T {
591
+ return (this as any).getAllForProperty(type, propertyName)[0];
592
+ }
593
+
594
+ /**
595
+ * Get all instances of this annotation class attached to the parameters of the given method.
596
+ * If called on a subclass of Annotation, it returns only annotations that match
597
+ * that subclass.
598
+ *
599
+ * @param this
600
+ * @param type The class where the method is defined
601
+ * @param methodName The name of the method where parameter annotations should be checked for
602
+ */
603
+ public static getAllForParameters<T extends Annotation, TS extends any[]>(
604
+ this : AnnotationConstructor<T, TS>,
605
+ type : any,
606
+ methodName : string
607
+ ): T[][] {
608
+ return (Annotations.getParameterAnnotations(type, methodName) as T[][])
609
+ .map(set => (set || []).filter(x => (this as any) === Annotation ? true : (x.$metadataName === this.getMetadataName())))
610
+ ;
611
+ }
612
+
613
+ /**
614
+ * Get all instances of this annotation class attached to the parameters of the constructor
615
+ * for the given class.
616
+ * If called on a subclass of Annotation, it returns only annotations that match
617
+ * that subclass.
618
+ *
619
+ * @param this
620
+ * @param type The class where constructor parameter annotations should be checked for
621
+ */
622
+ public static getAllForConstructorParameters<T extends Annotation, TS extends any[]>(
623
+ this : AnnotationConstructor<T, TS>,
624
+ type : any
625
+ ): T[][] {
626
+
627
+ let finalSet = new Array(<any>type.length).fill(undefined);
628
+ let annotations = (Annotations.getConstructorParameterAnnotations(type) as T[][])
629
+ .map(set => (set || []).filter(x => (this as any) === Annotation ? true : (x.$metadataName === this.getMetadataName())))
630
+ ;
631
+
632
+ for (let i = 0, max = annotations.length; i < max; ++i)
633
+ finalSet[i] = annotations[i];
634
+
635
+ return finalSet;
636
+ }
637
+ }
638
+
639
+ /**
640
+ * A helper class for managing annotations
641
+ */
642
+ export class Annotations {
643
+
644
+ /**
645
+ * Copy the annotations defined for one class onto another.
646
+ * @param from The class to copy annotations from
647
+ * @param to The class to copy annotations to
648
+ */
649
+ public static copyClassAnnotations(from : Function, to : Function) {
650
+ let annotations = Annotations.getClassAnnotations(from);
651
+ annotations.forEach(x => Annotations.applyToClass(x, to));
652
+ }
653
+
654
+ /**
655
+ * Apply this annotation to a given target.
656
+ * @param target
657
+ */
658
+ public static applyToClass<T extends IAnnotation>(annotation : T, target : any): T {
659
+ let list = this.getOrCreateListForClass(target);
660
+ let clone = this.clone(annotation);
661
+ list.push(clone);
662
+
663
+ if (Reflect.getOwnMetadata) {
664
+ let reflectedAnnotations = Reflect.getOwnMetadata('annotations', target) || [];
665
+ reflectedAnnotations.push({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
666
+ Reflect.defineMetadata('annotations', reflectedAnnotations, target);
667
+ }
668
+
669
+ return clone;
670
+ }
671
+
672
+ /**
673
+ * Apply this annotation instance to the given property.
674
+ * @param target
675
+ * @param name
676
+ */
677
+ public static applyToProperty<T extends IAnnotation>(annotation : T, target : any, name : string): T {
678
+ let list = this.getOrCreateListForProperty(target, name);
679
+ let clone = this.clone(annotation);
680
+ list.push(clone);
681
+
682
+ if (Reflect.getOwnMetadata) {
683
+ let reflectedAnnotations = Reflect.getOwnMetadata('propMetadata', target, name) || [];
684
+ reflectedAnnotations.push({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
685
+ Reflect.defineMetadata('propMetadata', reflectedAnnotations, target, name);
686
+ }
687
+
688
+ return clone;
689
+ }
690
+
691
+ /**
692
+ * Apply this annotation instance to the given method.
693
+ * @param target
694
+ * @param name
695
+ */
696
+ public static applyToMethod<T extends IAnnotation>(annotation : T, target : any, name : string): T {
697
+ let list = this.getOrCreateListForMethod(target, name);
698
+ let clone = Annotations.clone(annotation);
699
+ list.push(clone);
700
+
701
+ if (Reflect.getOwnMetadata && target.constructor) {
702
+ const meta = Reflect.getOwnMetadata('propMetadata', target.constructor) || {};
703
+ meta[name] = (meta.hasOwnProperty(name) && meta[name]) || [];
704
+ meta[name].unshift({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
705
+ Reflect.defineMetadata('propMetadata', meta, target.constructor);
706
+ }
707
+
708
+ return clone;
709
+ }
710
+
711
+ /**
712
+ * Apply this annotation instance to the given method parameter.
713
+ * @param target
714
+ * @param name
715
+ * @param index
716
+ */
717
+ public static applyToParameter<T extends IAnnotation>(annotation : T, target : any, name : string, index : number): T {
718
+ let list = this.getOrCreateListForMethodParameters(target, name);
719
+ while (list.length < index)
720
+ list.push(null);
721
+
722
+ let paramList = list[index] || [];
723
+ let clone = this.clone(annotation);
724
+ paramList.push(clone);
725
+ list[index] = paramList;
726
+
727
+ return clone;
728
+ }
729
+
730
+ /**
731
+ * Apply this annotation instance to the given constructor parameter.
732
+ * @param target
733
+ * @param name
734
+ * @param index
735
+ */
736
+ public static applyToConstructorParameter<T extends IAnnotation>(annotation : T, target : any, index : number): T {
737
+ let list = this.getOrCreateListForConstructorParameters(target);
738
+ while (list.length < index)
739
+ list.push(null);
740
+
741
+ let paramList = list[index] || [];
742
+ let clone = this.clone(annotation);
743
+ paramList.push(clone);
744
+ list[index] = paramList;
745
+
746
+ if (Reflect.getOwnMetadata) {
747
+ let parameterList = Reflect.getOwnMetadata('parameters', target) || [];
748
+
749
+ while (parameterList.length < index)
750
+ parameterList.push(null);
751
+
752
+ let parameterAnnotes = parameterList[index] || [];
753
+ parameterAnnotes.push(clone);
754
+ parameterList[index] = parameterAnnotes;
755
+
756
+ Reflect.defineMetadata('parameters', parameterList, target);
757
+ }
758
+
759
+ return clone;
760
+ }
761
+
762
+ /**
763
+ * Clone the given Annotation instance into a new instance. This is not
764
+ * a deep copy.
765
+ *
766
+ * @param annotation
767
+ */
768
+ public static clone<T extends IAnnotation>(annotation : T): T {
769
+ if (!annotation)
770
+ return annotation;
771
+
772
+ return Object.assign(Object.create(Object.getPrototypeOf(annotation)), annotation);
773
+ }
774
+
775
+ /**
776
+ * Get all annotations (including from Angular and other compatible
777
+ * frameworks).
778
+ *
779
+ * @param target The target to fetch annotations for
780
+ */
781
+ public static getClassAnnotations(target : any): IAnnotation[] {
782
+ return (this.getListForClass(target) || [])
783
+ .map(x => this.clone(x));
784
+ }
785
+
786
+ /**
787
+ * Get all annotations (including from Angular and other compatible
788
+ * frameworks).
789
+ *
790
+ * @param target The target to fetch annotations for
791
+ */
792
+ public static getMethodAnnotations(target : any, methodName : string, isStatic : boolean = false): IAnnotation[] {
793
+ return (this.getListForMethod(isStatic ? target : target.prototype, methodName) || [])
794
+ .map(x => this.clone(x));
795
+ }
796
+
797
+ /**
798
+ * Get all annotations (including from Angular and other compatible
799
+ * frameworks).
800
+ *
801
+ * @param target The target to fetch annotations for
802
+ */
803
+ public static getPropertyAnnotations(target : any, methodName : string, isStatic : boolean = false): IAnnotation[] {
804
+ return (this.getListForProperty(isStatic ? target : target.prototype, methodName) || [])
805
+ .map(x => this.clone(x));
806
+ }
807
+
808
+ /**
809
+ * Get the annotations defined on the parameters of the given method of the given
810
+ * class.
811
+ *
812
+ * @param type
813
+ * @param methodName
814
+ * @param isStatic Whether `type` itself (isStatic = true), or `type.prototype` (isStatic = false) should be the target.
815
+ * Note that passing true may indicate that the passed `type` is already the prototype of a class.
816
+ */
817
+ public static getParameterAnnotations(type : any, methodName : string, isStatic : boolean = false): IAnnotation[][] {
818
+ return (this.getListForMethodParameters(isStatic ? type : type.prototype, methodName) || [])
819
+ .map(set => set ? set.map(anno => this.clone(anno)) : []);
820
+ }
821
+
822
+ /**
823
+ * Get the annotations defined on the parameters of the given method of the given
824
+ * class.
825
+ *
826
+ * @param type
827
+ * @param methodName
828
+ */
829
+ public static getConstructorParameterAnnotations(type : any): IAnnotation[][] {
830
+ return (this.getListForConstructorParameters(type) || [])
831
+ .map(set => set ? set.map(anno => this.clone(anno)) : []);
832
+ }
833
+
834
+ /**
835
+ * Get a list of annotations for the given class.
836
+ * @param target
837
+ */
838
+ private static getListForClass(target : Object): IAnnotation[] {
839
+ if (!target)
840
+ return [];
841
+
842
+ let combinedSet = [];
843
+
844
+ let superclass = Object.getPrototypeOf(target);
845
+
846
+ if (superclass && superclass !== Function)
847
+ combinedSet = combinedSet.concat(this.getListForClass(superclass));
848
+
849
+ if (target.hasOwnProperty(ANNOTATIONS_KEY))
850
+ combinedSet = combinedSet.concat(target[ANNOTATIONS_KEY] || []);
851
+
852
+ return combinedSet;
853
+ }
854
+
855
+ /**
856
+ * Get a list of own annotations for the given class, or create that list.
857
+ * @param target
858
+ */
859
+ private static getOrCreateListForClass(target : Object): IAnnotation[] {
860
+ if (!target.hasOwnProperty(ANNOTATIONS_KEY))
861
+ Object.defineProperty(target, ANNOTATIONS_KEY, { enumerable: false, value: [] });
862
+ return target[ANNOTATIONS_KEY];
863
+ }
864
+
865
+ /**
866
+ * Gets a map of the annotations defined on all properties of the given class/function. To get the annotations of instance fields,
867
+ * make sure to use `Class.prototype`, otherwise static annotations are returned.
868
+ */
869
+ public static getMapForClassProperties(target : Object, mapToPopulate? : Record<string,IAnnotation[]>): Record<string,IAnnotation[]> {
870
+ let combinedSet = mapToPopulate || {};
871
+ if (!target || target === Function)
872
+ return combinedSet;
873
+
874
+ this.getMapForClassProperties(Object.getPrototypeOf(target), combinedSet);
875
+
876
+ if (target.hasOwnProperty(PROPERTY_ANNOTATIONS_KEY)) {
877
+ let ownMap : Record<string,IAnnotation[]> = target[PROPERTY_ANNOTATIONS_KEY] || {};
878
+ for (let key of Object.keys(ownMap))
879
+ combinedSet[key] = (combinedSet[key] || []).concat(ownMap[key]);
880
+ }
881
+
882
+ return combinedSet;
883
+ }
884
+
885
+ private static getOrCreateMapForClassProperties(target : Object): Record<string,IAnnotation[]> {
886
+ if (!target.hasOwnProperty(PROPERTY_ANNOTATIONS_KEY))
887
+ Object.defineProperty(target, PROPERTY_ANNOTATIONS_KEY, { enumerable: false, value: [] });
888
+ return target[PROPERTY_ANNOTATIONS_KEY];
889
+ }
890
+
891
+ private static getListForProperty(target : any, propertyKey : string): IAnnotation[] {
892
+ let map = this.getMapForClassProperties(target);
893
+
894
+ if (!map)
895
+ return null;
896
+
897
+ return map[propertyKey];
898
+ }
899
+
900
+ private static getOrCreateListForProperty(target : any, propertyKey : string): IAnnotation[] {
901
+ let map = this.getOrCreateMapForClassProperties(target);
902
+ if (!map[propertyKey])
903
+ map[propertyKey] = [];
904
+
905
+ return map[propertyKey];
906
+ }
907
+
908
+ private static getOrCreateListForMethod(target : any, methodName : string): IAnnotation[] {
909
+ return this.getOrCreateListForProperty(target, methodName);
910
+ }
911
+
912
+ private static getListForMethod(target : any, methodName : string): IAnnotation[] {
913
+ return this.getListForProperty(target, methodName);
914
+ }
915
+
916
+ /**
917
+ * Get a map of the annotations defined on all parameters of all methods of the given class/function.
918
+ * To get instance methods, make sure to pass `Class.prototype`, otherwise the results are for static fields.
919
+ */
920
+ public static getMapForMethodParameters(target : Object, mapToPopulate? : Record<string,IAnnotation[][]>): Record<string,IAnnotation[][]> {
921
+ let combinedMap = mapToPopulate || {};
922
+
923
+ if (!target || target === Function)
924
+ return combinedMap;
925
+
926
+ // superclass/prototype
927
+ this.getMapForMethodParameters(Object.getPrototypeOf(target), combinedMap);
928
+
929
+ if (target.hasOwnProperty(METHOD_PARAMETER_ANNOTATIONS_KEY)) {
930
+ let ownMap : Record<string,IAnnotation[][]> = target[METHOD_PARAMETER_ANNOTATIONS_KEY] || {};
931
+
932
+ for (let methodName of Object.keys(ownMap)) {
933
+ let parameters = ownMap[methodName];
934
+ let combinedMethodMap = combinedMap[methodName] || [];
935
+
936
+ for (let i = 0, max = parameters.length; i < max; ++i) {
937
+ combinedMethodMap[i] = (combinedMethodMap[i] || []).concat(parameters[i] || []);
938
+ }
939
+
940
+ combinedMap[methodName] = combinedMethodMap;
941
+ }
942
+ }
943
+
944
+ return combinedMap;
945
+ }
946
+
947
+ private static getOrCreateMapForMethodParameters(target : Object): Record<string, IAnnotation[][]> {
948
+ if (!target.hasOwnProperty(METHOD_PARAMETER_ANNOTATIONS_KEY))
949
+ Object.defineProperty(target, METHOD_PARAMETER_ANNOTATIONS_KEY, { enumerable: false, value: {} });
950
+ return target[METHOD_PARAMETER_ANNOTATIONS_KEY];
951
+ }
952
+
953
+ private static getListForMethodParameters(target : any, methodName : string): IAnnotation[][] {
954
+ let map = this.getMapForMethodParameters(target);
955
+
956
+ if (!map)
957
+ return null;
958
+
959
+ return map[methodName];
960
+ }
961
+
962
+ private static getOrCreateListForMethodParameters(target : any, methodName : string): IAnnotation[][] {
963
+ let map = this.getOrCreateMapForMethodParameters(target);
964
+ if (!map[methodName])
965
+ map[methodName] = [];
966
+
967
+ return map[methodName];
968
+ }
969
+
970
+ private static getOrCreateListForConstructorParameters(target : any): IAnnotation[][] {
971
+ if (!target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY])
972
+ Object.defineProperty(target, CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY, { enumerable: false, value: [] });
973
+ return target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY];
974
+ }
975
+
976
+ private static getListForConstructorParameters(target : any): IAnnotation[][] {
977
+ return target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY];
978
+ }
979
+ }
980
+
981
+ /**
982
+ * An annotation for attaching a label to a programmatic element.
983
+ * Can be queried with LabelAnnotation.getForClass() for example.
984
+ */
985
+ @MetadataName('alterior:Label')
986
+ export class LabelAnnotation extends Annotation {
987
+ constructor(readonly text : string) {
988
+ super();
989
+ }
990
+ }
991
+
992
+ export const Label = LabelAnnotation.decorator();