diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js index 5108227..b351dd6 100644 --- a/lib/ClassBuilder.js +++ b/lib/ClassBuilder.js @@ -28,6 +28,9 @@ var util = require( __dirname + '/util' ), warn = require( __dirname + '/warn' ), Warning = warn.Warning, + hasOwn = Object.prototype.hasOwnProperty, + + /** * IE contains a nasty enumeration "bug" (poor implementation) that makes * toString unenumerable. This means that, if you do obj.toString = foo, @@ -462,135 +465,157 @@ exports.prototype.buildMembers = function buildMembers( props, class_id, base, prop_init, memberdest, staticInstLookup ) { - var hasOwn = Array.prototype.hasOwnProperty, - defs = {}, + var context = { + _cb: this, + + // arguments + prop_init: prop_init, + class_id: class_id, + base: base, + staticInstLookup: staticInstLookup, + + defs: {}, + + // holds member builder state + state: {}, // TODO: there does not seem to be tests for these guys; perhaps // this can be rectified with the reflection implementation - members = memberdest.all, - abstract_methods = memberdest['abstract'], - static_members = memberdest['static'], - virtual_members = memberdest['virtual'], - - smethods = static_members.methods, - sprops = static_members.props, - - // holds member builder state - state = {}, - - _self = this - ; + members: memberdest.all, + abstract_methods: memberdest['abstract'], + static_members: memberdest['static'], + virtual_members: memberdest['virtual'], + }; util.propParse( props, { - each: function( name, value, keywords ) - { - // disallow use of our internal __initProps() method - if ( reserved_members[ name ] === true ) - { - throw Error( name + " is reserved" ); - } - - // if a member was defined multiple times in the same class - // declaration, throw an error (unless the `weak' keyword is - // provided, which exists to accomodate this situation) - if ( hasOwn.call( defs, name ) - && !( keywords['weak'] || defs[ name ].weak ) - ) - { - throw Error( - "Cannot redefine method '" + name + "' in same declaration" - ); - } - - // keep track of the definitions (only during class declaration) - // to catch duplicates - defs[ name ] = keywords; - }, - - property: function( name, value, keywords ) - { - var dest = ( keywordStatic( keywords ) ) ? sprops : prop_init; - - // build a new property, passing in the other members to compare - // against for preventing nonsensical overrides - _self._memberBuilder.buildProp( - dest, null, name, value, keywords, base - ); - }, - - getset: function( name, get, set, keywords ) - { - var dest = ( keywordStatic( keywords ) ) ? smethods : members, - is_static = keywordStatic( keywords ), - instLookup = ( ( is_static ) - ? staticInstLookup - : exports.getMethodInstance - ); - - _self._memberBuilder.buildGetterSetter( - dest, null, name, get, set, keywords, instLookup, class_id, base - ); - }, - - method: function( name, func, is_abstract, keywords ) - { - var is_static = keywordStatic( keywords ), - dest = ( is_static ) ? smethods : members, - instLookup = ( is_static ) - ? staticInstLookup - : exports.getMethodInstance - ; - - // constructor check - if ( public_methods[ name ] === true ) - { - if ( keywords[ 'protected' ] || keywords[ 'private' ] ) - { - throw TypeError( - name + " must be public" - ); - } - } - - var used = _self._memberBuilder.buildMethod( - dest, null, name, func, keywords, instLookup, - class_id, base, state - ); - - // do nothing more if we didn't end up using this definition - // (this may be the case, for example, with weak members) - if ( !used ) - { - return; - } - - // note the concrete method check; this ensures that weak - // abstract methods will not count if a concrete method of the - // smae name has already been seen - if ( is_abstract ) - { - abstract_methods[ name ] = true; - abstract_methods.__length++; - } - else if ( ( hasOwn.call( abstract_methods, name ) ) - && ( is_abstract === false ) - ) - { - // if this was a concrete method, then it should no longer - // be marked as abstract - delete abstract_methods[ name ]; - abstract_methods.__length--; - } - - if ( keywords['virtual'] ) - { - virtual_members[ name ] = true; - } - }, - } ); + each: _parseEach, + property: _parseProp, + getset: _parseGetSet, + method: _parseMethod, + }, context ); // process accumulated member state - this._memberBuilder.end( state ); + this._memberBuilder.end( context.state ); +} + + +function _parseEach( name, value, keywords ) +{ + var defs = this.defs; + + // disallow use of our internal __initProps() method + if ( reserved_members[ name ] === true ) + { + throw Error( name + " is reserved" ); + } + + // if a member was defined multiple times in the same class + // declaration, throw an error (unless the `weak' keyword is + // provided, which exists to accomodate this situation) + if ( hasOwn.call( defs, name ) + && !( keywords['weak'] || defs[ name ].weak ) + ) + { + throw Error( + "Cannot redefine method '" + name + "' in same declaration" + ); + } + + // keep track of the definitions (only during class declaration) + // to catch duplicates + defs[ name ] = keywords; +} + + +function _parseProp( name, value, keywords ) +{ + var dest = ( keywordStatic( keywords ) ) + ? this.static_members.props + : this.prop_init; + + // build a new property, passing in the other members to compare + // against for preventing nonsensical overrides + this._cb._memberBuilder.buildProp( + dest, null, name, value, keywords, this.base + ); +} + + +function _parseGetSet( name, get, set, keywords ) +{ + var dest = ( keywordStatic( keywords ) ) + ? this.static_members.methods + : this.members, + + is_static = keywordStatic( keywords ), + instLookup = ( ( is_static ) + ? this.staticInstLookup + : exports.getMethodInstance + ); + + this._cb._memberBuilder.buildGetterSetter( + dest, null, name, get, set, keywords, instLookup, + this.class_id, this.base + ); +} + + +function _parseMethod( name, func, is_abstract, keywords ) +{ + var is_static = keywordStatic( keywords ), + dest = ( is_static ) + ? this.static_members.methods + : this.members, + instLookup = ( is_static ) + ? this.staticInstLookup + : exports.getMethodInstance + ; + + // constructor check + if ( public_methods[ name ] === true ) + { + if ( keywords[ 'protected' ] || keywords[ 'private' ] ) + { + throw TypeError( + name + " must be public" + ); + } + } + + var used = this._cb._memberBuilder.buildMethod( + dest, null, name, func, keywords, instLookup, + this.class_id, this.base, this.state + ); + + // do nothing more if we didn't end up using this definition + // (this may be the case, for example, with weak members) + if ( !used ) + { + return; + } + + // note the concrete method check; this ensures that weak + // abstract methods will not count if a concrete method of the + // smae name has already been seen + if ( is_abstract ) + { + this.abstract_methods[ name ] = true; + this.abstract_methods.__length++; + } + else if ( ( hasOwn.call( this.abstract_methods, name ) ) + && ( is_abstract === false ) + ) + { + // if this was a concrete method, then it should no longer + // be marked as abstract + delete this.abstract_methods[ name ]; + this.abstract_methods.__length--; + } + + if ( keywords['virtual'] ) + { + this.virtual_members[ name ] = true; + } } @@ -741,9 +766,7 @@ exports.prototype.createConcreteCtor = function( cname, members ) // Provide a more intuitive string representation of the class // instance. If a toString() method was already supplied for us, // use that one instead. - if ( !( Object.prototype.hasOwnProperty.call( - members[ 'public' ], 'toString' - ) ) ) + if ( !( hasOwn.call( members[ 'public' ], 'toString' ) ) ) { // use __toString if available (see enum_bug), otherwise use // our own defaults @@ -991,8 +1014,7 @@ function attachStatic( ctor, members, base, inheriting ) // we use hasOwnProperty to ensure that undefined values will not // cause us to continue checking the parent, thereby potentially // failing to set perfectly legal values - var has = Object.prototype.hasOwnProperty, - found = false, + var found = false, // Determine if we were invoked in the context of a class. If // so, use that. Otherwise, use ourself. @@ -1011,16 +1033,16 @@ function attachStatic( ctor, members, base, inheriting ) // available and we are internal (within a method), we can move on // to check other levels of visibility. `found` will contain the // visibility level the property was found in, or false. - found = has.call( props[ 'public' ], prop ) && 'public'; + found = hasOwn.call( props[ 'public' ], prop ) && 'public'; if ( !found && _self._spropInternal ) { // Check for protected/private. We only check for private // properties if we are not currently checking the properties of // a subtype. This works because the context is passed to each // recursive call. - found = has.call( props[ 'protected' ], prop ) && 'protected' + found = hasOwn.call( props[ 'protected' ], prop ) && 'protected' || !in_subtype - && has.call( props[ 'private' ], prop ) && 'private' + && hasOwn.call( props[ 'private' ], prop ) && 'private' ; } diff --git a/lib/util.js b/lib/util.js index 2cc46f5..868c6c8 100644 --- a/lib/util.js +++ b/lib/util.js @@ -257,7 +257,7 @@ exports.copyTo = function( dest, src, deep ) * * @return undefined */ -exports.propParse = function( data, options ) +exports.propParse = function( data, options, context ) { // todo: profile; function calls are more expensive than if statements, so // it's probably a better idea not to use fvoid @@ -327,13 +327,13 @@ exports.propParse = function( data, options ) // if an 'each' callback was provided, pass the data before parsing it if ( callbackEach ) { - callbackEach.call( callbackEach, name, value, keywords ); + callbackEach.call( context, name, value, keywords ); } // getter/setter if ( getter || setter ) { - callbackGetSet.call( callbackGetSet, + callbackGetSet.call( context, name, getter, setter, keywords ); } @@ -341,7 +341,7 @@ exports.propParse = function( data, options ) else if ( ( typeof value === 'function' ) || ( keywords[ 'proxy' ] ) ) { callbackMethod.call( - callbackMethod, + context, name, value, exports.isAbstractMethod( value ), @@ -351,7 +351,7 @@ exports.propParse = function( data, options ) // simple property else { - callbackProp.call( callbackProp, name, value, keywords ); + callbackProp.call( context, name, value, keywords ); } } }; diff --git a/test/Util/PropParseTest.js b/test/Util/PropParseTest.js index 7257946..6c2910c 100644 --- a/test/Util/PropParseTest.js +++ b/test/Util/PropParseTest.js @@ -210,4 +210,46 @@ require( 'common' ).testCase( propParse( { 'abstract foo': [ 'valid_name' ] }, {} ); }, SyntaxError ); }, + + + /** + * The motivation behind this feature is to reduce the number of closures + * necessary to perform a particular task: this allows binding `this' of the + * handler to a custom context. + */ + 'Supports dynamic context to handlers': function() + { + var _self = this; + context = {}; + + // should trigger all of the handlers + var all = { + prop: 'prop', + method: function() {}, + }; + + // run test on getters/setters only if supported by the environment + if ( this.hasGetSet ) + { + Object.defineProperty( all, 'getset', { + get: ( get = function () {} ), + set: ( set = function () {} ), + + enumerable: true, + } ); + } + + function _chk() + { + _self.assertStrictEqual( this, context ); + } + + // check each supported handler for conformance + this.Sut.propParse( all, { + each: _chk, + property: _chk, + getset: _chk, + method: _chk, + }, context ); + }, } );