From 840a49501787a7a57b62836afab59ae5844e73ca Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Thu, 3 Mar 2011 22:33:18 -0500 Subject: [PATCH] Began implementing named classes - toString() implementation --- lib/class.js | 88 +++++++++++++++++++++---------- test/test-class-abstract.js | 12 ----- test/test-class-extend.js | 12 ----- test/test-class-name.js | 100 ++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 test/test-class-name.js diff --git a/lib/class.js b/lib/class.js index 62fd495..3637a33 100644 --- a/lib/class.js +++ b/lib/class.js @@ -47,24 +47,47 @@ var class_meta = {}; * * @return {Class} new class */ -module.exports = function( def ) +module.exports = function() { - // the class definition should be an object - if ( typeof def !== 'object' ) - { - throw TypeError( - "Must provide class definition when declaring a new class" - ); - } + var def = {}, + name = ''; - // 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 - // they're not getting the result they're looking for) - if ( arguments.length > 1 ) + // anonymous class + if ( typeof arguments[ 0 ] === 'object' ) { - throw Error( - "Expecting one argument for Class definition; " + - arguments.length + " given." + def = arguments[ 0 ]; + + // 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 + // they're not getting the result they're looking for) + if ( arguments.length > 1 ) + { + throw Error( + "Expecting one argument for Class definition; " + + 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" ); } @@ -248,6 +271,7 @@ var extend = ( function( extending ) props = args.pop() || {}, base = args.pop() || Class, prototype = new base(), + cname = '', hasOwn = Array.prototype.hasOwnProperty; @@ -261,6 +285,13 @@ var extend = ( function( extending ) || { __length: 0 } ; + // grab the name, if one was provided + if ( cname = props.__name ) + { + // we no longer need it + delete props.__name; + } + util.propParse( props, { each: function( name, value, keywords ) { @@ -328,7 +359,7 @@ var extend = ( function( extending ) prototype.parent = base.prototype; // set up the new class - var new_class = createCtor( abstract_methods ); + var new_class = createCtor( cname, abstract_methods ); attachPropInit( prototype, properties ); @@ -345,6 +376,7 @@ var extend = ( function( extending ) // create internal metadata for the new class var meta = createMeta( new_class, base.prototype.__cid ); meta.abstractMethods = abstract_methods; + meta.name = cname; // we're done with the extension process extending = false; @@ -359,11 +391,12 @@ var extend = ( function( extending ) * This constructor will call the __constructor method for concrete classes * and throw an exception for abstract classes (to prevent instantiation). * + * @param {string} cname class name (may be empty) * @param {Array.} abstract_methods list of abstract methods * * @return {Function} constructor */ - function createCtor( abstract_methods ) + function createCtor( cname, abstract_methods ) { // concrete class if ( abstract_methods.__length === 0 ) @@ -398,10 +431,10 @@ var extend = ( function( extending ) }; // provide a more intuitive string representation - __self.toString = function() - { - return ''; - }; + __self.toString = ( cname ) + ? function() { return ''; } + : function() { return ''; } + ; return __self; } @@ -412,15 +445,14 @@ var extend = ( function( 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 = function() - { - return ''; - }; + __abstract_self.toString = ( cname ) + ? function() { return ''; } + : function() { return ''; } + ; return __abstract_self; } @@ -476,7 +508,7 @@ var implement = function() /** * 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.} abstract_methods list of abstract method names * @param {number} class_id unique id to assign to class * diff --git a/test/test-class-abstract.js b/test/test-class-abstract.js index 4ce1a05..011e271 100644 --- a/test/test-class-abstract.js +++ b/test/test-class-abstract.js @@ -181,18 +181,6 @@ assert.throws( function() }, 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(), - '', - "Converting abstract class to string yields class string" - ); -} )(); - - /** * 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 diff --git a/test/test-class-extend.js b/test/test-class-extend.js index 84ab8a1..a82f61f 100644 --- a/test/test-class-extend.js +++ b/test/test-class-extend.js @@ -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(), - '', - "Converting class to string yields class string" - ); -} )(); - - ( function testInvokingClassModuleRequiresObjectAsArgumentIfCreating() { assert.throws( function() diff --git a/test/test-class-name.js b/test/test-class-name.js new file mode 100644 index 0000000..da600ae --- /dev/null +++ b/test/test-class-name.js @@ -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 . + * + * @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(), + '', + "Converting anonymous class to string yields class string" + ); + + // abstract + assert.equal( + Class( { 'abstract foo': [] } ).toString(), + '', + "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(), + '', + "Converting named class to string yields string with name of class" + ); + + // abstract + assert.equal( + Class( name, { 'abstract foo': [] } ).toString(), + '', + "Converting abstract named class to string yields string with name " + + "of class" + ); +} )(); +