Added support for implicit private members
Members with an underscore prefix are now implicitly private, which follows conventions established in many object-oriented languages. This allows for a concise definition style familiar to prototypal (and I suppose Ruby) programmers.newmaster
commit
d5965f4672
|
@ -24,18 +24,36 @@
|
||||||
* @type {Object.<string,boolean>}
|
* @type {Object.<string,boolean>}
|
||||||
*/
|
*/
|
||||||
var _keywords = {
|
var _keywords = {
|
||||||
'public': true,
|
'public': 1,
|
||||||
'protected': true,
|
'protected': 1<<1,
|
||||||
'private': true,
|
'private': 1<<2,
|
||||||
'static': true,
|
'static': 1<<3,
|
||||||
'abstract': true,
|
'abstract': 1<<4,
|
||||||
'const': true,
|
'const': 1<<5,
|
||||||
'virtual': true,
|
'virtual': 1<<6,
|
||||||
'override': true,
|
'override': 1<<7,
|
||||||
'proxy': true,
|
'proxy': 1<<8,
|
||||||
'weak': true,
|
'weak': 1<<9,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyword masks for conveniently checking the keyword bitfield
|
||||||
|
* @type {Object.<string,integer>}
|
||||||
|
*/
|
||||||
|
var _kmasks = {
|
||||||
|
amods: _keywords[ 'public' ]
|
||||||
|
| _keywords[ 'protected' ]
|
||||||
|
| _keywords[ 'private' ],
|
||||||
|
|
||||||
|
'virtual': _keywords[ 'abstract' ]
|
||||||
|
| _keywords[ 'virtual' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// expose magic values
|
||||||
|
exports.kvals = _keywords;
|
||||||
|
exports.kmasks = _kmasks;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses property keywords
|
* Parses property keywords
|
||||||
|
@ -48,6 +66,7 @@ exports.parseKeywords = function ( prop )
|
||||||
{
|
{
|
||||||
var name = prop,
|
var name = prop,
|
||||||
keywords = [],
|
keywords = [],
|
||||||
|
bitwords = 0x00,
|
||||||
keyword_obj = {};
|
keyword_obj = {};
|
||||||
|
|
||||||
prop = ''+( prop );
|
prop = ''+( prop );
|
||||||
|
@ -58,27 +77,41 @@ exports.parseKeywords = function ( prop )
|
||||||
{
|
{
|
||||||
name = keywords.pop();
|
name = keywords.pop();
|
||||||
|
|
||||||
var i = keywords.length,
|
var i = keywords.length;
|
||||||
keyword = '';
|
|
||||||
|
|
||||||
while ( i-- )
|
while ( i-- )
|
||||||
{
|
{
|
||||||
keyword = keywords[ i ];
|
var keyword = keywords[ i ],
|
||||||
|
kval = _keywords[ keyword ];
|
||||||
|
|
||||||
// ensure the keyword is recognized
|
// ensure the keyword is recognized
|
||||||
if ( !_keywords[ keyword ] )
|
if ( !kval )
|
||||||
{
|
{
|
||||||
throw Error(
|
throw Error(
|
||||||
"Unexpected keyword for '" + name + "': " + keyword
|
"Unexpected keyword for '" + name + "': " + keyword
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ease-of-access
|
||||||
keyword_obj[ keyword ] = true;
|
keyword_obj[ keyword ] = true;
|
||||||
|
|
||||||
|
// permits quick and concise checks
|
||||||
|
bitwords |= kval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// members with an underscore prefix are implicitly private, unless an
|
||||||
|
// access modifier is explicitly provided; double-underscore is ingored,
|
||||||
|
// as they denote special members that do not become part of the
|
||||||
|
// prototype and are reserved by ease.js
|
||||||
|
if ( ( name.match( /^_[^_]/ ) && !( bitwords & _kmasks.amods ) ) )
|
||||||
|
{
|
||||||
|
keyword_obj[ 'private' ] = true;
|
||||||
|
bitwords |= _keywords[ 'private' ];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
keywords: keyword_obj,
|
keywords: keyword_obj,
|
||||||
|
bitwords: bitwords,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,4 +141,115 @@ require( 'common' ).testCase(
|
||||||
|
|
||||||
this.assertFail( "Should not permit unknown keywords" );
|
this.assertFail( "Should not permit unknown keywords" );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's accepted convention in nearly every modern object-oriented
|
||||||
|
* language that underscore-prefixed members denote private. (Granted,
|
||||||
|
* the Java community sometimes uses underscore suffixes, but that's
|
||||||
|
* considerably less common in the JavaScript community.)
|
||||||
|
*
|
||||||
|
* For the sake of conciseness, this allows omission of the `private'
|
||||||
|
* keyword; this, coupled with the fact that all non-underscore-prefixed
|
||||||
|
* members are public by default, satisfies the two most common
|
||||||
|
* visibility modifiers for classes and allows a definition style more
|
||||||
|
* natural to JavaScript developers from prototypal development.
|
||||||
|
*/
|
||||||
|
'Implciity marks underscore-prefixed members as private': function()
|
||||||
|
{
|
||||||
|
this.assertDeepEqual(
|
||||||
|
this.Sut.parseKeywords( '_foo' ).keywords,
|
||||||
|
{ 'private': true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All that said, we want users to be able to do what they want. Let's
|
||||||
|
* have explicit access modifier declarations override the implicit
|
||||||
|
* behavior rather than providing confusing errors (because multiple
|
||||||
|
* access modifiers were provided).
|
||||||
|
*/
|
||||||
|
'Fields are not implicitly private with explicit access modifier':
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
this.assertDeepEqual(
|
||||||
|
this.Sut.parseKeywords( 'public _foo' ).keywords,
|
||||||
|
{ 'public': true }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.assertDeepEqual(
|
||||||
|
this.Sut.parseKeywords( 'protected _foo' ).keywords,
|
||||||
|
{ 'protected': true }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.assertDeepEqual(
|
||||||
|
this.Sut.parseKeywords( 'private _foo' ).keywords,
|
||||||
|
{ 'private': true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-underscore members are reserved by ease.js for special purposes
|
||||||
|
* and are not included as part of the prototype chain. Further, if we
|
||||||
|
* did not have this exception, then __construct would be marked as
|
||||||
|
* private, which would be in error.
|
||||||
|
*/
|
||||||
|
'Double-underscore members are not implicitly private': function()
|
||||||
|
{
|
||||||
|
this.assertDeepEqual(
|
||||||
|
this.Sut.parseKeywords( '__foo' ).keywords,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As the keyword bit values are magic values, they must be exposed if
|
||||||
|
* the bitfield is to be used. The bitmasks are useful for quick and
|
||||||
|
* convenient checks in other parts of the framework.
|
||||||
|
*/
|
||||||
|
'Exposes keyword bit values and masks': function()
|
||||||
|
{
|
||||||
|
this.assertOk( this.Sut.kvals );
|
||||||
|
this.assertOk( this.Sut.kmasks );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access modifier checks are common; ensure that the bitmask can
|
||||||
|
* properly check them all and does not check for any other keywords.
|
||||||
|
*/
|
||||||
|
'Access modifier bitmask catches all access modifiers': function()
|
||||||
|
{
|
||||||
|
var kvals = this.Sut.kvals;
|
||||||
|
|
||||||
|
// this can be easily checked by ensuring that the inclusive logical
|
||||||
|
// or of all the access modifier bits are no different than the mask
|
||||||
|
this.assertEqual(
|
||||||
|
this.Sut.kmasks.amods
|
||||||
|
| kvals[ 'public' ]
|
||||||
|
| kvals[ 'protected' ]
|
||||||
|
| kvals[ 'private' ],
|
||||||
|
this.Sut.kmasks.amods
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Likewise, a virtual bitmask is also useful since it can be denoted by
|
||||||
|
* multiple keywords (abstract is implicitly virtual).
|
||||||
|
*/
|
||||||
|
'Virtual bitmask catches abstract and virtual keywords': function()
|
||||||
|
{
|
||||||
|
var kvals = this.Sut.kvals;
|
||||||
|
|
||||||
|
this.assertEqual(
|
||||||
|
this.Sut.kmasks[ 'virtual' ]
|
||||||
|
| kvals[ 'abstract' ]
|
||||||
|
| kvals[ 'virtual' ],
|
||||||
|
this.Sut.kmasks[ 'virtual' ]
|
||||||
|
);
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
Loading…
Reference in New Issue