-
Star
(372)
You must be signed in to star a gist -
Fork
(90)
You must be signed in to fork a gist
-
-
Save remojansen/16c661a7afd68e22ac6e to your computer and use it in GitHub Desktop.
| function logClass(target: any) { | |
| // save a reference to the original constructor | |
| var original = target; | |
| // a utility function to generate instances of a class | |
| function construct(constructor, args) { | |
| var c : any = function () { | |
| return constructor.apply(this, args); | |
| } | |
| c.prototype = constructor.prototype; | |
| return new c(); | |
| } | |
| // the new constructor behaviour | |
| var f : any = function (...args) { | |
| console.log("New: " + original.name); | |
| return construct(original, args); | |
| } | |
| // copy prototype so intanceof operator still works | |
| f.prototype = original.prototype; | |
| // return new constructor (will override original) | |
| return f; | |
| } | |
| @logClass | |
| class Person { | |
| public name: string; | |
| public surname: string; | |
| constructor(name : string, surname : string) { | |
| this.name = name; | |
| this.surname = surname; | |
| } | |
| } | |
| var p = new Person("remo", "jansen"); |
| @logClassWithArgs({ when : { name : "remo"} }) | |
| class Person { | |
| public name: string; | |
| // ... | |
| } | |
| function logClassWithArgs(filter: Object) { | |
| return (target: Object) => { | |
| // implement class decorator here, the class decorator | |
| // will have access to the decorator arguments (filter) | |
| // because they are stored in a closure | |
| } | |
| } |
| function log(...args : any[]) { | |
| switch(args.length) { | |
| case 1: | |
| return logClass.apply(this, args); | |
| case 2: | |
| return logProperty.apply(this, args); | |
| case 3: | |
| if(typeof args[2] === "number") { | |
| return logParameter.apply(this, args); | |
| } | |
| return logMethod.apply(this, args); | |
| default: | |
| throw new Error(); | |
| } | |
| } |
| function logMethod(target, key, descriptor) { | |
| // save a reference to the original method this way we keep the values currently in the | |
| // descriptor and don't overwrite what another decorator might have done to the descriptor. | |
| if(descriptor === undefined) { | |
| descriptor = Object.getOwnPropertyDescriptor(target, key); | |
| } | |
| var originalMethod = descriptor.value; | |
| //editing the descriptor/value parameter | |
| descriptor.value = function () { | |
| var args = []; | |
| for (var _i = 0; _i < arguments.length; _i++) { | |
| args[_i - 0] = arguments[_i]; | |
| } | |
| var a = args.map(function (a) { return JSON.stringify(a); }).join(); | |
| // note usage of originalMethod here | |
| var result = originalMethod.apply(this, args); | |
| var r = JSON.stringify(result); | |
| console.log("Call: " + key + "(" + a + ") => " + r); | |
| return result; | |
| }; | |
| // return edited descriptor as opposed to overwriting the descriptor | |
| return descriptor; | |
| } | |
| class Person { | |
| public name: string; | |
| public surname: string; | |
| constructor(name : string, surname : string) { | |
| this.name = name; | |
| this.surname = surname; | |
| } | |
| @logMethod | |
| public saySomething(something : string, somethingElse : string) : string { | |
| return this.name + " " + this.surname + " says: " + something + " " + somethingElse; | |
| } | |
| } | |
| var p = new Person("remo", "jansen"); | |
| p.saySomething("I love playing", "halo"); |
| function logParameter(target: any, key : string, index : number) { | |
| var metadataKey = `__log_${key}_parameters`; | |
| if (Array.isArray(target[metadataKey])) { | |
| target[metadataKey].push(index); | |
| } | |
| else { | |
| target[metadataKey] = [index]; | |
| } | |
| } | |
| function logMethod(target, key, descriptor) { | |
| if(descriptor === undefined) { | |
| descriptor = Object.getOwnPropertyDescriptor(target, key); | |
| } | |
| var originalMethod = descriptor.value; | |
| //editing the descriptor/value parameter | |
| descriptor.value = function (...args: any[]) { | |
| var metadataKey = `__log_${key}_parameters`; | |
| var indices = target[metadataKey]; | |
| if (Array.isArray(indices)) { | |
| for (var i = 0; i < args.length; i++) { | |
| if (indices.indexOf(i) !== -1) { | |
| var arg = args[i]; | |
| var argStr = JSON.stringify(arg) || arg.toString(); | |
| console.log(`${key} arg[${i}]: ${argStr}`); | |
| } | |
| } | |
| var result = originalMethod.apply(this, args); | |
| return result; | |
| } | |
| else { | |
| var a = args.map(a => (JSON.stringify(a) || a.toString())).join(); | |
| var result = originalMethod.apply(this, args); | |
| var r = JSON.stringify(result); | |
| console.log(`Call: ${key}(${a}) => ${r}`); | |
| return result; | |
| } | |
| } | |
| // return edited descriptor as opposed to overwriting the descriptor | |
| return descriptor; | |
| } | |
| class Person { | |
| public name: string; | |
| public surname: string; | |
| constructor(name : string, surname : string) { | |
| this.name = name; | |
| this.surname = surname; | |
| } | |
| @logMethod | |
| public saySomething(@logParameter something : string, somethingElse : string) : string { | |
| return this.name + " " + this.surname + " says: " + something + " " + somethingElse; | |
| } | |
| } | |
| var p = new Person("remo", "jansen"); | |
| p.saySomething("I love playing", "halo"); |
| function logProperty(target: any, key: string) { | |
| // property value | |
| var _val = this[key]; | |
| // property getter | |
| var getter = function () { | |
| console.log(`Get: ${key} => ${_val}`); | |
| return _val; | |
| }; | |
| // property setter | |
| var setter = function (newVal) { | |
| console.log(`Set: ${key} => ${newVal}`); | |
| _val = newVal; | |
| }; | |
| // Delete property. | |
| if (delete this[key]) { | |
| // Create new property with getter and setter | |
| Object.defineProperty(target, key, { | |
| get: getter, | |
| set: setter, | |
| enumerable: true, | |
| configurable: true | |
| }); | |
| } | |
| } | |
| class Person { | |
| @logProperty | |
| public name: string; | |
| public surname: string; | |
| constructor(name : string, surname : string) { | |
| this.name = name; | |
| this.surname = surname; | |
| } | |
| } | |
| var p = new Person("remo", "Jansen"); | |
| p.name = "Remo"; | |
| var n = p.name; |
| function logParamTypes(target : any, key : string) { | |
| var types = Reflect.getMetadata("design:paramtypes", target, key); | |
| var s = types.map(a => a.name).join(); | |
| console.log(`${key} param types: ${s}`); | |
| } | |
| class Foo {} | |
| interface IFoo {} | |
| class Demo{ | |
| @logParameters | |
| doSomething( | |
| param1 : string, | |
| param2 : number, | |
| param3 : Foo, | |
| param4 : { test : string }, | |
| param5 : IFoo, | |
| param6 : Function, | |
| param7 : (a : number) => void, | |
| ) : number { | |
| return 1 | |
| } | |
| } | |
| // doSomething param types: String, Number, Foo, Object, Object, Function, Function |
Hi guys sorry but the decorators signatures are a bit different since I wrote this. You can fins the new signatures here:
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;I will try to update this in the future but right now I don't have time :( Feel free to fork and send a PR if you do!
@remojansen what does the <T> at the start of the type alias for MethodDecorator mean? How is declare type MethodDecorator = <T>... different from declare type MethodDecorator<T> = ...? Is there a way to specialize it so that MethodDecorator works only for functions of a given type?
https://gist.github.com/remojansen/16c661a7afd68e22ac6e#file-method_decorator-ts-L5
Can you explain why descriptor could be undefined here?
Useful examples! Can you @remojansen please provide an example of decorator of async class method that executes only on Promise.resolve()? That will be really useful for sending analytics, for example.
@afr1983 Don't call Object.defineProperty for backingField. This adds the property to the prototype and you are not using it. When you do this[backingField], you are accessing an instance property.
The backingField is expected to show up in for..in, this is the behavior if you manually create a property.