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..111e892 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" ); }, 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();