Skip to content

Commit

Permalink
feat(all): decorators with optional arguments do not require invocation
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
This may cause issue with tools that rely on static analysis of the
decorators. Since the deocorators are typed with intersections they must
be removed from a static function wrapper.
  • Loading branch information
steelsojka committed Mar 17, 2018
1 parent 5300a2e commit 59a71d7
Show file tree
Hide file tree
Showing 39 changed files with 530 additions and 175 deletions.
2 changes: 1 addition & 1 deletion src/applicators/InvokeApplicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ export class InvokeApplicator extends Applicator {
return execute(value.bind(this), ...invokeArgs, ...args);
}
}
}
}
4 changes: 2 additions & 2 deletions src/attempt.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import attempt = require('lodash/attempt');
import partial = require('lodash/partial');

import { DecoratorConfig, DecoratorFactory, TypedMethodDecorator } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedMethodDecorator } from './factory';
import { PreValueApplicator } from './applicators';

const attemptFn = (fn: () => void) => partial(attempt, fn);
Expand Down Expand Up @@ -31,6 +31,6 @@ export const Attempt = DecoratorFactory.createDecorator(
new DecoratorConfig(attemptFn, new PreValueApplicator(), {
optionalParams: true
})
) as TypedMethodDecorator;
) as BiTypedMethodDecorator;
export { Attempt as attempt };
export default Attempt;
4 changes: 2 additions & 2 deletions src/bind.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import bind = require('lodash/bind');

import { DecoratorConfig, DecoratorFactory, TypedMethodDecorator1 } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedMethodDecorator1 } from './factory';
import { BindApplicator } from './applicators';

/**
Expand Down Expand Up @@ -32,6 +32,6 @@ export const Bind = DecoratorFactory.createInstanceDecorator(
new DecoratorConfig(bind, new BindApplicator(), {
optionalParams: true
})
) as TypedMethodDecorator1<any>;
) as BiTypedMethodDecorator1<any>;
export { Bind as bind, };
export default Bind;
4 changes: 2 additions & 2 deletions src/curry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import curry = require('lodash/curry');

import { DecoratorConfig, DecoratorFactory, TypedMethodDecorator1 } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedMethodDecorator1 } from './factory';
import { PreValueApplicator } from './applicators';

/**
Expand Down Expand Up @@ -31,6 +31,6 @@ import { PreValueApplicator } from './applicators';
*/
export const Curry = DecoratorFactory.createInstanceDecorator(
new DecoratorConfig(curry, new PreValueApplicator(), { bound: true, optionalParams: true })
) as TypedMethodDecorator1<number>;
) as BiTypedMethodDecorator1<number>;
export { Curry as curry };
export default Curry;
15 changes: 15 additions & 0 deletions src/curryAll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ describe('curryAll', () => {
expect(add5(10)).to.equal(15);
});

it('should curry the method with default arity (paramless)', () => {
class MyClass {
@CurryAll
add(a: any, b?: any) {
return a + b;
}
}

const myClass = new MyClass();
const add5 = myClass.add(5);

expect(add5).to.be.an.instanceOf(Function);
expect(add5(10)).to.equal(15);
});

