diff --git a/lib/MemberBuilder.js b/lib/MemberBuilder.js index b5f9d28..83df3f4 100644 --- a/lib/MemberBuilder.js +++ b/lib/MemberBuilder.js @@ -133,7 +133,12 @@ exports.buildMethod = function( // we might be overriding an existing method if ( keywords[ 'proxy' ] ) { - dest[ name ] = this._createProxy( value, instCallback, cid, name ); + // TODO: Note that this is not compatible with method hiding, due to its + // positioning (see hideMethod() below); address once method hiding is + // implemented (the validators currently handle everything else) + dest[ name ] = this._createProxy( + value, instCallback, cid, name, keywords + ); } else if ( prev ) { @@ -373,15 +378,16 @@ function hideMethod( super_method, new_method, instCallback, cid ) * @param {Function} instCallback function to call in order to retrieve * object to bind 'this' keyword to * - * @param {number} cid class id - * @param {string} mname name of method to invoke on destination object + * @param {number} cid class id + * @param {string} mname name of method to invoke on destination object + * @param {Object} keywords method keywords * * @return {Function} proxy method */ -exports._createProxy = function( proxy_to, instCallback, cid, mname ) +exports._createProxy = function( proxy_to, instCallback, cid, mname, keywords ) { return this._wrapProxy.wrapMethod( - proxy_to, null, cid, instCallback, mname + proxy_to, null, cid, instCallback, mname, keywords ); }; diff --git a/lib/MethodWrapperFactory.js b/lib/MethodWrapperFactory.js index c1c6a80..906810b 100644 --- a/lib/MethodWrapperFactory.js +++ b/lib/MethodWrapperFactory.js @@ -56,11 +56,12 @@ module.exports = exports = function MethodWrapperFactory( factory ) * @param {function()} getInst function to determine instance and return * associated visibility object * @param {string=} name name of method + * @param {Object=} keywords method keywords */ exports.prototype.wrapMethod = function( - method, super_method, cid, getInst, name + method, super_method, cid, getInst, name, keywords ) { - return this._factory( method, super_method, cid, getInst, name ); + return this._factory( method, super_method, cid, getInst, name, keywords ); }; diff --git a/lib/MethodWrappers.js b/lib/MethodWrappers.js index 4d3ecd1..5e8466e 100644 --- a/lib/MethodWrappers.js +++ b/lib/MethodWrappers.js @@ -83,21 +83,29 @@ exports.standard = { }, - wrapProxy: function( proxy_to, _, cid, getInst, name ) + wrapProxy: function( proxy_to, _, cid, getInst, name, keywords ) { + // it is important that we store only a boolean value as to whether or + // not this method is static *outside* of the returned closure, so as + // not to keep an unnecessary reference to the keywords object + var is_static = keywords && keywords[ 'static' ]; + return function() { var context = getInst( this, cid ) || this, retval = undefined, - dest = context[ proxy_to ] + dest = ( ( is_static ) + ? context.$( proxy_to ) + : context[ proxy_to ] + ) ; // rather than allowing a cryptic error to be thrown, attempt to // detect when the proxy call will fail and provide a useful error // message - if ( ( typeof dest !== 'object' ) - || ( typeof dest[ name ] !== 'function' ) - ) + if ( !( ( dest !== null ) && ( typeof dest === 'object' ) + && ( typeof dest[ name ] === 'function' ) + ) ) { throw TypeError( "Unable to proxy " + name + "() call to '" + proxy_to + diff --git a/test/MemberBuilder/MethodTest.js b/test/MemberBuilder/MethodTest.js index ecaee7f..f138a6e 100644 --- a/test/MemberBuilder/MethodTest.js +++ b/test/MemberBuilder/MethodTest.js @@ -170,7 +170,7 @@ require( 'common' ).testCase( ); this.assertDeepEqual( - [ value, null, cid, instCallback, name ], + [ value, null, cid, instCallback, name, keywords ], this.proxyFactoryCall, "Proxy factory should be called with proper arguments" ); diff --git a/test/MethodWrapperFactoryTest.js b/test/MethodWrapperFactoryTest.js index 01d1da8..27d5a2c 100644 --- a/test/MethodWrapperFactoryTest.js +++ b/test/MethodWrapperFactoryTest.js @@ -65,11 +65,13 @@ var common = require( './common' ), cid = 55, getInst = function() {}, name = 'someMethod', + keywords = { 'static': true, 'public': true }, retval = 'foobar'; var result = Sut( function( - given_method, given_super, given_cid, givenGetInst, givenName + given_method, given_super, given_cid, givenGetInst, given_name, + given_keywords ) { called = true; @@ -90,13 +92,17 @@ var common = require( './common' ), "Factory method should be provided with proper inst function" ); - assert.equal( givenName, name, + assert.equal( given_name, name, "Factory method should be provided with proper method name" ); + assert.equal( given_keywords, keywords, + "Factory method should be provided with proper keywords" + ); + return retval; } - ).wrapMethod( method, super_method, cid, getInst, name ); + ).wrapMethod( method, super_method, cid, getInst, name, keywords ); // we'll include this in addition to the following assertion (which is // redundant) to make debugging more clear diff --git a/test/MethodWrappersTest.js b/test/MethodWrappersTest.js index a5a1564..ba481ed 100644 --- a/test/MethodWrappersTest.js +++ b/test/MethodWrappersTest.js @@ -342,3 +342,42 @@ function proxyErrorAssertCommon( e, prop, method ) ); } )(); + +/** + * If the `static' keyword is provided, then the proxy mustn't operate on + * instance properties. Instead, the static accessor method $() must be used. + */ +( function testCanProxyToStaticMembers() +{ + var getInst = function() + { + // pretend that we're a static class with a static accessor method + return { + $: function( name ) + { + // implicitly tests that the argument is properly passed + // (would otherwise return `undefined`) + return s[ name ]; + }, + }; + }, + + keywords = { 'static': true }; + + val = [ 'value' ], + s = { + // destination object + foo: { + method: function() + { + return val; + }, + } + }; + + assert.strictEqual( val, + sut.standard.wrapProxy( 'foo', null, 0, getInst, 'method', keywords )(), + "Should properly proxy to static membesr via static accessor method" + ); +} )(); +