1
0
Fork 0

Alias `constructor` member to `__construct`

This allows ease.js classes to mimic the structure of ES6 classes, which use
`constructor` to denote the constructor.  This patch simply aliases it to
`__construct`, which ease.js handles as it would normally.

To that note, since the ES6 `class` keyword is purely syntatic sugar around
the prototype model, there is not much benefit to using it over ease.js if
benefits of ease.js are still desired, since the member definition syntax is
a feature of object literals:

```
// ease.js using ES6
let Person = Class(
{
    _name: '',

    // note that __construct still works as well
    constructor( name ) {
      this._name = ''+name;
    },

    sayHi() {
      return "Hi, I'm " + this.getName();
    },

    // keywords still work as expected
    'protected getName'() {
      return this._name;
    }
} );

// ES6 using `class` keyword
class Person
{
    // note that ES6 will _not_ make this private
    _name: '',

    constructor( name ) {
      this._name = ''+name;
    },

    sayHi() {
      return "Hi, I'm " + this.getName();
    }

    // keywords unsupported (you'd have to use Symbols)
    getName() {
      return this._name;
    }
}

// ES3/5 ease.js
var Person = Class(
{
    _name: '',

    __construct: function( name ) {
      this._name = ''+name;
    },

    sayHi: function() {
      return "Hi, I'm " + this._name;
    },

    'protected getName': function() {
      return this._name;
    }
} );
```

As you can see, the only change between writing ES6-style method definitions
is the syntax; all keywords and other features continue to work as expected.
master
Mike Gerwitz 2015-09-15 00:10:07 -04:00
parent a931796bdf
commit ba2605f836
4 changed files with 127 additions and 38 deletions

View File

@ -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;

View File

@ -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 )
{

View File

@ -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" );
},

View File

@ -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();