diff --git a/lib/class_final.js b/lib/class_final.js index 313e1f7..49b4b8a 100644 --- a/lib/class_final.js +++ b/lib/class_final.js @@ -24,18 +24,79 @@ var Class = require( __dirname + '/class' ); -module.exports = function() + +/** + * Creates a final class + * + * @return {Class} final class + */ +exports = module.exports = function() +{ + markFinal( arguments ); + + // forward everything to Class + var result = Class.apply( this, arguments ); + + if ( !Class.isClass( result ) ) + { + finalOverride( result ); + } + + return result; +}; + + +/** + * Creates a final class from a class extend operation + * + * @return {Class} final class + */ +exports.extend = function() +{ + markFinal( arguments ); + return Class.extend.apply( this, arguments ); +}; + + +/** + * Causes a definition to be flagged as final + * + * This function assumes the last argument to be the definition, which is the + * common case, and modifies the object referenced by that argument. + * + * @param {arguments} args arguments to parse + * + * @return {undefined} + */ +function markFinal( args ) { // the last argument _should_ be the definition - var dfn = arguments[ arguments.length - 1 ]; + var dfn = args[ args.length - 1 ]; if ( typeof dfn === 'object' ) { - // mark it as final + // mark as abstract dfn.___$$final$$ = true; } +} - // forward everything to Class - return Class.apply( this, arguments ); -}; + +/** + * Overrides object members to permit final classes + * + * @param {Object} obj object to override + * + * @return {undefined} + */ +function finalOverride( obj ) +{ + var extend = obj.extend; + + // wrap extend, applying the abstract flag + obj.extend = function() + { + markFinal( arguments ); + return extend.apply( this, arguments ); + }; +} diff --git a/test/test-class_builder-final.js b/test/test-class_builder-final.js index 648495e..627e37e 100644 --- a/test/test-class_builder-final.js +++ b/test/test-class_builder-final.js @@ -157,3 +157,27 @@ var common = require( './common' ), assert.fail( "Should not be able to extend final classes" ); } )(); + +/** + * Ensure we're able to create final classes by extending existing classes. + */ +( function testCanCreateFinalSubtypes() +{ + var Foo = builder.build( {} ), + FinalNamed = FinalClass( 'FinalNamed' ).extend( Foo, {} ), + FinalAnon = FinalClass.extend( Foo, {} ) + ; + + // named + assert.throws( function() + { + FinalNamed.extend( {} ); + }, Error, "Cannot extend final named subtype" ); + + // anonymous + assert.throws( function() + { + FinalAnon.extend( {} ); + }, Error, "Cannot extend final anonymous subtype" ); +} )(); +