it('should curry the method with fixed arity', () => {
class MyClass {
@CurryAll(2)
Expand Down
14 changes: 5 additions & 9 deletions src/curryAll.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import curry = require('lodash/curry');

import { DecoratorConfig, DecoratorFactory, LodashMethodDecorator } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedDecorator1 } from './factory';
import { PreValueApplicator } from './applicators';

const decorator = DecoratorFactory.createDecorator(
new DecoratorConfig(curry, new PreValueApplicator())
);

/**
* Creates a function that accepts arguments of func and either invokes func returning its result, if at least arity number of arguments have been provided, or returns a function that accepts the remaining func arguments, and so on.
* The arity of func may be specified if func.length is not sufficient.
Expand All @@ -31,8 +27,8 @@ const decorator = DecoratorFactory.createDecorator(
*
* add5AndMultiply(10); // => 15
*/
export function CurryAll(arity?: number): LodashMethodDecorator {
return decorator(arity);
}
export const CurryAll = DecoratorFactory.createDecorator(
new DecoratorConfig(curry, new PreValueApplicator(), { optionalParams: true })
) as BiTypedDecorator1<number>;
export { CurryAll as curryAll };
export default decorator;
export default CurryAll;
14 changes: 14 additions & 0 deletions src/curryRight.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ describe('curryRight', () => {
expect(set5(10)).to.eql([ 10, 5 ]);
});

it('should curry the method with default arity (paramless)', () => {
class MyClass {
@CurryRight
add(a: any, b?: any) {
return [ a, b ];
}
}

const myClass = new MyClass();
const set5 = myClass.add(5) as any;

expect(set5(10)).to.eql([ 10, 5 ]);
});

it('should retain the class context', () => {
class MyClass {
value = 'blorg';
Expand Down
14 changes: 5 additions & 9 deletions src/curryRight.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import curryRight = require('lodash/curryRight');

import { DecoratorConfig, DecoratorFactory, LodashMethodDecorator } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedMethodDecorator1 } from './factory';
import { PreValueApplicator } from './applicators';

const decorator = DecoratorFactory.createInstanceDecorator(
new DecoratorConfig(curryRight, new PreValueApplicator(), { bound: true })
);

/**
* This method is like _.curry except that arguments are applied to func in the manner of _.partialRight instead of _.partial.
* The arity of func may be specified if func.length is not sufficient.
Expand All @@ -33,8 +29,8 @@ const decorator = DecoratorFactory.createInstanceDecorator(
*
* add5AndMultiply(10); // => 30
*/
export function CurryRight(arity?: number): LodashMethodDecorator {
return decorator(arity);
}
export const CurryRight = DecoratorFactory.createInstanceDecorator(
new DecoratorConfig(curryRight, new PreValueApplicator(), { bound: true, optionalParams: true })
) as BiTypedMethodDecorator1<number>;
export { CurryRight as curryRight };
export default decorator;
export default CurryRight;
14 changes: 14 additions & 0 deletions src/curryRightAll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,18 @@ describe('curryRightAll', () => {

expect(set5(10)).to.eql([ 10, 5 ]);
});

it('should curry the method with default arity (paramless)', () => {
class MyClass {
@CurryRightAll
add(a: any, b?: any) {
return [ a, b ];
}
}

const myClass = new MyClass();
const set5 = myClass.add(5) as any;

expect(set5(10)).to.eql([ 10, 5 ]);
});
});
14 changes: 5 additions & 9 deletions src/curryRightAll.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import curryRight = require('lodash/curryRight');

import { DecoratorConfig, DecoratorFactory, LodashMethodDecorator } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedMethodDecorator1 } from './factory';
import { PreValueApplicator } from './applicators';

const decorator = DecoratorFactory.createDecorator(
new DecoratorConfig(curryRight, new PreValueApplicator())
);

/**
* This method is like _.curry except that arguments are applied to func in the manner of _.partialRight instead of _.partial.
* The arity of func may be specified if func.length is not sufficient.
Expand All @@ -31,8 +27,8 @@ const decorator = DecoratorFactory.createDecorator(
*
* add5AndMultiply(10); // => 15
*/
export function CurryRightAll(arity?: number): LodashMethodDecorator {
return decorator(arity);
}
export const CurryRightAll = DecoratorFactory.createDecorator(
new DecoratorConfig(curryRight, new PreValueApplicator(), { optionalParams: true })
) as BiTypedMethodDecorator1<number>;
export { CurryRightAll as curryRightAll };
export default decorator;
export default CurryRightAll;
51 changes: 51 additions & 0 deletions src/defer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ describe('defer', () => {
}, 0);
});

it('should defer the method (paramless)', (done) => {
const _spy = spy();

class MyClass {
@Defer
fn(...args: any[]) {
expect(this, 'context').to.equal(myClass);
_spy(...args);
}
}

const myClass = new MyClass();

myClass.fn(10);
expect(_spy.called).to.be.false;

setTimeout(() => {
expect(_spy.callCount).to.equal(1);
expect(_spy.getCalls()[0].args).to.eql([ 10 ]);
done();
}, 0);
});

it('should debounce the property setter', (done) => {
class MyClass {
private _value: number = 100;
Expand Down Expand Up @@ -54,4 +77,32 @@ describe('defer', () => {
done();
}, 0);
});

it('should debounce the property setter (paramless)', (done) => {
class MyClass {
private _value: number = 100;

@Defer
set value(value: number) {
expect(this, 'context').to.equal(myClass);
this._value = value;
}

get value(): number {
return this._value;
}
}

const myClass = new MyClass();

myClass.value = 5;
myClass.value = 15;

expect(myClass.value).to.equal(100);

setTimeout(() => {
expect(myClass.value).to.equal(15);
done();
}, 0);
});
});
14 changes: 5 additions & 9 deletions src/defer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import defer = require('lodash/defer');

