From 2136ebedd50082f1cf1da14db82ac554ec783ad8 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 15 Dec 2011 22:58:23 -0500 Subject: [PATCH] Now properly handling extending from objects and properly throwing errors for scalars --- lib/ClassBuilder.js | 34 +++++++++++++++++++++++++--------- test/test-class-extend.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index 868c384..50fd123 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -294,7 +294,7 @@ exports.prototype.build = function extend( _, __ ) var args = Array.prototype.slice.call( arguments ), props = args.pop() || {}, base = args.pop() || exports.ClassBase, - prototype = new base(), + prototype = this._getBase( base ), cname = '', prop_init = this._memberBuilder.initMembers(), @@ -309,12 +309,6 @@ exports.prototype.build = function extend( _, __ ) || { __length: 0 } ; - // must extend from constructor or class - if ( typeof base !== 'function' ) - { - throw TypeError( "Must extend from class or constructor"); - } - // prevent extending final classes if ( base.___$$final$$ === true ) { @@ -424,6 +418,27 @@ exports.prototype.build = function extend( _, __ ) }; +exports.prototype._getBase = function( base ) +{ + var type = ( typeof base ); + + switch ( type ) + { + // constructor (we could also check to ensure that the return value of + // the constructor is an object, but that is not our concern) + case 'function': + return new base(); + + // we can use objects as the prototype directly + case 'object': + return base; + } + + // scalars + throw TypeError( 'Must extend from Class, constructor or object' ); +}; + + exports.prototype.buildMembers = function buildMembers( props, class_id, base, prop_init, abstract_methods, members, static_members, staticInstLookup @@ -758,11 +773,12 @@ exports.prototype._attachPropInit = function( // defaults to false inherit = !!inherit; - var iid = this.__iid; + var iid = this.__iid, + parent = prototype.___$$parent$$; // first initialize the parent's properties, so that ours will overwrite // them - var parent_init = prototype.___$$parent$$.__initProps; + var parent_init = parent && parent.__initProps; if ( typeof parent_init === 'function' ) { // call the parent prop_init, letting it know that it's been diff --git a/test/test-class-extend.js b/test/test-class-extend.js index 50e9890..8b9add3 100644 --- a/test/test-class-extend.js +++ b/test/test-class-extend.js @@ -432,3 +432,34 @@ for ( var i = 0; i < class_count; i++ ) assert.fail( "Should not be permitted to override non-virtual method" ); } )(); + +/** + * If we attempt to extend an object (rather than a constructor), we should + * simply use that as the prototype directly rather than attempting to + * instantiate it. + */ +( function testExtendingObjectWillNotAttemptInstantiation() +{ + var obj = { foo: 'bar' }; + + assert.equal( obj.foo, Class.extend( obj, {} )().foo, + 'Should be able to use object as prototype' + ); +} )(); + + +/** + * It only makes sense to extend from an object or function (constructor, more + * specifically) + * + * We could also test to ensure that the return value of the constructor is an + * object, but that is unnecessary for the time being. + */ +( function testWillThrowExceptionIfNonObjectOrCtorIsProvided() +{ + assert['throws']( function() + { + Class.extend( 'foo', {} ); + }, TypeError, 'Should not be able to extend from non-object or non-ctor' ); +} )(); +