Comparing version 0.7.1 to 0.7.2
213
meld.js
/** @license MIT License (c) copyright B Cavalier & J Hann */ | ||
/** | ||
* aop | ||
* meld | ||
* Aspect Oriented Programming for Javascript | ||
* | ||
* aop is part of the cujo.js family of libraries (http://cujojs.com/) | ||
* meld is part of the cujo.js family of libraries (http://cujojs.com/) | ||
* | ||
@@ -12,3 +12,3 @@ * Licensed under the MIT License at: | ||
* | ||
* @version 0.7.1 | ||
* @version 0.7.2 | ||
*/ | ||
@@ -61,5 +61,27 @@ (function (define) { | ||
advised = this.advised = function() { | ||
var context, args, result, afterType, exception; | ||
var context, args, callOrig, result, afterType, exception; | ||
context = this; | ||
// If called as a constructor (i.e. using "new"), create a context | ||
// of the correct type, so that all advice types (including before!) | ||
// are called with the correct context. | ||
// NOTE: Requires ES5 Object.create() | ||
if(this instanceof advised) { | ||
// shamelessly derived from https://github.com/cujojs/wire/blob/c7c55fe50238ecb4afbb35f902058ab6b32beb8f/lib/component.js#L25 | ||
if (!Object.create) { | ||
throw new Error('An ES5 environment is required for advice on constructors'); | ||
} | ||
context = Object.create(orig.prototype); | ||
callOrig = function (args) { | ||
return applyConstructor(orig, context, args); | ||
}; | ||
} else { | ||
context = this; | ||
callOrig = function(args) { | ||
return orig.apply(context, args); | ||
}; | ||
} | ||
args = slice.call(arguments); | ||
@@ -71,3 +93,3 @@ afterType = 'afterReturning'; | ||
try { | ||
result = advisor._callAroundAdvice(context, func, args, callOrig); | ||
result = advisor._callAroundAdvice(context, func, args, callOrigAndOn); | ||
} catch(e) { | ||
@@ -90,6 +112,4 @@ result = exception = e; | ||
function callOrig(args) { | ||
var result = context instanceof advised | ||
? callConstructor(orig, args) | ||
: orig.apply(context, args); | ||
function callOrigAndOn(args) { | ||
var result = callOrig(args); | ||
advisor._callSimpleAdvice('on', context, args); | ||
@@ -165,14 +185,5 @@ | ||
function callAround(around, i, args) { | ||
var proceed, joinpoint; | ||
var proceedCalled, joinpoint; | ||
/** | ||
* Create proceed function that calls the next around advice, or | ||
* the original. May be called multiple times, for example, in retry | ||
* scenarios | ||
* @param [args] {Array} optional arguments to use instead of the | ||
* original arguments | ||
*/ | ||
proceed = function (args) { | ||
return callNext(i - 1, args); | ||
}; | ||
proceedCalled = 0; | ||
@@ -185,3 +196,4 @@ // Joinpoint is immutable | ||
proceed: proceedCall, | ||
proceedApply: proceedApply | ||
proceedApply: proceedApply, | ||
proceedCount: proceedCount | ||
}); | ||
@@ -192,9 +204,43 @@ | ||
function proceedCall() { | ||
/** | ||
* The number of times proceed() has been called | ||
* @return {Number} | ||
*/ | ||
function proceedCount() { | ||
return proceedCalled; | ||
} | ||
/** | ||
* Proceed to the original method/function or the next around | ||
* advice using original arguments or new argument list if | ||
* arguments.length > 0 | ||
* @return {*} result of original method/function or next around advice | ||
*/ | ||
function proceedCall(/* newArg1, newArg2... */) { | ||
return proceed(arguments.length > 0 ? slice.call(arguments) : args); | ||
} | ||
/** | ||
* Proceed to the original method/function or the next around | ||
* advice using original arguments or new argument list if | ||
* newArgs is supplied | ||
* @param [newArgs] {Array} new arguments with which to proceed | ||
* @return {*} result of original method/function or next around advice | ||
*/ | ||
function proceedApply(newArgs) { | ||
return proceed(newArgs || args); | ||
} | ||
/** | ||
* Create proceed function that calls the next around advice, or | ||
* the original. May be called multiple times, for example, in retry | ||
* scenarios | ||
* @param [args] {Array} optional arguments to use instead of the | ||
* original arguments | ||
*/ | ||
function proceed(args) { | ||
proceedCalled++; | ||
return callNext(i - 1, args); | ||
} | ||
} | ||
@@ -212,3 +258,3 @@ | ||
var aspects, advisor, adviceType, advice, advices; | ||
var advisor, aspects; | ||
@@ -218,41 +264,10 @@ advisor = this; | ||
for(adviceType in iterators) { | ||
advice = aspect[adviceType]; | ||
insertAspect(aspects, aspect); | ||
if(advice) { | ||
advices = aspects[adviceType]; | ||
if(!advices) { | ||
aspects[adviceType] = advices = []; | ||
} | ||
advices.push({ | ||
aspect: aspect, | ||
advice: advice | ||
}); | ||
} | ||
} | ||
return { | ||
remove: function () { | ||
var adviceType, advices, count; | ||
var remaining = removeAspect(aspects, aspect); | ||
count = 0; | ||
for(adviceType in iterators) { | ||
advices = aspects[adviceType]; | ||
if(advices) { | ||
count += advices.length; | ||
for (var i = advices.length - 1; i >= 0; --i) { | ||
if (advices[i].aspect === aspect) { | ||
advices.splice(i, 1); | ||
--count; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
// If there are no aspects left, restore the original method | ||
if (!count) { | ||
if (!remaining) { | ||
advisor.remove(); | ||
@@ -275,6 +290,11 @@ } | ||
// Returns the advisor for the target object-function pair. A new advisor | ||
// will be created if one does not already exist. | ||
Advisor.get = function(target, func) { | ||
if(!(func in target)) { | ||
/** | ||
* Returns the advisor for the target object-function pair. A new advisor | ||
* will be created if one does not already exist. | ||
* @param target {*} target containing a method with tthe supplied methodName | ||
* @param methodName {String} name of method on target for which to get an advisor | ||
* @return {Object} existing or newly created advisor for the supplied method | ||
*/ | ||
Advisor.get = function(target, methodName) { | ||
if(!(methodName in target)) { | ||
return; | ||
@@ -285,6 +305,6 @@ } | ||
advised = target[func]; | ||
advised = target[methodName]; | ||
if(typeof advised !== 'function') { | ||
throw new Error('Advice can only be applied to functions: ' + func); | ||
throw new Error('Advice can only be applied to functions: ' + methodName); | ||
} | ||
@@ -294,4 +314,4 @@ | ||
if(!advisor) { | ||
advisor = new Advisor(target, func); | ||
target[func] = advisor.advised; | ||
advisor = new Advisor(target, methodName); | ||
target[methodName] = advisor.advised; | ||
} | ||
@@ -442,9 +462,58 @@ | ||
function callConstructor(C, args) { | ||
// shamelessly derived from https://github.com/cujojs/wire/blob/c7c55fe50238ecb4afbb35f902058ab6b32beb8f/lib/component.js#L25 | ||
if (!Object.create) { | ||
throw new Error('An ES5 environment is required for advice on constructors'); | ||
/** | ||
* Insert the supplied aspect into aspectList | ||
* @param aspectList {Object} list of aspects, categorized by advice type | ||
* @param aspect {Object} aspect containing one or more supported advice types | ||
*/ | ||
function insertAspect(aspectList, aspect) { | ||
var adviceType, advice, advices; | ||
for(adviceType in iterators) { | ||
advice = aspect[adviceType]; | ||
if(advice) { | ||
advices = aspectList[adviceType]; | ||
if(!advices) { | ||
aspectList[adviceType] = advices = []; | ||
} | ||
advices.push({ | ||
aspect: aspect, | ||
advice: advice | ||
}); | ||
} | ||
} | ||
var instance = Object.create(C.prototype); | ||
} | ||
/** | ||
* Remove the supplied aspect from aspectList | ||
* @param aspectList {Object} list of aspects, categorized by advice type | ||
* @param aspect {Object} aspect containing one or more supported advice types | ||
* @return {Number} Number of *advices* left on the advised function. If | ||
* this returns zero, then it is safe to remove the advisor completely. | ||
*/ | ||
function removeAspect(aspectList, aspect) { | ||
var adviceType, advices, remaining; | ||
remaining = 0; | ||
for(adviceType in iterators) { | ||
advices = aspectList[adviceType]; | ||
if(advices) { | ||
remaining += advices.length; | ||
for (var i = advices.length - 1; i >= 0; --i) { | ||
if (advices[i].aspect === aspect) { | ||
advices.splice(i, 1); | ||
--remaining; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
return remaining; | ||
} | ||
function applyConstructor(C, instance, args) { | ||
try { | ||
@@ -465,3 +534,3 @@ Object.defineProperty(instance, 'constructor', { | ||
function forEach(array, func) { | ||
for (var i = 0, len = array.length; i < len; ++i) { | ||
for (var i = 0, len = array.length; i < len; i++) { | ||
func(array[i]); | ||
@@ -468,0 +537,0 @@ } |
{ | ||
"name": "meld", | ||
"version": "0.7.1", | ||
"version": "0.7.2", | ||
"description": "AOP for JS with before, around, on, afterReturning, afterThrowing, after advice, and pointcut support", | ||
@@ -5,0 +5,0 @@ "keywords": ["aop", "aspect", "cujo"], |
@@ -7,2 +7,6 @@ [![Build Status](https://secure.travis-ci.org/cujojs/meld.png)](http://travis-ci.org/cujojs/meld) | ||
### 0.7.2 | ||
* Fix for context when advising constructors: `this` is now the constructed instance in all advice functions. | ||
### 0.7.1 | ||
@@ -9,0 +13,0 @@ |
@@ -64,4 +64,7 @@ (function(buster, aop) { | ||
// Calling joinpoint.proceed() multiple times is allowed | ||
assert.same(0, joinpoint.proceedCount()); | ||
refute.exception(joinpoint.proceed); | ||
assert.same(1, joinpoint.proceedCount()); | ||
refute.exception(joinpoint.proceed); | ||
assert.same(2, joinpoint.proceedCount()); | ||
}); | ||
@@ -68,0 +71,0 @@ |
(function(buster, meld) { | ||
"use strict"; | ||
var assert, refute; | ||
var assert, refute, arg; | ||
@@ -9,3 +9,4 @@ assert = buster.assert; | ||
var arg = {}; | ||
arg = {}; | ||
function method() {} | ||
@@ -15,5 +16,55 @@ // Test fixture | ||
this.prop = a; | ||
this.method = method; | ||
} | ||
Fixture.prototype = { | ||
prototypeMethod: function() {}, | ||
prototypeProperty: true | ||
}; | ||
function FixtureThrows(a) { | ||
throw new Error(a); | ||
} | ||
buster.testCase('constructors', { | ||
'should provide correct context to advice types': function() { | ||
var Advised, count; | ||
Advised = Fixture; | ||
count = 0; | ||
Advised = meld.before(Advised, verifyContext); | ||
Advised = meld.on(Advised, verifyContext); | ||
Advised = meld.around(Advised, verifyContextAround); | ||
Advised = meld.afterReturning(Advised, verifyContext); | ||
Advised = meld.after(Advised, verifyContext); | ||
new Advised(); | ||
assert.equals(count, 5); | ||
function verifyContext() { | ||
count++; | ||
assert(this instanceof Fixture); | ||
} | ||
function verifyContextAround(jp) { | ||
verifyContext.call(this); | ||
return jp.proceed(); | ||
} | ||
}, | ||
'should provide correct context to afterThrowing': function() { | ||
var Advised; | ||
Advised = meld.afterThrowing(FixtureThrows, verifyContext); | ||
assert.exception(function() { | ||
new Advised(); | ||
}); | ||
function verifyContext() { | ||
assert(this instanceof FixtureThrows); | ||
} | ||
}, | ||
'should advise a constructor using around advise': function() { | ||
@@ -43,5 +94,15 @@ var target, spy, ret; | ||
assert.called(spy); | ||
assert(ret instanceof Fixture); | ||
assert.same(Fixture, ret.constructor); | ||
assert.same(arg, ret.prop); | ||
assert.same(ret.constructor, Fixture); | ||
// Make sure prototype methods are preserved, and are | ||
// still on the prototype and have not been promoted to own props | ||
assert.same(ret.prototypeMethod, Fixture.prototype.prototypeMethod); | ||
refute(ret.hasOwnProperty('prototypeMethod')); | ||
// Make sure instance methods and props added in the constructor | ||
// are preserved | ||
assert.same(ret.method, method); | ||
assert.same(ret.prop, arg); | ||
}, | ||
@@ -64,4 +125,4 @@ | ||
assert(ret instanceof Fixture); | ||
assert.same(Fixture, ret.constructor); | ||
assert.same(arg, ret.prop); | ||
assert.same(ret.constructor, Fixture); | ||
assert.same(ret.prop, arg); | ||
}, | ||
@@ -78,4 +139,4 @@ | ||
assert(ret instanceof Fixture); | ||
assert.same(Fixture, ret.constructor); | ||
assert.same(arg, ret.prop); | ||
assert.same(ret.constructor, Fixture); | ||
assert.same(ret.prop, arg); | ||
} | ||
@@ -82,0 +143,0 @@ |
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
44729
1341
55
0