///
/**
* @alterior/annotations
* A class library for handling Typescript metadata decorators via "annotation" classes
*
* (C) 2017-2019 William Lahti
*
*/
import { NotSupportedError } from '@alterior/common';
/**
* Represents an annotation which could be stored in the standard annotation lists
* on a class.
*/
export interface IAnnotation {
$metadataName?: string;
}
// These are the properties on a class where annotation metadata is deposited
// when annotation decorators are executed. Note that these are intended to
// be compatible with Angular 6's model
export const ANNOTATIONS_KEY = '__annotations__';
export const CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY = '__parameters__';
export const PROPERTY_ANNOTATIONS_KEY = '__prop__metadata__';
export const METHOD_PARAMETER_ANNOTATIONS_KEY = '__parameter__metadata__';
/**
* Represents an Annotation subclass from the perspective of using it to
* construct itself by passing an options object.
*/
interface AnnotationConstructor {
new(...args: TS): AnnoT;
getMetadataName(): string;
}
export type AnnotationClassDecorator = (...args: TS) => ((target: any) => void);
export type AnnotationPropertyDecorator = (...args: TS) => ((target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => void);
export type AnnotationMethodDecorator = (...args: TS) => ((target: any, propertyKey: string | symbol) => void);
export type AnnotationParameterDecorator = (...args: TS) => ((target: any, propertyKey: string | symbol, index: number) => void);
// (...args: TS): (target, ...args) => void;
type UnionToIntersection =
(U extends any ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never
type DecoratorTypeUnionForValidTargets =
Targets extends 'class' ? ClassDecorator
: Targets extends 'method' ? MethodDecorator
: Targets extends 'property' ? PropertyDecorator
: Targets extends 'parameter' ? ParameterDecorator
: never;
;
type DecoratorTypeForValidTargets = UnionToIntersection>;
/**
* Represents a decorator which accepts an Annotation's options object.
*/
export type AnnotationDecorator = (...args: TS) => Decorator;
export type Decorator = ClassDecorator &
PropertyDecorator &
MethodDecorator &
ParameterDecorator;
export type DecoratorSite = ClassDecoratorSite | MethodDecoratorSite | PropertyDecoratorSite | ParameterDecoratorSite;
export interface ClassDecoratorSite {
type: 'class';
target: any;
}
export interface MethodDecoratorSite {
type: 'method';
target: any;
propertyKey: string | symbol;
propertyDescriptor: PropertyDescriptor;
}
export interface PropertyDecoratorSite {
type: 'property';
target: any;
propertyKey: string | symbol;
/**
* Field properties may not have a property descriptor, for instance `class` does not create one by default unless
* you specify `get` or `set` for the property.
*/
propertyDescriptor?: PropertyDescriptor;
}
export interface ParameterDecoratorSite {
type: 'parameter';
target: any;
/**
* The property key of the method whose parameter is being decorated.
* If this is undefined, it means we are looking at a constructor parameter.
*/
propertyKey?: string | symbol;
index: number;
}
export type AnnotationDecoratorTarget = 'class' | 'property' | 'method' | 'parameter';
export interface AnnotationDecoratorOptions {
factory?: (target: DecoratorSite, ...args: TS) => AnnoT | void;
validTargets?: AnnotationDecoratorTarget[];
allowMultiple?: boolean;
}
/**
* Thrown when a caller attempts to decorate an annotation target when the
* annotation does not support that target.
*/
export class AnnotationTargetError extends NotSupportedError {
constructor(annotationClass: Function, invalidType: string, supportedTypes: string[], message?: string) {
super(message || `You cannot decorate a ${invalidType} with annotation ${annotationClass.name}. Valid targets: ${supportedTypes.join(', ')}`);
this._invalidType = invalidType;
this._annotationClass = annotationClass;
this._supportedTypes = supportedTypes;
}
private _invalidType: string;
private _annotationClass: Function;
private _supportedTypes: string[];
get invalidType(): string {
return this._invalidType;
}
get supportedTypes(): string[] {
return this._supportedTypes;
}
get annotationClass(): Function {
return this._annotationClass;
}
}
/**
* Create a decorator suitable for use along with an Annotation class.
* This is the core of the Annotation.decorator() method.
*
* @param ctor
* @param options
*/
function makeDecorator(
ctor: AnnotationConstructor,
options?: AnnotationDecoratorOptions
): AnnotationDecorator {
if (!ctor)
throw new Error(`Cannot create decorator: Passed class reference was undefined/null: This can happen due to circular dependencies.`);
let factory: ((target: DecoratorSite, ...args: TS) => AnnoT | void) | null = null;
let validTargets: string[] | null = null;
let allowMultiple = false;
if (options) {
if (options.factory)
factory = options.factory;
if (options.validTargets)
validTargets = options.validTargets as any;
if (options.allowMultiple)
allowMultiple = options.allowMultiple;
}
if (!factory)
factory = (target, ...args) => new ctor(...args);
if (!validTargets)
validTargets = ['class', 'method', 'property', 'parameter'];
return (...decoratorArgs: TS) => {
return (target: any, ...args: any[]) => {
// Note that checking the length is not enough, because for properties
// two arguments are passed, but the property descriptor is `undefined`.
// So we make sure that we have a valid property descriptor (args[1])
if (args.length === 2 && args[1] !== undefined) {
if (typeof args[1] === 'number') {
// Parameter decorator on a method or a constructor (methodName will be undefined)
let methodName: string = args[0];
let index: number = args[1];
if (!validTargets.includes('parameter'))
throw new AnnotationTargetError(ctor, 'parameter', validTargets);
if (!allowMultiple) {
let existingParamDecs = Annotations.getParameterAnnotations(target, methodName, true);
let existingParamAnnots = existingParamDecs[index] || [];
if (existingParamAnnots.find(x => x.$metadataName === (ctor as any)['$metadataName']))
throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
}
if (methodName) {
let annotation = factory({
type: 'parameter',
target,
propertyKey: methodName,
index
}, ...decoratorArgs);
if (!annotation)
return;
annotation.applyToParameter(target, methodName, index);
} else {
let annotation = factory({
type: 'parameter',
propertyKey: undefined,
target,
index
}, ...decoratorArgs);
if (!annotation)
return;
annotation.applyToConstructorParameter(target, index);
}
} else {
// Method decorator
let methodName: string = args[0];
let descriptor: PropertyDescriptor = args[1];
if (!validTargets.includes('method'))
throw new AnnotationTargetError(ctor, 'method', validTargets);
if (!allowMultiple) {
let existingAnnots = Annotations.getMethodAnnotations(target, methodName, true);
if (existingAnnots.find(x => x.$metadataName === (ctor as any)['$metadataName']))
throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
}
let annotation = factory({
type: 'method',
target,
propertyKey: methodName,
propertyDescriptor: descriptor
}, ...decoratorArgs);
if (!annotation)
return;
annotation.applyToMethod(target, methodName);
}
} else if (args.length >= 1) {
// Property decorator
let propertyKey: string = args[0];
if (!validTargets.includes('property'))
throw new AnnotationTargetError(ctor, 'property', validTargets);
if (!allowMultiple) {
let existingAnnots = Annotations.getPropertyAnnotations(target, propertyKey, true);
if (existingAnnots.find(x => x.$metadataName === (ctor as any)['$metadataName']))
throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
}
let annotation = factory({
type: 'property',
target,
propertyKey
}, ...decoratorArgs);
if (!annotation)
return;
annotation.applyToProperty(target, propertyKey);
} else if (args.length === 0) {
// Class decorator
if (!validTargets.includes('class'))
throw new AnnotationTargetError(ctor, 'class', validTargets);
if (!allowMultiple) {
let existingAnnots = Annotations.getClassAnnotations(target);
if (existingAnnots.find(x => x.$metadataName === (ctor as any)['$metadataName']))
throw new Error(`Annotation ${ctor.name} can only be applied to an element once.`);
}
let annotation = factory({
type: 'class',
target
}, ...decoratorArgs);
if (!annotation)
return;
annotation.applyToClass(target);
} else {
// Invalid, or future decorator types we don't support yet.
throw new Error(`Encountered unknown decorator invocation with ${args.length + 1} parameters.`);
}
};
}
}
export function MetadataName(name: string) {
return (target: any) => Object.defineProperty(target, '$metadataName', { value: name });
}
/**
* Represents a metadata annotation which can be applied to classes,
* constructor parameters, methods, properties, or method parameters
* via decorators.
*
* Custom annotations are defined as subclasses of this class.
* By convention, all custom annotation classes should have a name
* which ends in "Annotation" such as "NameAnnotation".
*
* To create a new annotation create a subclass of `Annotation`
* with a constructor that takes the parameters you are interested in
* storing, and save the appropriate information onto fields of the
* new instance. For your convenience, Annotation provides a default
* constructor which takes a map object and applies its properties onto
* the current instance, but you may replace it with a constructor that
* takes any arguments you wish.
*
* You may wish to add type safety to the default constructor parameter.
* To do so, override the constructor and define it:
*
```
class XYZ extends Annotation {
constructor(
options : MyOptions
) {
super(options);
}
}
```
*
* Annotations are applied by using decorators.
* When you define a custom annotation, you must also define a
* custom decorator:
*
```
const Name =
NameAnnotation.decorator();
```
* You can then use that decorator:
```
@Name()
class ABC {
// ...
}
```
*
*/
export class Annotation implements IAnnotation {
constructor(
props?: any
) {
this.$metadataName = (this.constructor as any)['$metadataName'];
if (!this.$metadataName || !this.$metadataName.includes(':')) {
throw new Error(
`You must specify a metadata name for this annotation in the form of `
+ ` 'mynamespace:myproperty'. You specified: '${this.$metadataName || ''}'`
);
}
Object.assign(this, props || {});
}
readonly $metadataName: string;
toString() {
return `@${this.constructor.name}`;
}
static getMetadataName(): string {
if (!(this as any)['$metadataName'])
throw new Error(`Annotation subclass ${this.name} must have @MetadataName()`);
return (this as any)['$metadataName'];
}
/**
* Construct a decorator suitable for attaching annotations of the called type
* onto classes, constructor parameters, methods, properties, and parameters.
* Must be called while referencing the subclass of Annotation you wish to construct
* the decorator for. E.g., for FooAnnotation, call FooAnnotation.decorator().
*
* @param this The Annotation subclass for which the decorator is being constructed
* @param options Allows for specifying options which will modify the behavior of the decorator.
* See the DecoratorOptions documentation for more information.
*/
public static decorator<
T extends Annotation,
TS extends any[],
U extends AnnotationDecoratorTarget
>(
this: AnnotationConstructor,
options?: Exclude, 'validTargets'> & { validTargets: U[] }
): (...args: TS) => DecoratorTypeForValidTargets;
public static decorator(this: AnnotationConstructor, options?: AnnotationDecoratorOptions): AnnotationDecorator;
public static decorator(this: AnnotationConstructor, options?: AnnotationDecoratorOptions): AnnotationDecorator {
if ((this as any) === Annotation) {
if (!options || !options.factory) {
throw new Error(`When calling Annotation.decorator() to create a mutator, you must specify a factory (or use Mutator.decorator())`);
}
}
return makeDecorator(this, options);
}
/**
* Clone this annotation instance into a new one. This is not a deep copy.
*/
public clone(): this {
return Annotations.clone(this);
}
/**
* Apply this annotation to a given target.
* @param target
*/
public applyToClass(target: any): this {
return Annotations.applyToClass(this, target);
}
/**
* Apply this annotation instance to the given property.
* @param target
* @param name
*/
public applyToProperty(target: any, name: string): this {
return Annotations.applyToProperty(this, target, name);
}
/**
* Apply this annotation instance to the given method.
* @param target
* @param name
*/
public applyToMethod(target: any, name: string): this {
return Annotations.applyToMethod(this, target, name);
}
/**
* Apply this annotation instance to the given method parameter.
* @param target
* @param name
* @param index
*/
public applyToParameter(target: any, name: string, index: number): this {
return Annotations.applyToParameter(this, target, name, index);
}
/**
* Apply this annotation instance to the given constructor parameter.
* @param target
* @param name
* @param index
*/
public applyToConstructorParameter(target: any, index: number): this {
return Annotations.applyToConstructorParameter(this, target, index);
}
/**
* Filter the given list of annotations for the ones which match this annotation class
* based on matching $metadataName.
*
* @param this
* @param annotations
*/
public static filter(
this: AnnotationConstructor,
annotations: IAnnotation[]
): T[] {
return annotations.filter(
x => x.$metadataName === this.getMetadataName()
) as T[];
}
/**
* Get all instances of this annotation class attached to the given class.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
* @param this
* @param type The class to check
*/
public static getAllForClass(
this: AnnotationConstructor,
type: any
): T[] {
return (Annotations.getClassAnnotations(type) as T[])
.filter(x => x.$metadataName === this.getMetadataName())
;
}
/**
* Get a single instance of this annotation class attached to the given class.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type
*/
public static getForClass(
this: AnnotationConstructor,
type: any
): T {
return (this as any).getAllForClass(type)[0];
}
/**
* Get all instances of this annotation class attached to the given method.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where the method is defined
* @param methodName The name of the method to check
*/
public static getAllForMethod(
this: AnnotationConstructor,
type: any,
methodName: string
): T[] {
return (Annotations.getMethodAnnotations(type, methodName) as T[])
.filter(x => x.$metadataName === this.getMetadataName())
;
}
/**
* Get one instance of this annotation class attached to the given method.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where the method is defined
* @param methodName The name of the method to check
*/
public static getForMethod(
this: AnnotationConstructor,
type: any,
methodName: string
): T {
return (this as any).getAllForMethod(type, methodName)[0];
}
/**
* Get all instances of this annotation class attached to the given property.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where the property is defined
* @param propertyName The name of the property to check
*/
public static getAllForProperty(
this: AnnotationConstructor,
type: any,
propertyName: string
): T[] {
return (Annotations.getPropertyAnnotations(type, propertyName) as T[])
.filter(x => x.$metadataName === this.getMetadataName())
;
}
/**
* Get one instance of this annotation class attached to the given property.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where the property is defined
* @param propertyName The name of the property to check
*/
public static getForProperty(
this: AnnotationConstructor,
type: any,
propertyName: string
): T {
return (this as any).getAllForProperty(type, propertyName)[0];
}
/**
* Get all instances of this annotation class attached to the parameters of the given method.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where the method is defined
* @param methodName The name of the method where parameter annotations should be checked for
*/
public static getAllForParameters(
this: AnnotationConstructor,
type: any,
methodName: string
): T[][] {
return (Annotations.getParameterAnnotations(type, methodName) as T[][])
.map(set => (set || []).filter(x => (this as any) === Annotation ? true : (x.$metadataName === this.getMetadataName())))
;
}
/**
* Get all instances of this annotation class attached to the parameters of the constructor
* for the given class.
* If called on a subclass of Annotation, it returns only annotations that match
* that subclass.
*
* @param this
* @param type The class where constructor parameter annotations should be checked for
*/
public static getAllForConstructorParameters(
this: AnnotationConstructor,
type: any
): T[][] {
let finalSet = new Array(type.length).fill(undefined);
let annotations = (Annotations.getConstructorParameterAnnotations(type) as T[][])
.map(set => (set || []).filter(x => (this as any) === Annotation ? true : (x.$metadataName === this.getMetadataName())))
;
for (let i = 0, max = annotations.length; i < max; ++i)
finalSet[i] = annotations[i];
return finalSet;
}
}
/**
* A helper class for managing annotations
*/
export class Annotations {
/**
* Copy the annotations defined for one class onto another.
* @param from The class to copy annotations from
* @param to The class to copy annotations to
*/
public static copyClassAnnotations(from: Function, to: Function) {
let annotations = Annotations.getClassAnnotations(from);
annotations.forEach(x => Annotations.applyToClass(x, to));
}
/**
* Apply this annotation to a given target.
* @param target
*/
public static applyToClass(annotation: T, target: any): T {
let list = this.getOrCreateListForClass(target);
let clone = this.clone(annotation);
list.push(clone);
if (Reflect.getOwnMetadata) {
let reflectedAnnotations = Reflect.getOwnMetadata('annotations', target) || [];
reflectedAnnotations.push({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
Reflect.defineMetadata('annotations', reflectedAnnotations, target);
}
return clone;
}
/**
* Apply this annotation instance to the given property.
* @param target
* @param name
*/
public static applyToProperty(annotation: T, target: any, name: string): T {
let list = this.getOrCreateListForProperty(target, name);
let clone = this.clone(annotation);
list.push(clone);
if (Reflect.getOwnMetadata) {
let reflectedAnnotations = Reflect.getOwnMetadata('propMetadata', target, name) || [];
reflectedAnnotations.push({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
Reflect.defineMetadata('propMetadata', reflectedAnnotations, target, name);
}
return clone;
}
/**
* Apply this annotation instance to the given method.
* @param target
* @param name
*/
public static applyToMethod(annotation: T, target: any, name: string): T {
let list = this.getOrCreateListForMethod(target, name);
let clone = Annotations.clone(annotation);
list.push(clone);
if (Reflect.getOwnMetadata && target.constructor) {
const meta = Reflect.getOwnMetadata('propMetadata', target.constructor) || {};
meta[name] = (meta.hasOwnProperty(name) && meta[name]) || [];
meta[name].unshift({ toString() { return `${clone.$metadataName}`; }, annotation: clone });
Reflect.defineMetadata('propMetadata', meta, target.constructor);
}
return clone;
}
/**
* Apply this annotation instance to the given method parameter.
* @param target
* @param name
* @param index
*/
public static applyToParameter(annotation: T, target: any, name: string, index: number): T {
let list: (IAnnotation[] | null)[] = this.getOrCreateListForMethodParameters(target, name);
while (list.length < index)
list.push(null);
let paramList = list[index] || [];
let clone = this.clone(annotation);
paramList.push(clone);
list[index] = paramList;
return clone;
}
/**
* Apply this annotation instance to the given constructor parameter.
* @param target
* @param name
* @param index
*/
public static applyToConstructorParameter(annotation: T, target: any, index: number): T {
let list: (IAnnotation[] | null)[] = this.getOrCreateListForConstructorParameters(target);
while (list.length < index)
list.push(null);
let paramList = list[index] || [];
let clone = this.clone(annotation);
paramList.push(clone);
list[index] = paramList;
if (Reflect.getOwnMetadata) {
let parameterList = Reflect.getOwnMetadata('parameters', target) || [];
while (parameterList.length < index)
parameterList.push(null);
let parameterAnnotes = parameterList[index] || [];
parameterAnnotes.push(clone);
parameterList[index] = parameterAnnotes;
Reflect.defineMetadata('parameters', parameterList, target);
}
return clone;
}
/**
* Clone the given Annotation instance into a new instance. This is not
* a deep copy.
*
* @param annotation
*/
public static clone(annotation: T): T {
if (!annotation)
return annotation;
return Object.assign(Object.create(Object.getPrototypeOf(annotation)), annotation);
}
/**
* Get all annotations (including from Angular and other compatible
* frameworks).
*
* @param target The target to fetch annotations for
*/
public static getClassAnnotations(target: any): IAnnotation[] {
return (this.getListForClass(target) || [])
.map(x => this.clone(x));
}
/**
* Get all annotations (including from Angular and other compatible
* frameworks).
*
* @param target The target to fetch annotations for
*/
public static getMethodAnnotations(target: any, methodName: string, isStatic: boolean = false): IAnnotation[] {
return (this.getListForMethod(isStatic ? target : target.prototype, methodName) || [])
.map(x => this.clone(x));
}
/**
* Get all annotations (including from Angular and other compatible
* frameworks).
*
* @param target The target to fetch annotations for
*/
public static getPropertyAnnotations(target: any, methodName: string, isStatic: boolean = false): IAnnotation[] {
return (this.getListForProperty(isStatic ? target : target.prototype, methodName) || [])
.map(x => this.clone(x));
}
/**
* Get the annotations defined on the parameters of the given method of the given
* class.
*
* @param type
* @param methodName
* @param isStatic Whether `type` itself (isStatic = true), or `type.prototype` (isStatic = false) should be the target.
* Note that passing true may indicate that the passed `type` is already the prototype of a class.
*/
public static getParameterAnnotations(type: any, methodName: string, isStatic: boolean = false): IAnnotation[][] {
return (this.getListForMethodParameters(isStatic ? type : type.prototype, methodName) || [])
.map(set => set ? set.map(anno => this.clone(anno)) : []);
}
/**
* Get the annotations defined on the parameters of the given method of the given
* class.
*
* @param type
* @param methodName
*/
public static getConstructorParameterAnnotations(type: any): IAnnotation[][] {
return (this.getListForConstructorParameters(type) || [])
.map(set => set ? set.map(anno => this.clone(anno)) : []);
}
/**
* Get a list of annotations for the given class.
* @param target
*/
private static getListForClass(target: Object): IAnnotation[] {
if (!target)
return [];
let combinedSet: IAnnotation[] = [];
let superclass = Object.getPrototypeOf(target);
if (superclass && superclass !== Function)
combinedSet = combinedSet.concat(this.getListForClass(superclass));
if (target.hasOwnProperty(ANNOTATIONS_KEY))
combinedSet = combinedSet.concat((target as any)[ANNOTATIONS_KEY] || []);
return combinedSet;
}
/**
* Get a list of own annotations for the given class, or create that list.
* @param target
*/
private static getOrCreateListForClass(target: Object): IAnnotation[] {
if (!target.hasOwnProperty(ANNOTATIONS_KEY))
Object.defineProperty(target, ANNOTATIONS_KEY, { enumerable: false, value: [] });
return (target as any)[ANNOTATIONS_KEY];
}
/**
* Gets a map of the annotations defined on all properties of the given class/function. To get the annotations of instance fields,
* make sure to use `Class.prototype`, otherwise static annotations are returned.
*/
public static getMapForClassProperties(target: Object, mapToPopulate?: Record): Record {
let combinedSet = mapToPopulate || {};
if (!target || target === Function)
return combinedSet;
this.getMapForClassProperties(Object.getPrototypeOf(target), combinedSet);
if (target.hasOwnProperty(PROPERTY_ANNOTATIONS_KEY)) {
let ownMap: Record = (target as any)[PROPERTY_ANNOTATIONS_KEY] || {};
for (let key of Object.keys(ownMap))
combinedSet[key] = (combinedSet[key] || []).concat(ownMap[key]);
}
return combinedSet;
}
private static getOrCreateMapForClassProperties(target: Object): Record {
if (!target.hasOwnProperty(PROPERTY_ANNOTATIONS_KEY))
Object.defineProperty(target, PROPERTY_ANNOTATIONS_KEY, { enumerable: false, value: [] });
return (target as any)[PROPERTY_ANNOTATIONS_KEY];
}
private static getListForProperty(target: any, propertyKey: string): IAnnotation[] | null {
let map = this.getMapForClassProperties(target);
if (!map)
return null;
return map[propertyKey];
}
private static getOrCreateListForProperty(target: any, propertyKey: string): IAnnotation[] {
let map = this.getOrCreateMapForClassProperties(target);
if (!map[propertyKey])
map[propertyKey] = [];
return map[propertyKey];
}
private static getOrCreateListForMethod(target: any, methodName: string): IAnnotation[] {
return this.getOrCreateListForProperty(target, methodName);
}
private static getListForMethod(target: any, methodName: string): IAnnotation[] | null {
return this.getListForProperty(target, methodName);
}
/**
* Get a map of the annotations defined on all parameters of all methods of the given class/function.
* To get instance methods, make sure to pass `Class.prototype`, otherwise the results are for static fields.
*/
public static getMapForMethodParameters(target: Object, mapToPopulate?: Record): Record {
let combinedMap = mapToPopulate || {};
if (!target || target === Function)
return combinedMap;
// superclass/prototype
this.getMapForMethodParameters(Object.getPrototypeOf(target), combinedMap);
if (target.hasOwnProperty(METHOD_PARAMETER_ANNOTATIONS_KEY)) {
let ownMap: Record = (target as any)[METHOD_PARAMETER_ANNOTATIONS_KEY] || {};
for (let methodName of Object.keys(ownMap)) {
let parameters = ownMap[methodName];
let combinedMethodMap = combinedMap[methodName] || [];
for (let i = 0, max = parameters.length; i < max; ++i) {
combinedMethodMap[i] = (combinedMethodMap[i] || []).concat(parameters[i] || []);
}
combinedMap[methodName] = combinedMethodMap;
}
}
return combinedMap;
}
private static getOrCreateMapForMethodParameters(target: Object): Record {
if (!target.hasOwnProperty(METHOD_PARAMETER_ANNOTATIONS_KEY))
Object.defineProperty(target, METHOD_PARAMETER_ANNOTATIONS_KEY, { enumerable: false, value: {} });
return (target as any)[METHOD_PARAMETER_ANNOTATIONS_KEY];
}
private static getListForMethodParameters(target: any, methodName: string): IAnnotation[][] | null {
let map = this.getMapForMethodParameters(target);
if (!map)
return null;
return map[methodName];
}
private static getOrCreateListForMethodParameters(target: any, methodName: string): IAnnotation[][] {
let map = this.getOrCreateMapForMethodParameters(target);
if (!map[methodName])
map[methodName] = [];
return map[methodName];
}
private static getOrCreateListForConstructorParameters(target: any): IAnnotation[][] {
if (!target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY])
Object.defineProperty(target, CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY, { enumerable: false, value: [] });
return target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY];
}
private static getListForConstructorParameters(target: any): IAnnotation[][] {
return target[CONSTRUCTOR_PARAMETERS_ANNOTATIONS_KEY];
}
}
/**
* An annotation for attaching a label to a programmatic element.
* Can be queried with LabelAnnotation.getForClass() for example.
*/
@MetadataName('alterior:Label')
export class LabelAnnotation extends Annotation {
constructor(readonly text: string) {
super();
}
}
export const Label = LabelAnnotation.decorator();