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 }
|
|| { __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
|
// grab the name, if one was provided
|
||||||
if ( cname = props.__name )
|
if ( cname = props.__name )
|
||||||
{
|
{
|
||||||
|
@ -252,6 +261,8 @@ exports.build = function extend()
|
||||||
new_class.___$$methods$$ = members;
|
new_class.___$$methods$$ = members;
|
||||||
new_class.___$$sinit$$ = staticInit;
|
new_class.___$$sinit$$ = staticInit;
|
||||||
|
|
||||||
|
attachFlags( new_class, props );
|
||||||
|
|
||||||
// We reduce the overall cost of this definition by defining it on the
|
// We reduce the overall cost of this definition by defining it on the
|
||||||
// prototype rather than during instantiation. While this does increase the
|
// prototype rather than during instantiation. While this does increase the
|
||||||
// amount of time it takes to access the property through the prototype
|
// 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 );
|
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
|
if ( keywords[ 'abstract' ] )
|
||||||
// they cannot be inherited/overridden)
|
|
||||||
if ( keywords[ 'abstract' ] && keywords[ 'private' ] )
|
|
||||||
{
|
{
|
||||||
throw TypeError(
|
// do not permit private abstract methods (doesn't make sense, since
|
||||||
"Method '" + name + "' cannot be both private and abstract"
|
// 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
|
// const doesn't make sense for methods; they're always immutable
|
||||||
|
|
|
@ -24,7 +24,10 @@
|
||||||
|
|
||||||
var common = require( './common' ),
|
var common = require( './common' ),
|
||||||
assert = require( 'assert' ),
|
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(
|
assert.ok(
|
||||||
e.message.search( 'foo' ) !== -1,
|
e.message.search( 'foo' ) !== -1,
|
||||||
"Final property error message contains name of method"
|
"Final property error message contains name of property"
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -92,3 +95,65 @@ var common = require( './common' ),
|
||||||
assert.fail( "Should not be able to use final keyword with properties" );
|
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
|
# order matters
|
||||||
CAT_MODULES="prop_parser util propobj member_builder class_builder"
|
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
|
# Output template header
|
||||||
|
|
Loading…
Reference in New Issue