diff --git a/index.js b/index.js
index cd7d1c5..66a3f40 100644
--- a/index.js
+++ b/index.js
@@ -1,7 +1,7 @@
/**
* Provides ease of access to all submodules
*
- * Copyright (C) 2010, 2011, 2013, 2014 Free Software Foundation, Inc.
+ * Copyright (C) 2010, 2011, 2013, 2014, 2015 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@@ -19,10 +19,26 @@
* along with this program. If not, see .
*/
+/**
+ * Wrap a prototype using ease.js
+ *
+ * This function is the entry point for ease.js; its fields expose all of
+ * its core features. When invoked, it wraps the given prototype using
+ * ease.js, producing an ease.js Class. This is more natural when using the
+ * ECMAScript 6 `class` syntax to define prototypes.
+ *
+ * @param {Function} proto prototype to wrap
+ *
+ * @return {Function} ease.js Class wrapping PROTO
+ */
+var exports = module.exports = function( proto )
+{
+ return exports.Class.extend( proto, {} );
+};
+
exports.Class = require( './lib/class' );
exports.AbstractClass = require( './lib/class_abstract' );
exports.FinalClass = require( './lib/class_final' );
exports.Interface = require( './lib/interface' );
exports.Trait = require( './lib/Trait' );
exports.version = require( './lib/version' );
-
diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js
index cb0331a..1789592 100644
--- a/lib/ClassBuilder.js
+++ b/lib/ClassBuilder.js
@@ -28,6 +28,8 @@ var util = require( './util' ),
Warning = require( './warn' ).Warning,
Symbol = require( './util/Symbol' ),
+ parseKeywords = require( './prop_parser' ).parseKeywords,
+
hasOwn = Object.prototype.hasOwnProperty,
@@ -50,15 +52,27 @@ var util = require( './util' ),
/**
* Hash of reserved members
*
- * These methods cannot be defined in the class. They are for internal use
- * only. We must check both properties and methods to ensure that neither is
- * defined.
+ * These methods cannot be defined in the class; they are for internal
+ * use only. We must check both properties and methods to ensure that
+ * neither is defined.
*
* @type {Object.}
*/
reserved_members = {
'__initProps': true,
- 'constructor': true,
+ },
+
+ /**
+ * Hash of aliased members
+ *
+ * These are members that alias to another. Ideally, this should be a
+ * very small list. It is useful for introducing features without
+ * deprecating old.
+ *
+ * @type {Object.}
+ */
+ aliased_members = {
+ 'constructor': '__construct',
},
/**
@@ -641,12 +655,56 @@ exports.prototype.buildMembers = function buildMembers(
parser.method && hjoin( 'method', handlers.method );
}
+ handlers.keywordParser = _keywordParser;
+
// parse members and process accumulated member state
util.propParse( props, handlers, context );
this._memberBuilder.end( context.state );
}
+/**
+ * Member keyword parser
+ *
+ * In reality, this parser is simply intended to override names where there
+ * are applicable aliases; all keyword parsing is kept to the original
+ * implementation.
+ *
+ * @param {string} prop property to parse
+ *
+ * @return {{name: string, bitwords: number, keywords: Object.}}
+ */
+function _keywordParser( prop )
+{
+ var result = parseKeywords( prop ),
+ alias = _getMemberAlias( result.name );
+
+ if ( alias !== undefined )
+ {
+ result.name = alias;
+ }
+
+ return result;
+}
+
+
+/**
+ * Return a member alias for NAME
+ *
+ * If NAME has no alias, then the result is `undefined`.
+ *
+ * @param {string} name member name
+ *
+ * @return {string|undefined}
+ */
+function _getMemberAlias( name )
+{
+ return ( hasOwn.call( aliased_members, name ) )
+ ? aliased_members[ name ]
+ : undefined;
+}
+
+
function _parseEach( name, value, keywords )
{
var defs = this.defs;
diff --git a/lib/prop_parser.js b/lib/prop_parser.js
index 6938328..0b43302 100644
--- a/lib/prop_parser.js
+++ b/lib/prop_parser.js
@@ -1,7 +1,8 @@
/**
* Property keyword parser module
*
- * Copyright (C) 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
+ * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015
+ * Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@@ -60,7 +61,7 @@ exports.kmasks = _kmasks;
*
* @param {string} prop property string, which may contain keywords
*
- * @return {{name: string, keywords: Object.}}
+ * @return {{name: string, bitwords: number, keywords: Object.}}
*/
exports.parseKeywords = function ( prop )
{
diff --git a/test/Class/ConstructorTest.js b/test/Class/ConstructorTest.js
index 78dfd50..d382870 100644
--- a/test/Class/ConstructorTest.js
+++ b/test/Class/ConstructorTest.js
@@ -1,7 +1,7 @@
/**
* Tests class module constructor creation
*
- * Copyright (C) 2014 Free Software Foundation, Inc.
+ * Copyright (C) 2014, 2015 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@@ -21,6 +21,24 @@
require( 'common' ).testCase(
{
+ caseSetUp: function()
+ {
+ // ease.js was written long before ES6 drafts began providing class
+ // support. Now that they do, we should support their constructor
+ // decision as well as our own.
+ this.ctors = [ '__construct', 'constructor' ];
+
+ // we only use ES3 features, thus this
+ this.mkctor = function( name, f )
+ {
+ var o = {};
+ o[ name ] = f;
+
+ return o;
+ };
+ },
+
+
setUp: function()
{
this.Sut = this.require( 'class' );
@@ -32,10 +50,15 @@ require( 'common' ).testCase(
* defining the class. (Note that the case of ensuring that it is not
* called when creating a subtype is handled by the ExtendTest case.)
*/
- 'Constructor should not be invoked before instantiation': function()
+ '@each(ctors) Should not be invoked before instantiation':
+ function( name )
{
- var called = false;
- this.Sut.extend( { __construct: function() { called = true; } } );
+ var called = false,
+ dfn = {};
+
+ this.Sut.extend(
+ this.mkctor( name, function() { called = true; } )
+ );
this.assertNotEqual( called, true );
},
@@ -47,14 +70,14 @@ require( 'common' ).testCase(
* is instantiated. Further, it should only be called a single time,
* which is particularly important if it produces side-effects.
*/
- 'Constructor should be invoked once upon instantiation': function()
+ '@each(ctors) Should be invoked once upon instantiation':
+ function( name )
{
var called = 0;
var Foo = this.Sut.extend(
- {
- __construct: function() { called++; }
- } );
+ this.mkctor( name, function() { called++; } )
+ );
// note that we're not yet testing the more consise new-less
// invocation style
@@ -67,16 +90,19 @@ require( 'common' ).testCase(
* Once invoked, the __construct method should be bound to the newly
* created instance.
*/
- 'Constructor should be invoked within context of new instance':
- function()
+ '@each(ctors) Should be invoked within context of new instance':
+ function( name )
{
var expected = Math.random();
- var Foo = this.Sut.extend(
- {
- val: null,
- __construct: function() { this.val = expected; }
- } );
+ var dfn = this.mkctor( name, function()
+ {
+ this.val = expected;
+ } );
+
+ dfn.val = null;
+
+ var Foo = this.Sut.extend( dfn );
// if `this' was bound to the instance, then __construct should set
// VAL to EXPECTED
@@ -90,19 +116,18 @@ require( 'common' ).testCase(
* ``class'') should be passed to __construct, unchanged and
* uncopied---that is, references should be retained.
*/
- 'Constructor arguments should be passed unchanged to __construct':
- function()
+ '@each(ctors) Arguments should be passed unchanged to __construct':
+ function( name )
{
var args = [ "foo", { bar: 'baz' }, [ 'moo', 'cow' ] ],
given = null;
var Foo = this.Sut.extend(
- {
- __construct: function()
+ this.mkctor( name, function()
{
given = Array.prototype.slice.call( arguments, 0 );
- }
- } );
+ } )
+ );
new Foo( args[ 0 ], args[ 1 ], args[ 2 ] );
@@ -125,15 +150,16 @@ require( 'common' ).testCase(
* the name __construct---is modelled after PHP; Java classes, for
* instance, do not inherit their parents' constructors.
*/
- 'Parent constructor should be invoked for subtype if not overridden':
- function()
+ '@each(ctors)Parent constructor invoked for subtype if not overridden':
+ function( name )
{
var called = false;
- var Sub = this.Sut.extend(
- {
- __construct: function() { called = true; }
- } ).extend( {} );
+ var dfn = {};
+ dfn[ name ] = function() { called = true; };
+
+ var Sub = this.Sut.extend( dfn )
+ .extend( {} );
new Sub();
this.assertOk( called );
@@ -172,18 +198,22 @@ require( 'common' ).testCase(
* __construct, since public is the default and there is no other
* option.)
*/
- '__construct must be public': function()
+ '@each(ctors) Constructor must be public': function( name )
{
var Sut = this.Sut;
this.assertThrows( function()
{
- Sut( { 'protected __construct': function() {} } );
+ var dfn = {};
+ dfn[ 'protected ' + name ] = function() {};
+ Sut( dfn );
}, TypeError, "Constructor should not be able to be protected" );
this.assertThrows( function()
{
- Sut( { 'private __construct': function() {} } );
+ var dfn = {};
+ dfn[ 'private ' + name ] = function() {};
+ Sut( dfn );
}, TypeError, "Constructor should not be able to be private" );
},
@@ -200,4 +230,35 @@ require( 'common' ).testCase(
var Foo = this.Sut.extend( {} );
this.assertStrictEqual( Foo().constructor, Foo );
},
+
+
+ /**
+ * We support multiple constructor styles; only one may be provided.
+ *
+ * This error should happen as a consequence of other method checks that
+ * prohibit redefinitions.
+ */
+ 'Cannot provide multiple constructor styles': function()
+ {
+ var Sut = this.Sut,
+ len = this.ctors.length;
+
+ // this will not test every permutation, but will hopefully be a
+ // reasonable test in the event that any additional constructors are
+ // added in the future (we start at 1 because it'll wrap modulo LEN)
+ for ( var i = 1; i < len; i++ )
+ {
+ var dfn = {},
+ a = this.ctors[ i ],
+ b = this.ctors[ ( i + 1 ) % len ];
+
+ dfn[ a ] = function() {};
+ dfn[ b ] = function() {};
+
+ this.assertThrows( function()
+ {
+ Sut( dfn );
+ }, Error, "Multiple constructors should not be permitted" );
+ }
+ },
} );
diff --git a/test/ClassBuilder/MemberRestrictionTest.js b/test/ClassBuilder/MemberRestrictionTest.js
index fa5b798..608b211 100644
--- a/test/ClassBuilder/MemberRestrictionTest.js
+++ b/test/ClassBuilder/MemberRestrictionTest.js
@@ -1,7 +1,7 @@
/**
* Tests class builder member restrictions
*
- * Copyright (C) 2014 Free Software Foundation, Inc.
+ * Copyright (C) 2014, 2015 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@@ -88,7 +88,7 @@ require( 'common' ).testCase(
*/
'Proper members are reserved': function()
{
- var chk = [ '__initProps', 'constructor' ],
+ var chk = [ '__initProps' ],
i = chk.length,
reserved = this.Sut.getReservedMembers();
diff --git a/test/IndexTest.js b/test/IndexTest.js
index 59675ae..87cf064 100644
--- a/test/IndexTest.js
+++ b/test/IndexTest.js
@@ -1,7 +1,7 @@
/**
* Tests index.js
*
- * Copyright (C) 2014 Free Software Foundation, Inc.
+ * Copyright (C) 2014, 2015 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@@ -63,4 +63,27 @@ require( 'common' ).testCase(
{
this.exportedAs( 'version', 'version' );
},
+
+
+ /**
+ * Since ECMAScript 6 introduces the ability to define prototypes using
+ * the `class` keyword and some syntatic sugar, it is awkward to use the
+ * traditional ease.js class abstraction with it. Instead, if a user
+ * wishes to wrap a prototype defined in this manner (to take advantage
+ * of ease.js' features), it would be more appropriate to make it look
+ * like we're doing just that.
+ *
+ * This is a shorthand for Class.extend( proto, {} ).
+ */
+ 'Invoking is equivalent to extending with empty definition': function()
+ {
+ var proto = function() {},
+ result = this.Sut( proto );
+
+ this.assertOk( this.Sut.Class.isClass( result ) );
+
+ // this is not a comprehensive test (once we add reflection of some
+ // sort, verify that nothing is added to the prototype)
+ this.assertOk( this.Sut.Class.isA( proto, result() ) );
+ },
} );