ES6-style constructors
Included with this change is a simple "wrapper" implementation: ``` // equivalent easejs( Foo ); Class.extend( Foo, {} ); ```master
commit
cbf98cccf1
20
index.js
20
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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' );
|
||||
|
||||
|
|
|
@ -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.<string,boolean>}
|
||||
*/
|
||||
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.<string,string>}
|
||||
*/
|
||||
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.<string, boolean>}}
|
||||
*/
|
||||
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;
|
||||
|
|
|
@ -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.<string, boolean>}}
|
||||
* @return {{name: string, bitwords: number, keywords: Object.<string, boolean>}}
|
||||
*/
|
||||
exports.parseKeywords = function ( prop )
|
||||
{
|
||||
|
|
|
@ -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,17 +90,20 @@ 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(
|
||||
var dfn = this.mkctor( name, function()
|
||||
{
|
||||
val: null,
|
||||
__construct: function() { this.val = expected; }
|
||||
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
|
||||
var inst = new Foo();
|
||||
|
@ -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" );
|
||||
}
|
||||
},
|
||||
} );
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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() ) );
|
||||
},
|
||||
} );
|
||||
|
|
Loading…
Reference in New Issue