Skip to content

Commit

Permalink
Merge pull request #51 from mgechev/minko/member-precondition
Browse files Browse the repository at this point in the history
feat: implement mangle friendly MemberPrecondition
  • Loading branch information
mgechev committed Dec 20, 2017
2 parents 6b7a74f + 2653434 commit bdc9880
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 28 deletions.
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -119,8 +119,8 @@ The library offers the following combinations of advices and joint points:
export interface MethodSelector {
classNamePattern?: RegExp;
methodNamePattern?: RegExp;
classes?: any[];
methods?: any[];
classes?: Function[];
methods?: Function[];
}
```

Expand All @@ -130,6 +130,8 @@ export interface MethodSelector {
export interface MemberSelector {
classNamePattern: RegExp;
fieldNamePattern: RegExp;
classes?: Function[];
methods?: PropertyDescriptor[];
}
```

Expand Down
3 changes: 1 addition & 2 deletions lib/src/joint_points/accessor_use.ts
Expand Up @@ -32,13 +32,12 @@ export class AccessorJointPoint extends JointPoint {
}

public match(target: Function): any[] {
const name = target.name;
const keys = Object.getOwnPropertyNames(target.prototype);
const res = keys
.map(key => {
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, key);
if (
this.precondition.assert({ className: name, fieldName: key }) &&
this.precondition.assert({ classDefinition: target, fieldName: key }) &&
(this.type === 'get' || (this.type === 'set' && typeof descriptor[this.type] === 'function'))
) {
return key;
Expand Down
4 changes: 2 additions & 2 deletions lib/src/joint_points/method_call.ts
Expand Up @@ -22,8 +22,8 @@ export class MethodCallJointPoint extends JointPoint {
let descriptor = Object.getOwnPropertyDescriptor(target.prototype, key);
if (
this.precondition.assert({
classInstance: target,
methodName: key,
classDefinition: target,
methodName: key
}) &&
typeof descriptor.value === 'function'
) {
Expand Down
53 changes: 37 additions & 16 deletions lib/src/joint_points/preconditions.ts
Expand Up @@ -11,35 +11,56 @@ export class MethodPrecondition implements Precondition {
}
}

assert({ classInstance, methodName }: { classInstance: any; methodName: string }): boolean {
assert({ classDefinition, methodName }: { classDefinition: any; methodName: string }): boolean {
const s = this.selector;
const className = classInstance.name;

const matchAnyMethod = (methods: any[], target: any, methodName: string) => {
let keys = Object.getOwnPropertyNames(target.prototype);
return methods.some(f => {
return target.prototype[methodName] === f;
});
};
const className = classDefinition.name;

const matchClass =
(!s.classNamePattern && !s.classes) ||
(s.classNamePattern && s.classNamePattern.test(className)) ||
(s.classes && s.classes.some(c => c === classInstance));
(s.classes && s.classes.some(c => c === classDefinition));

if (!matchClass) {
return false;
}

const matchMember =
return !!(
(!s.methodNamePattern && !s.methods) ||
(s.methodNamePattern && s.methodNamePattern.test(methodName)) ||
(s.methods && matchAnyMethod(s.methods, classInstance, methodName));

return matchClass && matchMember;
(s.methods && s.methods.some(m => classDefinition.prototype[methodName] === m))
);
}
}

export class MemberPrecondition implements Precondition {
constructor(private selector: MemberSelector) {}

assert({ className, fieldName }: { fieldName: string; className: string }): boolean {
return this.selector.classNamePattern.test(className) && this.selector.fieldNamePattern.test(fieldName);
assert({ classDefinition, fieldName }: { classDefinition: any; fieldName: string }): boolean {
const s = this.selector;
const className = classDefinition.name;

const matchClass =
(!s.classNamePattern && !s.classes) ||
(s.classNamePattern && s.classNamePattern.test(className)) ||
(s.classes && s.classes.some(c => c === classDefinition));

if (!matchClass) {
return false;
}

const d = Object.getOwnPropertyDescriptor(classDefinition.prototype, fieldName);
return !!(
(!s.fieldNamePattern && !s.fields) ||
(s.fieldNamePattern && s.fieldNamePattern.test(fieldName)) ||
(s.fields &&
s.fields.some(f => {
if (!f) {
throw new Error(
'Got invalid property descriptor for a member selector. Use Object.getOwnPropertyDescriptor(fn.prototype, name) if you are using field selectors.'
);
}
return d && (d.get === f.get && d.set === f.set);
}))
);
}
}
10 changes: 6 additions & 4 deletions lib/src/joint_points/selectors.ts
@@ -1,11 +1,13 @@
export interface MethodSelector {
classNamePattern?: RegExp;
methodNamePattern?: RegExp;
classes?: any[];
methods?: any[];
classes?: Function[];
methods?: Function[];
}

export interface MemberSelector {
classNamePattern: RegExp;
fieldNamePattern: RegExp;
classNamePattern?: RegExp;
fieldNamePattern?: RegExp;
classes?: Function[];
fields?: PropertyDescriptor[];
}
4 changes: 2 additions & 2 deletions lib/src/joint_points/static_method.ts
Expand Up @@ -20,8 +20,8 @@ export class StaticMethodJointPoint extends JointPoint {
const descriptor = Object.getOwnPropertyDescriptor(target, key);
return (
this.precondition.assert({
classInstance: target,
methodName: key,
classDefinition: target,
methodName: key
}) && typeof descriptor.value === 'function'
);
});
Expand Down
104 changes: 104 additions & 0 deletions test/core/preconditions.spec.ts
@@ -0,0 +1,104 @@
import { MemberSelector } from './../../lib/src/joint_points/selectors';
import { MemberPrecondition } from './../../lib/src/joint_points/preconditions';

import { expect } from 'chai';

describe('Preconditions', () => {
describe('MemberPrecondition', () => {
it('should match by regex', () => {
class Foo {
get bar(): any {
return null;
}
get baz(): any {
return null;
}
set foobar(v: any) {
// empty
}
}

const selector: MemberSelector = {
classNamePattern: /Foo/,
fieldNamePattern: /bar/
};
const p = new MemberPrecondition(selector);
expect(p.assert({ classDefinition: Foo, fieldName: 'bar' })).equal(true);
expect(p.assert({ classDefinition: Foo, fieldName: 'baz' })).equal(false);
});

it('should match by value', () => {
class Foo {
get bar(): any {
return undefined;
}
get baz(): any {
return null;
}
set foobar(v: any) {
// empty
}
}

const selector: MemberSelector = {
classes: [Foo],
fields: [Object.getOwnPropertyDescriptor(Foo.prototype, 'baz')]
};
const p = new MemberPrecondition(selector);
// expect(p.assert({ classDefinition: Foo, fieldName: 'baz' })).equal(true);
expect(p.assert({ classDefinition: Foo, fieldName: 'bar' })).equal(false);
});

it('should throw with invalid selector', () => {
class Foo {
get bar(): any {
return null;
}
get baz(): any {
return null;
}
set foobar(v: any) {
// empty
}
}

const selector: MemberSelector = {
classes: [Foo],
fields: [Foo.prototype.foobar]
};
const p = new MemberPrecondition(selector);
expect(() => {
p.assert({ classDefinition: Foo, fieldName: 'baz' });
}).to.throw();
});

it('should match by value & regex', () => {
class Foo {
get bar(): any {
return null;
}
get baz(): any {
return null;
}
set foobar(v: any) {
// empty
}
}

const p1 = new MemberPrecondition({
classNamePattern: /Foo/,
fields: [Object.getOwnPropertyDescriptor(Foo.prototype, 'baz')]
});
expect(p1.assert({ classDefinition: Foo, fieldName: 'bar' })).equal(false);
expect(p1.assert({ classDefinition: Foo, fieldName: 'baz' })).equal(true);

const p2 = new MemberPrecondition({
classes: [Foo],
fieldNamePattern: /bar/
});
expect(p2.assert({ classDefinition: Foo, fieldName: 'bar' })).equal(true);
expect(p2.assert({ classDefinition: Foo, fieldName: 'foobar' })).equal(true);
expect(p2.assert({ classDefinition: Foo, fieldName: 'baz' })).equal(false);
});
});
});
1 change: 1 addition & 0 deletions tsconfig.json
Expand Up @@ -14,6 +14,7 @@
"test/advices/sync_advices.spec.ts",
"test/advices/async_advices.spec.ts",
"test/core/pointcut.spec.ts",
"test/core/preconditions.spec.ts",
"demo/index.ts"
],
"angularCompilerOptions": {
Expand Down

0 comments on commit bdc9880

Please sign in to comment.