From a67d7048379b8d67a71c9eac5613ef88529a1c0b Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sun, 22 May 2011 21:05:46 -0400 Subject: [PATCH] Added support for named abstract subclasses --- lib/class_abstract.js | 42 +++++++++++++++++++------ lib/class_builder.js | 2 +- test/test-class-abstract.js | 63 ++++++++++++++++++++++++++++++++++++- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/lib/class_abstract.js b/lib/class_abstract.js index a0cfa0b..e7c2107 100644 --- a/lib/class_abstract.js +++ b/lib/class_abstract.js @@ -39,7 +39,16 @@ module.exports = exports = function() markAbstract( arguments ); // forward everything to Class - return Class.apply( this, arguments ); + var result = Class.apply( this, arguments ); + + // if we're using the temporary object, then override its methods to permit + // abstract classes + if ( !Class.isClass( result ) ) + { + abstractOverride( result ); + } + + return result; }; @@ -64,16 +73,9 @@ exports.extend = function() */ exports.implement = function() { - var impl = Class.implement.apply( this, arguments ), - extend = impl.extend; - - // wrap extend, applying the abstract flag - impl.extend = function() - { - markAbstract( arguments ); - return extend.apply( this, arguments ); - }; + var impl = Class.implement.apply( this, arguments ); + abstractOverride( impl ); return impl; }; @@ -100,3 +102,23 @@ function markAbstract( args ) } } + +/** + * Overrides object members to permit abstract classes + * + * @param {Object} obj object to override + * + * @return {undefined} + */ +function abstractOverride( obj ) +{ + var extend = obj.extend; + + // wrap extend, applying the abstract flag + obj.extend = function() + { + markAbstract( arguments ); + return extend.apply( this, arguments ); + }; +} + diff --git a/lib/class_builder.js b/lib/class_builder.js index 03565f8..32160e1 100644 --- a/lib/class_builder.js +++ b/lib/class_builder.js @@ -1004,7 +1004,7 @@ exports.isInstanceOf = function( type, instance ) try { - // check prototype chain (with throw an error if type is not a + // check prototype chain (will throw an error if type is not a // constructor (function) if ( instance instanceof type ) { diff --git a/test/test-class-abstract.js b/test/test-class-abstract.js index a85781d..85c5734 100644 --- a/test/test-class-abstract.js +++ b/test/test-class-abstract.js @@ -106,14 +106,27 @@ var common = require( './common' ), } )(); -( function testAbstractClassContainsExtendMethod() +/** + * Just as Class contains an extend method, so should AbstractClass. + */ +( function testAbstractClassExtendMethodReturnsNewClass() { assert.ok( typeof AbstractClass.extend === 'function', "AbstractClass contains extend method" ); + + assert.ok( + Class.isClass( + AbstractClass.extend( { 'abstract foo': [] } ) + ), + "Abstract class extend method returns class" + ); } )(); +/** + * Just as Class contains an implement method, so should AbstractClass. + */ ( function testAbstractClassContainsImplementMethod() { assert.ok( typeof AbstractClass.implement === 'function', @@ -363,3 +376,51 @@ var ConcreteFoo = Class.extend( AbstractFoo, }, Error, "Should not throw error if overriding a prototype method" ); } )(); + +/** + * Ensure we support named abstract class extending + */ +( function testCanCreateNamedAbstractSubtypes() +{ + assert.doesNotThrow( function() + { + var cls = AbstractClass( 'NamedSubFoo' ).extend( AbstractFoo, {} ); + }, Error, "Can create named abstract subtypes" ); +} )(); + + +/** + * Abstract classes, when extended, should yield a concrete class by default. + * Otherwise, the user should once again use AbstractClass to clearly state that + * the subtype is abstract. + */ +( function testExtendingAbstractClassIsNotAbstractByDefault() +{ + var cls_named = AbstractClass( 'NamedSubFoo' ).extend( AbstractFoo, {} ), + anon_named = AbstractClass.extend( AbstractFoo, {} ); + + // named + assert.throws( + function() + { + // should throw an error, since we're not declaring it as abstract + // and we're not providing a concrete impl + Class.isAbstract( cls_named.extend( {} ) ); + }, + TypeError, + "Extending named abstract classes should be concrete by default" + ); + + // anonymous + assert.throws( + function() + { + // should throw an error, since we're not declaring it as abstract + // and we're not providing a concrete impl + Class.isAbstract( AbstractFoo.extend( {} ) ); + }, + TypeError, + "Extending anonymous abstract classes should be concrete by default" + ); +} )(); +