injection-js
Version:
Dependency Injection library for JavaScript and TypeScript
236 lines • 10 kB
JavaScript
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { global, stringify } from '../facade/lang';
import { Type, isType } from '../facade/type';
/**
* Attention: This regex has to hold even if the code is minified!
*/
export const DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*arguments\)/;
export class ReflectionCapabilities {
constructor(reflect) {
this._reflect = reflect || global['Reflect'];
}
isReflectionEnabled() {
return true;
}
factory(t) {
return (...args) => new t(...args);
}
/** @internal */
_zipTypesAndAnnotations(paramTypes, paramAnnotations) {
let result;
if (typeof paramTypes === 'undefined') {
result = new Array(paramAnnotations.length);
}
else {
result = new Array(paramTypes.length);
}
for (let i = 0; i < result.length; i++) {
// TS outputs Object for parameters without types, while Traceur omits
// the annotations. For now we preserve the Traceur behavior to aid
// migration, but this can be revisited.
if (typeof paramTypes === 'undefined') {
result[i] = [];
// tslint:disable-next-line:triple-equals
}
else if (paramTypes[i] != Object) {
result[i] = [paramTypes[i]];
}
else {
result[i] = [];
}
if (paramAnnotations && paramAnnotations[i] != null) {
result[i] = result[i].concat(paramAnnotations[i]);
}
}
return result;
}
_ownParameters(type, parentCtor) {
// If we have no decorators, we only have function.length as metadata.
// In that case, to detect whether a child class declared an own constructor or not,
// we need to look inside of that constructor to check whether it is
// just calling the parent.
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
// that sets 'design:paramtypes' to []
// if a class inherits from another class but has no ctor declared itself.
if (DELEGATE_CTOR.exec(type.toString())) {
return null;
}
// Prefer the direct API.
if (type.parameters && type.parameters !== parentCtor.parameters) {
return type.parameters;
}
// API of tsickle for lowering decorators to properties on the class.
const tsickleCtorParams = type.ctorParameters;
if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters = typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
const paramTypes = ctorParameters.map((ctorParam) => ctorParam && ctorParam.type);
const paramAnnotations = ctorParameters.map((ctorParam) => ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators));
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
// API for metadata created by invoking the decorators.
if (this._reflect != null && this._reflect.getOwnMetadata != null) {
const paramAnnotations = this._reflect.getOwnMetadata('parameters', type);
const paramTypes = this._reflect.getOwnMetadata('design:paramtypes', type);
if (paramTypes || paramAnnotations) {
return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
}
}
// If a class has no decorators, at least create metadata
// based on function.length.
// Note: We know that this is a real constructor as we checked
// the content of the constructor above.
return new Array(type.length).fill(undefined);
}
parameters(type) {
// Note: only report metadata if we have at least one class decorator
// to stay in sync with the static reflector.
if (!isType(type)) {
return [];
}
const parentCtor = getParentCtor(type);
let parameters = this._ownParameters(type, parentCtor);
if (!parameters && parentCtor !== Object) {
parameters = this.parameters(parentCtor);
}
return parameters || [];
}
_ownAnnotations(typeOrFunc, parentCtor) {
// Prefer the direct API.
if (typeOrFunc.annotations && typeOrFunc.annotations !== parentCtor.annotations) {
let annotations = typeOrFunc.annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
// API of tsickle for lowering decorators to properties on the class.
if (typeOrFunc.decorators && typeOrFunc.decorators !== parentCtor.decorators) {
return convertTsickleDecoratorIntoMetadata(typeOrFunc.decorators);
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
}
return null;
}
annotations(typeOrFunc) {
if (!isType(typeOrFunc)) {
return [];
}
const parentCtor = getParentCtor(typeOrFunc);
const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
return parentAnnotations.concat(ownAnnotations);
}
_ownPropMetadata(typeOrFunc, parentCtor) {
// Prefer the direct API.
if (typeOrFunc.propMetadata && typeOrFunc.propMetadata !== parentCtor.propMetadata) {
let propMetadata = typeOrFunc.propMetadata;
if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
propMetadata = propMetadata.propMetadata;
}
return propMetadata;
}
// API of tsickle for lowering decorators to properties on the class.
if (typeOrFunc.propDecorators && typeOrFunc.propDecorators !== parentCtor.propDecorators) {
const propDecorators = typeOrFunc.propDecorators;
const propMetadata = {};
Object.keys(propDecorators).forEach((prop) => {
propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]);
});
return propMetadata;
}
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('propMetadata', typeOrFunc);
}
return null;
}
propMetadata(typeOrFunc) {
if (!isType(typeOrFunc)) {
return {};
}
const parentCtor = getParentCtor(typeOrFunc);
const propMetadata = {};
if (parentCtor !== Object) {
const parentPropMetadata = this.propMetadata(parentCtor);
Object.keys(parentPropMetadata).forEach((propName) => {
propMetadata[propName] = parentPropMetadata[propName];
});
}
const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
if (ownPropMetadata) {
Object.keys(ownPropMetadata).forEach((propName) => {
const decorators = [];
if (propMetadata.hasOwnProperty(propName)) {
decorators.push(...propMetadata[propName]);
}
decorators.push(...ownPropMetadata[propName]);
propMetadata[propName] = decorators;
});
}
return propMetadata;
}
hasLifecycleHook(type, lcProperty) {
return type instanceof Type && lcProperty in type.prototype;
}
getter(name) {
return new Function('o', 'return o.' + name + ';');
}
setter(name) {
return new Function('o', 'v', 'return o.' + name + ' = v;');
}
method(name) {
const functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
return o.${name}.apply(o, args);`;
return new Function('o', 'args', functionBody);
}
// There is not a concept of import uri in Js, but this is useful in developing Dart applications.
importUri(type) {
// StaticSymbol
if (typeof type === 'object' && type['filePath']) {
return type['filePath'];
}
// Runtime type
return `./${stringify(type)}`;
}
resourceUri(type) {
return `./${stringify(type)}`;
}
resolveIdentifier(name, moduleUrl, members, runtime) {
return runtime;
}
resolveEnum(enumIdentifier, name) {
return enumIdentifier[name];
}
}
function convertTsickleDecoratorIntoMetadata(decoratorInvocations) {
if (!decoratorInvocations) {
return [];
}
return decoratorInvocations.map((decoratorInvocation) => {
const decoratorType = decoratorInvocation.type;
const annotationCls = decoratorType.annotationCls;
const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
return new annotationCls(...annotationArgs);
});
}
function getParentCtor(ctor) {
if (!ctor.prototype) {
return Object;
}
const parentProto = Object.getPrototypeOf(ctor.prototype);
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.
return parentCtor || Object;
}
//# sourceMappingURL=reflection_capabilities.js.map