Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Created March 10, 2015 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbuckton/54cf7d35ab5bd13704c1 to your computer and use it in GitHub Desktop.
Save rbuckton/54cf7d35ab5bd13704c1 to your computer and use it in GitHub Desktop.
interface ClassDecoratorDescriptor<TFunc extends Function> {
  target: TFunc;
  
  // TypeScript specific
  type?: Function;
  paramTypes?: Function[];
  returnType?: Function;
}

interface ParameterDecoratorDescriptor<TFunc extends Function> {
  target: TFunc;
  paramIndex: number;
  
  // TypeScript specific
  type?: Function;
  paramTypes?: Function[];
  returnType?: Function;
}

interface MemberDecoratorDescriptor<T> {
  target: Object;
  propertyKey: string | symbol;
  descriptor: TypedPropertyDescriptor<T>;
  
  // TypeScript specific
  type?: Function;
  paramTypes?: Function[];
  returnType?: Function;
}

interface TypedPropertyDescriptor<T> {
  enumerable?: boolean;
  writable?: boolean;
  configurable?: boolean;
  get?: () => T;
  set?: (value: T) => void;
  value?: T;
}

type DecoratorFunction = <TFunc extends Function>(desc: ClassDecoratorDescriptor<TFunc>): TFunc | void;
type ParameterDecoratorFunction = <TFunc extends Function>(desc: ParameterDecoratorDescriptor<TFunc>): TFunc | void;
type MemberDecoratorFunction = <T>(desc: MemberDecoratorDescriptor<T>): TypedPropertyDescriptor<T> | void;

Examples

@nonenumerable

class Person {
    @nonenumerable
    get kidCount() { return this.children.length; }
}

function nonenumerable<T>({ target, propertyKey, descriptor }: MemberDecoratorDescriptor<T>): void {
    descriptor.enumerable = false;
}

@memoize

class Person {
    @memoize
    get name(): string { return `${this.first} ${this.last}`; }
    set name(value: string) {
        let [first, last] = value.split(' ');
        this.first = first;
        this.last = last;
    }
}

function memoize<T>({ target, propertyKey, descriptor }: MemberDecoratorDescriptor<T>): void {
    let getter = descriptor.get, setter = descriptor.set;
    descriptor.get = function(): T {
        let table = memoizationFor(this);
        if (propertyKey in table) { return table[propertyKey]; }
        return table[propertyKey] = getter.call(this);
    }
    descriptor.set = function(value: T) {
        let table = memoizationFor(this);
        setter.call(this, val);
        table[propertyKey] = val;
    }
}

let memoized = new WeakMap<any, any>();

function memoizationFor(obj: any): { [key: string | symbol]: any; } {
    let table = memoized.get(obj);
    if (!table) { 
        table = Object.create(null);
        memoized.set(obj, table);
    }
    return table;
}

@annotation

// A simple decorator
@annotation
class MyClass {}

function annotation({ target }: ClassDecoratorDescriptor<Function>): void {
    target.annotated = true;
}

@inject (dependency injection marker)

interface Class<T> extends Function {
    new (...args: any[]): T;
    prototype: T;
}

class CompositionContainer {
    private _instances = new Map<Class<any>, any>();
    private _typeRegistry = new Map<Class<any>, Class<any>>();
    
    public for<T>(baseType: Class<T>) {
        return {
            use: (concreteType: Class<T>): void {
                this._typeRegistry.set(baseType, concreteType);
            }
        };
    }
    
    public get<T>(type: Class<T>): T {
        var instance = this._instances.get(type);
        if (!instance) {
            type = this._typeRegistry.get(type) || type;
            var parameters = constructorRegistry.get(type);
            var args = parameters ? parameters.map(paramType => this.get(paramType)) : [];
            instance = Reflect.construct(type, args);
            
            this._instances.set(type, instance);
            
            var properties = propertyRegistry.get(type);
            if (properties) {
                for (var [propertyKey, type] of properties) {
                    Reflect.set(instance, propertyKey, this.get(type));
                }
            }
        }
        return instance;
    }
}

