Skip to content

Commit

Permalink
feat: add support for annotated methods
Browse files Browse the repository at this point in the history
  • Loading branch information
draconisNoctis committed Apr 24, 2019
1 parent 3752850 commit eeead63
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 9 deletions.
57 changes: 55 additions & 2 deletions src/manager.spec.ts
Expand Up @@ -11,7 +11,8 @@ import {
BeforeStatic,
Getter,
Setter, StaticGetter,
StaticSetter
StaticSetter,
JoinpointShadow
} from './metadata';
import { Annotator } from '@neoskop/annotation-factory';

Expand Down Expand Up @@ -145,9 +146,17 @@ declare class TestClassDeclaration extends ParentClass {
misc2(...args : any[]) : any;
}

declare class TestClassJoinpointShadowDeclaration {
static staticMethod(arg : string): string;

method(arg : string): string;
}

describe('AopManager', () => {
let TestClass : typeof TestClassDeclaration;
let TestClassJoinpointShadow : typeof TestClassJoinpointShadowDeclaration;
let TestAspect : any;
let TestAspectJoinpointShadow : any;
let manager : AopManager | undefined;
let instance : TestClassDeclaration;

Expand Down Expand Up @@ -285,7 +294,33 @@ describe('AopManager', () => {
staticParentAdvice() {
}
}

class TestClassJoinpointShadow_ implements TestClassJoinpointShadowDeclaration {
@JoinpointShadow()
static staticMethod(arg : string) {
return arg.toUpperCase();
}

@JoinpointShadow()
method(arg : string) {
return arg.toUpperCase();
}
}

class TestAspectJoinpointShadow_ {
@AfterStatic(TestClassJoinpointShadow_, 'staticMethod')
afterStaticMethod(jp : JoinpointContext<typeof TestClassJoinpointShadow_, 'staticMethod'>) {
jp.setResult(jp.getResult() + '+AFTER');
}

@After(TestClassJoinpointShadow_, 'method')
afterMethod(jp : JoinpointContext<TestClassJoinpointShadow_, 'method'>) {
jp.setResult(jp.getResult() + '+AFTER');
}
}

TestAspectJoinpointShadow = TestAspectJoinpointShadow_;
TestClassJoinpointShadow = TestClassJoinpointShadow_;
TestAspect = _TestAspect;
});

Expand Down Expand Up @@ -415,7 +450,17 @@ describe('AopManager', () => {
instance = new TestClass();

expect(instance.parentTest()).to.be.equal('parentTest');
})
});

it('should use inner joinpoint shadow', () => {
const origin = TestClassJoinpointShadow.prototype.method;
manager!.install([ new TestAspectJoinpointShadow() ]);

const instance = new TestClassJoinpointShadow();

expect(instance.method).to.be.equal(origin, 'replaced method');
expect(instance.method('foo')).to.be.equal('FOO+AFTER');
});
});

describe('install (instance property)', () => {
Expand Down Expand Up @@ -682,6 +727,14 @@ describe('AopManager', () => {
expect(SPIES.staticAround).to.have.been.calledOnce;
expect(SPIES.staticAroundTest).not.to.have.been.called;
});

it('should use inner joinpoint shadow', () => {
const origin = TestClassJoinpointShadow.staticMethod;
manager!.install([ new TestAspectJoinpointShadow() ]);

expect(TestClassJoinpointShadow.staticMethod).to.be.equal(origin, 'replaced method');
expect(TestClassJoinpointShadow.staticMethod('foo')).to.be.equal('FOO+AFTER');
});
});

describe('install (static property)', () => {
Expand Down
24 changes: 24 additions & 0 deletions src/metadata.ts
Expand Up @@ -191,3 +191,27 @@ export const StaticGetter : StaticPropertyPointcutDecorator = Annotator.makeProp
*/
export const StaticSetter : StaticPropertyPointcutDecorator = Annotator.makePropDecorator('SetterStatic', propsFactory, StaticPropertyPointcut);

/**
* Marks a method explicit as joinpoint shadow
* Required for annotated methods!
* @example
* ```
* class Controller {
* @Get('/')
* @JoinpointShadow() // IMPORTANT: as last annotation!
* method() {
* return 'foo';
* }
* }
* ```
*/
export function JoinpointShadow() : MethodDecorator {
return (_target: Object, _propertyKey : string | symbol, descriptor: PropertyDescriptor) => {
descriptor.value = Object.assign(function(this : any) {
return descriptor.value.JoinpointShadow.apply(this, arguments);
}, { JoinpointShadow: descriptor.value });

return descriptor;
}
}

32 changes: 25 additions & 7 deletions src/utils.ts
Expand Up @@ -8,19 +8,37 @@ import { Property } from './metadata';
*/
export function createJoinpoint(fn : (this : any, target : Function, ...args : any[]) => any, pointcut : string, proto : any) : Joinpoint {
const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(proto, pointcut);
const origin = ownPropertyDescriptor && ownPropertyDescriptor.value;
return proto[ pointcut ] = Object.assign(function(this : any, ...args : any[]) {
const target = ownPropertyDescriptor ? ownPropertyDescriptor.value! : Object.getPrototypeOf(proto)[ pointcut ];
let origin = ownPropertyDescriptor && ownPropertyDescriptor.value;
if(origin && origin.JoinpointShadow) {
origin = origin.JoinpointShadow;
}
const joinpoint = Object.assign(function(this : any, ...args : any[]) {
let target = /*ownPropertyDescriptor ? ownPropertyDescriptor.value! :*/ origin || Object.getPrototypeOf(proto)[ pointcut ];
if(target.JoinpointShadow) {
target = target.JoinpointShadow;
}
return fn.apply(this, [ target, ...args ]);
}, {
restore() {
if(origin) {
proto[ pointcut ] = origin;
if(proto[ pointcut ].JoinpointShadow) {
if(origin) {
proto[ pointcut ].JoinpointShadow = origin;
}
} else {
delete proto[ pointcut ];
if(origin) {
proto[ pointcut ] = origin;
} else {
delete proto[ pointcut ];
}
}
}
})
});

if(proto[pointcut] && proto[pointcut].JoinpointShadow) {
return proto[pointcut].JoinpointShadow = joinpoint;
} else {
return proto[ pointcut ] = joinpoint;
}
}

/**
Expand Down

0 comments on commit eeead63

Please sign in to comment.