1
0
Fork 0

Began implementing named classes

- toString() implementation
closure/master
Mike Gerwitz 2011-03-03 22:33:18 -05:00
parent b96aaa35d9
commit 840a495017
4 changed files with 160 additions and 52 deletions

View File

@ -47,15 +47,15 @@ var class_meta = {};
* *
* @return {Class} new class * @return {Class} new class
*/ */
module.exports = function( def ) module.exports = function()
{ {
// the class definition should be an object var def = {},
if ( typeof def !== 'object' ) name = '';
// anonymous class
if ( typeof arguments[ 0 ] === 'object' )
{ {
throw TypeError( def = arguments[ 0 ];
"Must provide class definition when declaring a new class"
);
}
// ensure we have the proper number of arguments (if they passed in too // ensure we have the proper number of arguments (if they passed in too
// many, it may signify that they don't know what they're doing, and likely // many, it may signify that they don't know what they're doing, and likely
@ -67,6 +67,29 @@ module.exports = function( def )
arguments.length + " given." arguments.length + " given."
); );
} }
}
// named class
else if ( typeof arguments[ 0 ] === 'string' )
{
name = arguments[ 0 ];
def = arguments[ 1 ];
// add the name to the definition
def.__name = name;
// the definition must be an object
if ( typeof def !== 'object' )
{
throw TypeError( "Unexpected value for named class definition" );
}
}
else
{
// we don't know what to do!
throw TypeError(
"Expecting anonymous class definition or named class definition"
);
}
return extend( def ); return extend( def );
}; };
@ -248,6 +271,7 @@ var extend = ( function( extending )
props = args.pop() || {}, props = args.pop() || {},
base = args.pop() || Class, base = args.pop() || Class,
prototype = new base(), prototype = new base(),
cname = '',
hasOwn = Array.prototype.hasOwnProperty; hasOwn = Array.prototype.hasOwnProperty;
@ -261,6 +285,13 @@ var extend = ( function( extending )
|| { __length: 0 } || { __length: 0 }
; ;
// grab the name, if one was provided
if ( cname = props.__name )
{
// we no longer need it
delete props.__name;
}
util.propParse( props, { util.propParse( props, {
each: function( name, value, keywords ) each: function( name, value, keywords )
{ {
@ -328,7 +359,7 @@ var extend = ( function( extending )
prototype.parent = base.prototype; prototype.parent = base.prototype;
// set up the new class // set up the new class
var new_class = createCtor( abstract_methods ); var new_class = createCtor( cname, abstract_methods );
attachPropInit( prototype, properties ); attachPropInit( prototype, properties );
@ -345,6 +376,7 @@ var extend = ( function( extending )
// create internal metadata for the new class // create internal metadata for the new class
var meta = createMeta( new_class, base.prototype.__cid ); var meta = createMeta( new_class, base.prototype.__cid );
meta.abstractMethods = abstract_methods; meta.abstractMethods = abstract_methods;
meta.name = cname;
// we're done with the extension process // we're done with the extension process
extending = false; extending = false;
@ -359,11 +391,12 @@ var extend = ( function( extending )
* This constructor will call the __constructor method for concrete classes * This constructor will call the __constructor method for concrete classes
* and throw an exception for abstract classes (to prevent instantiation). * and throw an exception for abstract classes (to prevent instantiation).
* *
* @param {string} cname class name (may be empty)
* @param {Array.<string>} abstract_methods list of abstract methods * @param {Array.<string>} abstract_methods list of abstract methods
* *
* @return {Function} constructor * @return {Function} constructor
*/ */
function createCtor( abstract_methods ) function createCtor( cname, abstract_methods )
{ {
// concrete class // concrete class
if ( abstract_methods.__length === 0 ) if ( abstract_methods.__length === 0 )
@ -398,10 +431,10 @@ var extend = ( function( extending )
}; };
// provide a more intuitive string representation // provide a more intuitive string representation
__self.toString = function() __self.toString = ( cname )
{ ? function() { return '<class ' + cname + '>'; }
return '<Class>'; : function() { return '<Class>'; }
}; ;
return __self; return __self;
} }
@ -412,15 +445,14 @@ var extend = ( function( extending )
{ {
if ( !extending ) if ( !extending )
{ {
throw new Error( "Abstract classes cannot be instantiated" ); throw Error( "Abstract classes cannot be instantiated" );
} }
}; };
// provide a more intuitive string representation __abstract_self.toString = ( cname )
__abstract_self.toString = function() ? function() { return '<abstract class ' + cname + '>'; }
{ : function() { return '<AbstractClass>'; }
return '<Abstract Class>'; ;
};
return __abstract_self; return __abstract_self;
} }
@ -476,7 +508,7 @@ var implement = function()
/** /**
* Sets up common properties for the provided function (class) * Sets up common properties for the provided function (class)
* *
* @param {Function} func function (class) to set up * @param {function()} func function (class) to set up
* @param {Array.<string>} abstract_methods list of abstract method names * @param {Array.<string>} abstract_methods list of abstract method names
* @param {number} class_id unique id to assign to class * @param {number} class_id unique id to assign to class
* *

View File

@ -181,18 +181,6 @@ assert.throws( function()
}, TypeError, "Abstract methods must be declared as arrays" ); }, TypeError, "Abstract methods must be declared as arrays" );
// otherwise it'll output the internal constructor code, which is especially
// confusing since the user does not write it
( function testConvertingAbstractClassToStringYieldsClassString()
{
assert.equal(
Class.extend( { 'abstract foo': [] } ).toString(),
'<Abstract Class>',
"Converting abstract class to string yields class string"
);
} )();
/** /**
* There was an issue where the object holding the abstract methods list was not * There was an issue where the object holding the abstract methods list was not
* checking for methods by using hasOwnProperty(). Therefore, if a method such * checking for methods by using hasOwnProperty(). Therefore, if a method such

View File

@ -244,18 +244,6 @@ for ( var i = 0; i < class_count; i++ )
} }
// otherwise it'll output the internal constructor code, which is especially
// confusing since the user does not write it
( function testConvertingClassToStringYieldsClassString()
{
assert.equal(
Class.extend( {} ).toString(),
'<Class>',
"Converting class to string yields class string"
);
} )();
( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating() ( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating()
{ {
assert.throws( function() assert.throws( function()

View File

@ -0,0 +1,100 @@
/**
* Tests class naming
*
* 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 test
*/
var common = require( './common' ),
assert = require( 'assert' ),
Class = common.require( 'class' )
;
/**
* Classes may be named by passing the name as the first argument to the module
*/
( function testClassAcceptsName()
{
assert.doesNotThrow( function()
{
var cls = Class( 'Foo', {} );
assert.equal(
Class.isClass( cls ),
true,
"Class defined with name is returned as a valid class"
);
}, Error, "Class accepts name" );
// the second argument must be an object
assert.throws( function()
{
Class( 'Foo', 'Bar' );
}, TypeError, "Second argument to named class must be the definition" );
} )();
/**
* By default, anonymous classes should just state that they are a class when
* they are converted to a string
*/
( function testConvertingAnonymousClassToStringYieldsClassString()
{
// concrete
assert.equal(
Class( {} ).toString(),
'<Class>',
"Converting anonymous class to string yields class string"
);
// abstract
assert.equal(
Class( { 'abstract foo': [] } ).toString(),
'<AbstractClass>',
"Converting abstract anonymous class to string yields class string"
);
} )();
/**
* If the class is named, then the name should be presented when it is converted
* to a string
*/
( function testConvertingNamedClassToStringYieldsClassStringContainingName()
{
var name = 'Foo';
// concrete
assert.equal(
Class( name, {} ).toString(),
'<class ' + name + '>',
"Converting named class to string yields string with name of class"
);
// abstract
assert.equal(
Class( name, { 'abstract foo': [] } ).toString(),
'<abstract class ' + name + '>',
"Converting abstract named class to string yields string with name " +
"of class"
);
} )();