let propertyRegistry = new Map<Class<any>, Map<string | symbol, Class<any>>>();
let constructorRegistry = new Map<Class<any>, Class<any>[]>();
function inject({ target, propertyKey, paramIndex, type, paramTypes }): void {
    if (typeof propertyKey === "string" || typeof propertyKey === "symbol") {
        // @inject on property
        var properties = propertyRegistry.get(target);
        if (!properties) {
            properties = new Map<string | symbol, Class<any>>();
            propertyRegistry.set(target, properties);
        }
        properties.set(propertyKey, type);
    }
    else if (typeof paramIndex === "number") {
        // @inject in parameter
        var parameters = constructorRegistry.get(target);
        if (!parameters) {
            parameters = new Array<Function>(target.length);
            constructorRegistry.set(target, parameters);
        }
        parameters[paramIndex] = type;
    }
    else if (typeof target === "function") {
        // @inject on constructor
        var parameters = constructorRegistry.get(target);
        if (!parameters) {
            parameters = new Array<Function>(paramTypes.length);
            constructorRegistry.set(target, parameters);
        }
        for (var [index, value] of paramTypes.entries()) {
            if (typeof parameters[index] !== "function") {
                parameters[index] = value;
            }
        }
    }
}

// usage
class FrontEnd extends FrontEndBase {
    
    // @inject on property
    @inject
    public circularDependency: FrontEndBase;
    
    // @inject on parameters
    constructor(@inject user: UserServiceBase, @inject data: DataServiceBase) {
    }
}

// @inject on constructor
@inject
class UserService extends UserServiceBase {
    constructor(data: DataServiceBase) {
    }
}

// usage
let container = new CompositionContainer();
container.for(FrontEndBase).use(FrontEnd);
container.for(UserServiceBase).use(UserService);
container.for(DataServiceBase).use(DataService);
var frontEnd = container.get(FrontEnd);

Desugaring

Class Declaration

Syntax

@F("color")
@G
class C {
    constructor(x: number, y: string) {
    }
}

Helpers

function __decorate(decorators, descriptor, mutator) {
    var value = mutator && descriptor[mutator];
    if (!value && mutator === "descriptor" && typeof Object.getOwnPropertyDescriptor === "function") {
        value = Object.getOwnPropertyDescriptor(descriptor.target, descriptor.propertyKey);
    }
    for (var i = decorators.length - 1; i >= 0; i--) {
        var decorator = decorators[i];
        if (mutator) { descriptor[mutator] = value; }
        value = decorator(descriptor) || value;
    }
    if (mutator === "descriptor" && value && typeof Object.defineProperty === "function") {
        Object.defineProperty(descriptor.target, descriptor.propertyKey, value);
    }
    return value;
}

Desugaring (ES6)

var C = (function () {
    class C {
    }
    
    C = __decorate([F("color"), G], { target: C, paramTypes: [Number, String] }, "target");
    return C;
})();

Class Method Declaration

Syntax

class C {
    @F("color")
    @G
    method(x: number, y: string): boolean {
    }
}

Desugaring (ES6)

var C = (function () {
    class C {
        method(x, y) {
        }
    }
    
    __decorate([F("color"), G], { target: C.prototype, propertyKey: "method", type: Function, paramTypes: [Number, String], returnType: Boolean }, "descriptor");
    return C;
})();

Class Property Declaration (?)

Syntax

class C {
    @F("color")
    @G
    property: string;
}

Desugaring (ES6)

var C = (function () {
    class C {
    }
    
    __decorate([F("color"), G], { target: C.prototype, propertyKey: "property", type: String }, "descriptor");
})();

Parameter Declaration

Syntax

class C {
  method(@F("color") @G p: number) {
  }
}

Desugaring (ES6)

var C = (function() {
    class C {
        method(p) {
        }
    }
    
    __decorate([F("color"), G], { target: C.prototype.method, paramIndex: 0, type: Number }); // mutator is undefined
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment