From 96d2f74dae266e4aadaa6d38ac3129705e8101ec Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 21 Jan 2011 23:16:20 -0500 Subject: [PATCH] Method builder wraps overrides in an override function --- lib/member_builder.js | 62 ++++++++++++++++++++++++++++-- test/test-member_builder-method.js | 45 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/lib/member_builder.js b/lib/member_builder.js index 891d9a7..df4c623 100644 --- a/lib/member_builder.js +++ b/lib/member_builder.js @@ -22,7 +22,8 @@ * @package core */ -var visibility = [ 'public', 'protected', 'private' ]; +var util = require( './util' ), + visibility = [ 'public', 'protected', 'private' ]; /** @@ -74,7 +75,7 @@ exports.buildMethod = function( members, meta, name, value, keywords ) } // ensure parameter list is at least the length of its supertype - if ( prev && (value.length < prev.length ) ) + if ( prev && ( value.length < ( prev.__length || prev.length ) ) ) { throw new TypeError( "Declaration of method '" + name + "' must be compatiable " + @@ -82,7 +83,19 @@ exports.buildMethod = function( members, meta, name, value, keywords ) ); } - getMemberVisibility( members, keywords )[ name ] = value; + var dest = getMemberVisibility( members, keywords ); + + // we might be overriding an existing method + if ( prev ) + { + // override the method + dest[ name ] = overrideMethod( prev, value ); + } + else + { + // we are not overriding the method, so simply copy it over + dest[ name ] = value; + } }; @@ -220,3 +233,46 @@ function scanMembers( members, name ) return undefined; } + +/** + * Generates a method override function + * + * The override function simply wraps the method so that its invocation will + * pass a __super property. This property may be used to invoke the overridden + * method. + * + * @param {function()} super_method method to override + * @param {function()} new_method method to override with + * + * @return {function()} override method + */ +function overrideMethod( super_method, new_method ) +{ + // return a function that permits referencing the super method via the + // __super property + var override = function() + { + var tmp = this.__super; + + // assign _super temporarily for the method invocation so + // that the method can call the parent method + this.__super = super_method; + var retval = new_method.apply( this, arguments ); + this.__super = tmp; + + return retval; + }; + + // This is a trick to work around the fact that we cannot set the length + // property of a function. Instead, we define our own property - __length. + // This will store the expected number of arguments from the super method. + // This way, when a method is being overridden, we can check to ensure its + // compatibility with its super method. + util.defineSecureProp( override, + '__length', + ( super_method.__length || super_method.length ) + ); + + return override; +} + diff --git a/test/test-member_builder-method.js b/test/test-member_builder-method.js index fabe858..571cd5b 100644 --- a/test/test-member_builder-method.js +++ b/test/test-member_builder-method.js @@ -89,3 +89,48 @@ mb_common.assertCommon(); }, TypeError, "Method cannot have lesser number of parameters" ); } )(); + +/** + * The __super property is defined for method overrides and permits invoking the + * overridden method (method of the supertype). + * + * In this test, we are not looking to assert that __super matches the super + * method. Rather, we want to ensure it /invokes/ it. This is because the super + * method may be wrapped to provide additional functionality. We don't know, we + * don't care. We just want to make sure it's functioning properly. + */ +( function testOverridenMethodShouldContainReferenceToSuperMethod() +{ + var orig_called = false; + + // "super" method + mb_common.value = function() + { + orig_called = true; + }; + + mb_common.buildMemberQuick(); + + // override method + mb_common.value = function() + { + assert.notEqual( + this.__super, + undefined, + "__super is defined for overridden method" + ); + + this.__super(); + assert.equal( + orig_called, + true, + "Invoking __super calls super method" + ); + }; + + mb_common.buildMemberQuick( {}, true ); + + // invoke the method + mb_common.members[ 'public' ][ mb_common.name ](); +} )(); +