import { DecoratorConfig, DecoratorFactory, LodashMethodDecorator } from './factory';
import { DecoratorConfig, DecoratorFactory, BiTypedDecoratorN } from './factory';
import { InvokeApplicator } from './applicators';

const decorator = DecoratorFactory.createDecorator(
new DecoratorConfig(defer, new InvokeApplicator(), { setter: true })
);

/**
* Defers invoking the func until the current call stack has cleared. Any additional arguments are provided to func when it's invoked.
*
Expand All @@ -32,8 +28,8 @@ const decorator = DecoratorFactory.createDecorator(
* myClass.value; // => 110;
* }, 0);
*/
export function Defer(...args: any[]): LodashMethodDecorator {
return decorator(...args);
}
export const Defer = DecoratorFactory.createDecorator(
new DecoratorConfig(defer, new InvokeApplicator(), { setter: true, optionalParams: true })
) as BiTypedDecoratorN;
export { Defer as defer };
export default decorator;
export default Defer;
33 changes: 24 additions & 9 deletions src/factory/DecoratorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
InstanceChainContext
} from './common';
import { DecoratorConfig } from './DecoratorConfig';
import { copyMetadata, bind } from '../utils';
import { copyMetadata, bind, isMethodOrPropertyDecoratorArgs } from '../utils';

export type GenericDecorator = (...args: any[]) => LodashDecorator;

Expand All @@ -15,6 +15,8 @@ export class InternalDecoratorFactory {
const { applicator, optionalParams } = config;

return (...args: any[]): LodashDecorator => {
let params = args;

const decorator = (target: Object, name: string, _descriptor?: PropertyDescriptor): PropertyDescriptor => {
const descriptor = this._resolveDescriptor(target, name, _descriptor);
const { value, get, set } = descriptor;
Expand All @@ -23,25 +25,32 @@ export class InternalDecoratorFactory {
// 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);
descriptor.value = copyMetadata(applicator.apply({ config, target, value, args: params }), value);
} else if (isFunction(get) && config.getter) {
descriptor.get = copyMetadata(applicator.apply({ config, target, value: get, args }), get);
descriptor.get = copyMetadata(applicator.apply({ config, target, value: get, args: params }), get);
} else if (isFunction(set) && config.setter) {
descriptor.set = copyMetadata(applicator.apply({ config, target, value: set, args }), get);
descriptor.set = copyMetadata(applicator.apply({ config, target, value: set, args: params }), set);
}
}

return descriptor;
};

return optionalParams && args.length >= 2 ? decorator(args[0], args[1], args[2]) as any : decorator;
if (optionalParams && isMethodOrPropertyDecoratorArgs(...args)) {
params = [];

return decorator(args[0], args[1], args[2]) as any;
}

return decorator;
};
}

createInstanceDecorator(config: DecoratorConfig): GenericDecorator {
const { applicator, bound, optionalParams } = config;

return (...args: any[]): LodashDecorator => {
let params = args;
const decorator = (target: Object, name: string, _descriptor?: PropertyDescriptor): PropertyDescriptor => {
const descriptor = this._resolveDescriptor(target, name, _descriptor);
const { value, writable, enumerable, configurable, get, set } = descriptor;
Expand All @@ -63,7 +72,7 @@ export class InternalDecoratorFactory {
}

return copyMetadata(
applicator.apply({ args, target, instance, value: fn, config }),
applicator.apply({ args: params, target, instance, value: fn, config }),
fn
);
});
Expand Down Expand Up @@ -125,7 +134,7 @@ export class InternalDecoratorFactory {
descriptor.get = function() {
applyDecorator(this);

const descriptor = Object.getOwnPropertyDescriptor(this, name);
const descriptor = Object.getOwnPropertyDescriptor(this, name)!;

if (descriptor.get) {
return descriptor.get.call(this);
Expand All @@ -137,7 +146,7 @@ export class InternalDecoratorFactory {
descriptor.set = function(value) {
applyDecorator(this);

const descriptor = Object.getOwnPropertyDescriptor(this, name);
const descriptor = Object.getOwnPropertyDescriptor(this, name)!;

if (descriptor.set) {
descriptor.set.call(this, value);
Expand All @@ -149,7 +158,13 @@ export class InternalDecoratorFactory {
return descriptor;
};

return optionalParams && args.length >= 2 ? decorator(args[0], args[1], args[2]) as any: decorator;
if (optionalParams && isMethodOrPropertyDecoratorArgs(...args)) {
params = [];

return decorator(args[0], args[1], args[2]) as any;
}

return decorator;
};
}

Expand Down
Loading

0 comments on commit 59a71d7

Please sign in to comment.