Home Reference Source Test Repository

buildDocs/factory/DecoratorFactory.js

import { isFunction } from 'lodash';
import { InstanceChainMap } from './common';
import { copyMetadata, bind } from '../utils';
export class InternalDecoratorFactory {
    createDecorator(config) {
        const { applicator } = config;
        return (...args) => {
            return (target, name, _descriptor) => {
                const descriptor = this._resolveDescriptor(target, name, _descriptor);
                const { value, get, set } = descriptor;
                // If this decorator is being applied after an instance decorator we simply ignore it
                // as we can't apply it correctly.
                if (!InstanceChainMap.has([target, name])) {
                    if (isFunction(value)) {
                        descriptor.value = copyMetadata(applicator.apply({ config, target, value, args }), value);
                    }
                    else if (isFunction(get) && config.getter) {
                        descriptor.get = copyMetadata(applicator.apply({ config, target, value: get, args }), get);
                    }
                    else if (isFunction(set) && config.setter) {
                        descriptor.set = copyMetadata(applicator.apply({ config, target, value: set, args }), get);
                    }
                }
                return descriptor;
            };
        };
    }
    createInstanceDecorator(config) {
        const { applicator, bound } = config;
        return (...args) => {
            return (target, name, _descriptor) => {
                const descriptor = this._resolveDescriptor(target, name, _descriptor);
                const { value, writable, enumerable, configurable, get, set } = descriptor;
                const isFirstInstance = !InstanceChainMap.has([target, name]);
                const fnChain = InstanceChainMap.get([target, name]) || [];
                const isGetter = isFirstInstance && isFunction(get);
                const isSetter = isFirstInstance && isFunction(set);
                const isMethod = isFirstInstance && isFunction(value);
                const isProperty = isFirstInstance && !isGetter && !isSetter && !isMethod;
                fnChain.push((fn, instance, context) => {
                    if (!this._isApplicable(context, config)) {
                        return fn;
                    }
                    if (bound) {
                        fn = bind(fn, instance);
                    }
                    return copyMetadata(applicator.apply({ args, target, instance, value: fn, config }), fn);
                });
                InstanceChainMap.set([target, name], fnChain);
                if (!isFirstInstance) {
                    return descriptor;
                }
                const applyChain = (fn, context, instance) => {
                    return fnChain.reduce((result, next) => next(result, instance, context), fn);
                };
                const applyDecorator = (instance) => {
                    let getter = get || undefined;
                    let setter = set || undefined;
                    if (isGetter || isSetter) {
                        // If we have a getter apply the decorators to the getter and assign it to the instance.
                        if (isGetter) {
                            getter = applyChain(get, { value: get, getter: true }, instance);
                        }
                        if (isSetter) {
                            setter = applyChain(set, { value: set, setter: true }, instance);
                        }
                        Object.defineProperty(instance, name, {
                            enumerable,
                            configurable,
                            get: getter,
                            set: setter
                        });
                    }
                    else if (isMethod || isProperty) {
                        const newFn = isMethod
                            ? applyChain(value, { value, method: true }, instance)
                            : applyChain(value, { value, property: true }, instance);
                        Object.defineProperty(instance, name, {
                            writable,
                            enumerable,
                            configurable,
                            value: newFn
                        });
                    }
                };
                if (isMethod || isProperty) {
                    delete descriptor.value;
                    delete descriptor.writable;
                }
                descriptor.get = function () {
                    applyDecorator(this);
                    const descriptor = Object.getOwnPropertyDescriptor(this, name);
                    if (descriptor.get) {
                        return descriptor.get.call(this);
                    }
                    return descriptor.value;
                };
                descriptor.set = function (value) {
                    applyDecorator(this);
                    const descriptor = Object.getOwnPropertyDescriptor(this, name);
                    if (descriptor.set) {
                        descriptor.set.call(this, value);
                    }
                    else if (isProperty || isMethod) {
                        this[name] = value;
                    }
                };
                return descriptor;
            };
        };
    }
    _isApplicable(context, config) {
        return !Boolean(context.getter && !config.getter
            || context.setter && !config.setter
            || context.method && !config.method
            || context.property && !config.property);
    }
    _resolveDescriptor(target, name, descriptor) {
        if (descriptor) {
            return descriptor;
        }
        return Object.getOwnPropertyDescriptor(target, name) || {};
    }
}
export const DecoratorFactory = new InternalDecoratorFactory();