Added support for final classes
- This commit was originally many. Unfortunately, certain Git objects became corrupt shortly after my 500th commit due to HDD issues. Due to the scope, I was unable to recover the set of commits I needed (after an hour of trying every method). - Fortunately, vim's swap files came to the rescue. Had I been able to properly shut down my PC, I would have been rather frustrated.closure/master
parent
0ef016600c
commit
9690663d1c
|
@ -200,6 +200,15 @@ exports.build = function extend()
|
|||
|| { __length: 0 }
|
||||
;
|
||||
|
||||
// prevent extending final classes
|
||||
if ( base.___$$final$$ === true )
|
||||
{
|
||||
throw Error(
|
||||
"Cannot extend final class " +
|
||||
( base.___$$meta$$.name || '(anonymous)' )
|
||||
);
|
||||
}
|
||||
|
||||
// grab the name, if one was provided
|
||||
if ( cname = props.__name )
|
||||
{
|
||||
|
@ -252,6 +261,8 @@ exports.build = function extend()
|
|||
new_class.___$$methods$$ = members;
|
||||
new_class.___$$sinit$$ = staticInit;
|
||||
|
||||
attachFlags( new_class, props );
|
||||
|
||||
// We reduce the overall cost of this definition by defining it on the
|
||||
// prototype rather than during instantiation. While this does increase the
|
||||
// amount of time it takes to access the property through the prototype
|
||||
|
@ -1032,3 +1043,21 @@ function attachId( ctor, id )
|
|||
util.defineSecureProp( ctor.prototype, '__cid', id );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets class flags
|
||||
*
|
||||
* @param {Class} ctor class to flag
|
||||
* @param {Object} props class properties
|
||||
*
|
||||
* @return {undefined}
|
||||
*/
|
||||
function attachFlags( ctor, props )
|
||||
{
|
||||
ctor.___$$final$$ = !!( props.___$$final$$ );
|
||||
|
||||
// The properties are no longer needed. Set to undefined rather than delete
|
||||
// (v8 performance)
|
||||
props.___$$final$$ = undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Wrapper permitting the definition of final classes
|
||||
*
|
||||
* Copyright (C) 2010 Mike Gerwitz
|
||||
*
|
||||
* This file is part of ease.js.
|
||||
*
|
||||
* ease.js is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Mike Gerwitz
|
||||
* @package core
|
||||
*/
|
||||
|
||||
var Class = require( __dirname + '/class' );
|
||||
|
||||
module.exports = function()
|
||||
{
|
||||
// the last argument _should_ be the definition
|
||||
var dfn = arguments[ arguments.length - 1 ];
|
||||
|
||||
if ( typeof dfn === 'object' )
|
||||
{
|
||||
// mark it as final
|
||||
dfn.___$$final$$ = true;
|
||||
}
|
||||
|
||||
// forward everything to Class
|
||||
return Class.apply( this, arguments );
|
||||
};
|
||||
|
|
@ -81,13 +81,24 @@ exports.buildMethod = function(
|
|||
: {}
|
||||
;
|
||||
|
||||
// do not permit private abstract methods (doesn't make sense, since
|
||||
// they cannot be inherited/overridden)
|
||||
if ( keywords[ 'abstract' ] && keywords[ 'private' ] )
|
||||
if ( keywords[ 'abstract' ] )
|
||||
{
|
||||
throw TypeError(
|
||||
"Method '" + name + "' cannot be both private and abstract"
|
||||
);
|
||||
// do not permit private abstract methods (doesn't make sense, since
|
||||
// they cannot be inherited/overridden)
|
||||
if ( keywords[ 'private' ] )
|
||||
{
|
||||
throw TypeError(
|
||||
"Method '" + name + "' cannot be both private and abstract"
|
||||
);
|
||||
}
|
||||
|
||||
// abstract final also does not make sense
|
||||
if ( keywords[ 'final' ] )
|
||||
{
|
||||
throw TypeError(
|
||||
"Method '" + name + "' cannot be both abstract and final"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// const doesn't make sense for methods; they're always immutable
|
||||
|
|
|
@ -24,7 +24,10 @@
|
|||
|
||||
var common = require( './common' ),
|
||||
assert = require( 'assert' ),
|
||||
builder = common.require( 'class_builder' )
|
||||
builder = common.require( 'class_builder' ),
|
||||
|
||||
Class = common.require( 'class' )
|
||||
FinalClass = common.require( 'class_final' )
|
||||
;
|
||||
|
||||
|
||||
|
@ -83,7 +86,7 @@ var common = require( './common' ),
|
|||
{
|
||||
assert.ok(
|
||||
e.message.search( 'foo' ) !== -1,
|
||||
"Final property error message contains name of method"
|
||||
"Final property error message contains name of property"
|
||||
);
|
||||
|
||||
return;
|
||||
|
@ -92,3 +95,65 @@ var common = require( './common' ),
|
|||
assert.fail( "Should not be able to use final keyword with properties" );
|
||||
} )();
|
||||
|
||||
|
||||
/**
|
||||
* The 'abstract' keyword's very point is to state that no definition is
|
||||
* provided and that a subtype must provide one. Therefore, declaring something
|
||||
* 'abstract final' is rather contradictory and should not be permitted.
|
||||
*/
|
||||
( function testFinalyKeywordCannotBeUsedWithAbstract()
|
||||
{
|
||||
try
|
||||
{
|
||||
// should fail
|
||||
builder.build( { 'abstract final foo': [] } );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
assert.ok(
|
||||
e.message.search( 'foo' ) !== -1,
|
||||
"Abstract final error message contains name of method"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert.fail( "Should not be able to use final keyword with abstract" );
|
||||
} )();
|
||||
|
||||
|
||||
/**
|
||||
* Ensure that FinalClass properly forwards data to create a new Class.
|
||||
*/
|
||||
( function testFinalClassesAreValidClasses()
|
||||
{
|
||||
assert.ok( Class.isClass( FinalClass( {} ) ),
|
||||
"Final classes should generate valid classes"
|
||||
);
|
||||
} )();
|
||||
|
||||
|
||||
/**
|
||||
* When a class is declared as final, it should prevent it from ever being
|
||||
* extended. Ever.
|
||||
*/
|
||||
( function testFinalClassesCannotBeExtended()
|
||||
{
|
||||
try
|
||||
{
|
||||
// this should fail
|
||||
FinalClass( 'Foo', {} ).extend( {} );
|
||||
}
|
||||
catch ( e )
|
||||
{
|
||||
assert.ok(
|
||||
e.message.search( 'Foo' ) !== -1,
|
||||
"Final class error message should contain name of class"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert.fail( "Should not be able to extend final classes" );
|
||||
} )();
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ RMTRAIL="$PATH_TOOLS/rmtrail"
|
|||
|
||||
# order matters
|
||||
CAT_MODULES="prop_parser util propobj member_builder class_builder"
|
||||
CAT_MODULES="$CAT_MODULES class interface"
|
||||
CAT_MODULES="$CAT_MODULES class class_final interface"
|
||||
|
||||
##
|
||||
# Output template header
|
||||
|
|
Loading…
Reference in New Issue