import { Annotation, Annotations, IAnnotation } from "@alterior/annotations"; import { getParameterNames } from "@alterior/common"; export type Constructor = new (...args: any[]) => T; export type AbstractConstructor = abstract new (...args: any[]) => T; export type AnyConstructor = Constructor | AbstractConstructor; export type Visibility = 'private' | 'public' | 'protected'; /** * Represents a property on a class. A property can also be a field or a method. */ export class Property { constructor( private _type: Constructor, private _name: string, private _isStatic: boolean = false ) { this._visibility = _name[0] === '_' ? 'private' : 'public'; } private _visibility: Visibility; defineMetadata(key: string, value: string) { Reflect.defineMetadata(key, value, this.type, this.name); } getMetadata(key: string): any { return Reflect.getMetadata(key, this.type, this.name); } deleteMetadata(key: string) { Reflect.deleteMetadata(key, this.type, this.name); } private _valueType: Type | undefined; get valueType(): Type | undefined { if (!this._valueType) { let rawType = this.getMetadata('design:type'); if (!rawType) return undefined; this._valueType = new Type(rawType); } return this._valueType; } public get isStatic() { return this._isStatic; } protected get type() { return this._type; } private _annotations: IAnnotation[] | undefined; get annotations() { if (!this._annotations) { if (this._name === 'constructor') this._annotations = Annotations.getClassAnnotations(this._type); else this._annotations = Annotations.getPropertyAnnotations(this._type, this.name, this.isStatic); } return this._annotations; } annotationsOfType(type: Constructor): T[] { return (type as any).filter(this.annotations); } annotationOfType(type: Constructor): T { return (type as any).filter(this.annotations)[0]; } private _descriptor: PropertyDescriptor | undefined = undefined; get descriptor(): PropertyDescriptor | undefined { if (!this._descriptor) this._descriptor = Object.getOwnPropertyDescriptor(this._type.prototype, this._name); return this._descriptor; } get name() { return this._name; } get visibility() { return this._visibility; } } /** * Represents a method on a class. A method can be a static or instance method, and has a set of parameters * and a return type. */ export class Method extends Property { constructor( type: Constructor, name: string, isStatic: boolean = false ) { super(type, name, isStatic); } private _returnType: Type | undefined; get returnType(): Type | undefined { if (!this._returnType) { let rawType = this.getMetadata('design:returntype'); if (!rawType) return undefined; this._returnType = new Type(rawType); } return this._returnType; } private _parameterTypes: (Type | undefined)[] | undefined; get parameterTypes(): (Type | undefined)[] { if (!this._parameterTypes) { let rawTypes = this.getMetadata('design:paramtypes') as Constructor[]; this._parameterTypes = rawTypes.map(x => x ? new Type(x) : undefined); } return this._parameterTypes; } get implementation(): Function { return (this.type as any)[this.name]; } private _parameterNames: string[] | undefined; get parameterNames() { if (!this._parameterNames) this._parameterNames = getParameterNames(this.implementation); return this._parameterNames; } get parameters(): Parameter[] { let parameterNames = this.parameterNames; return [...Array(this.implementation.length).keys()] .map(i => new Parameter(this, i, parameterNames[i])) ; } private _parameterAnnotations: IAnnotation[][] | undefined; get parameterAnnotations(): IAnnotation[][] { if (!this._parameterAnnotations) this._parameterAnnotations = Annotations.getParameterAnnotations(this.type, this.name); return this._parameterAnnotations; } } export class Field extends Property { constructor( type: Constructor, name: string, isStatic: boolean = false ) { super(type, name, isStatic); } } export class ConstructorMethod extends Method { constructor( type: Constructor ) { super(type, 'constructor'); } private _ctorParameterAnnotations: IAnnotation[][] | undefined; get parameterAnnotations() { if (!this._ctorParameterAnnotations) this._ctorParameterAnnotations = Annotations.getConstructorParameterAnnotations(this.type); return this._ctorParameterAnnotations; } } export class Parameter { constructor( private _method: Method, private _index: number, private _name: string | undefined = undefined ) { } get annotations() { return this.method.parameterAnnotations[this.index] ?? []; } annotationsOfType(type: Constructor): T[] { return (type as any).filter(this.annotations); } annotationOfType(type: Constructor): T { return (type as any).filter(this.annotations)[0]; } protected get method() { return this._method; } get valueType() { return this.method.parameterTypes[this.index]; } get index() { return this._index; } get name() { return this._name; } } /** * Represents a class Type and it's metadata */ export class Type { constructor( private _class: Constructor ) { } private _methodNames: string[] | undefined; private _fieldNames: string[] | undefined; get name() { return this._class.name; } getMetadata(key: string) { Reflect.getOwnMetadata(key, this._class); } defineMetadata(key: string, value: any) { Reflect.defineMetadata(key, value, this._class.prototype); } deleteMetadata(key: string) { Reflect.deleteMetadata(key, this._class); } private _metadataKeys: string[] | undefined; get metadataKeys(): string[] { if (!this._metadataKeys) this._metadataKeys = Reflect.getOwnMetadataKeys(this._class); return this._metadataKeys; } private _annotations: IAnnotation[] | undefined; /** * Get all annotations attached to this class */ get annotations(): IAnnotation[] { if (!this._annotations) this._annotations = Annotations.getClassAnnotations(this._class); return this._annotations; } annotationsOfType(type: Constructor): T[] { return (type as any).filter(this.annotations); } annotationOfType(type: Constructor): T { return (type as any).filter(this.annotations)[0]; } private _staticPropertyNames: string[] = []; private _staticMethodNames: string[] = []; private _staticFieldNames: string[] = []; get staticPropertyNames() { if (!this._staticPropertyNames) this._staticPropertyNames = Object.getOwnPropertyNames(this._class).filter(x => !['length', 'prototype', 'name'].includes(x)); return this._staticPropertyNames.slice(); } get staticMethodNames() { if (!this._staticPropertyNames) this._staticPropertyNames = this.staticPropertyNames.filter(x => typeof (this._class as any)[x] === 'function'); return this._staticMethodNames.slice(); } get staticFieldNames() { if (!this._staticFieldNames) this._staticFieldNames = this.staticPropertyNames.filter(x => typeof (this._class as any)[x] !== 'function'); return this._staticFieldNames.slice(); } private _staticMethods: Method[] | undefined; get staticMethods(): Method[] { if (!this._staticMethods) this._staticMethods = this.staticMethodNames.map(methodName => new Method(this._class, methodName, true)); return this._staticMethods; } private _staticFields: Field[] | undefined; get staticFields(): Field[] { if (!this._staticFields) this._staticFields = this.staticFieldNames.map(fieldName => new Field(this._class, fieldName, true)); return this._staticFields; } private _staticProperties: Property[] | undefined; get staticProperties() { if (!this._staticProperties) this._staticProperties = ([] as Property[]).concat(this.staticFields, this.staticMethods); return this._staticProperties; } private _propertyNames: string[] | undefined; get propertyNames() { if (!this._propertyNames) this._propertyNames = Object.getOwnPropertyNames(this._class.prototype); return this._propertyNames.slice(); } get methodNames() { if (!this._methodNames) this._methodNames = this.propertyNames.filter(x => typeof this._class.prototype[x] === 'function'); return this._methodNames.slice(); } get fieldNames() { if (!this._fieldNames) this._fieldNames = this.propertyNames.filter(x => typeof this._class.prototype[x] !== 'function'); return this._fieldNames.slice(); } private _ctor: ConstructorMethod | undefined; get constructorMethod() { if (!this._ctor) this._ctor = new ConstructorMethod(this._class); return this._ctor; } private _methods: Method[] | undefined; get methods() { if (!this._methods) this._methods = this.methodNames.map(methodName => new Method(this._class, methodName)); return this._methods; } private _properties: Property[] | undefined; get properties() { if (!this._properties) this._properties = ([] as Property[]).concat(this.fields, this.methods); return this._properties; } private _fields: Field[] | undefined; get fields() { if (!this._fields) this._fields = this.fieldNames.map(fieldName => new Field(this._class, fieldName)); return this._fields; } get base(): Type { return new Type(Object.getPrototypeOf(this._class)); } } export class Reflector { getTypeFromInstance(instance: T): Type { return this.getTypeFromClass(instance.constructor as any); } getTypeFromClass(typeClass: Constructor): Type { return new Type(typeClass); } }