Visibility de-escalation no longer permitted
parent
9b9bcfd150
commit
ce736bea55
10
lib/class.js
10
lib/class.js
|
@ -439,9 +439,9 @@ var extend = ( function( extending )
|
||||||
prototype = new base(),
|
prototype = new base(),
|
||||||
cname = '',
|
cname = '',
|
||||||
|
|
||||||
hasOwn = Array.prototype.hasOwnProperty;
|
hasOwn = Array.prototype.hasOwnProperty,
|
||||||
|
|
||||||
var properties = {},
|
properties = {},
|
||||||
prop_init = member_builder.initMembers(),
|
prop_init = member_builder.initMembers(),
|
||||||
members = member_builder.initMembers( prototype ),
|
members = member_builder.initMembers( prototype ),
|
||||||
|
|
||||||
|
@ -489,7 +489,7 @@ var extend = ( function( extending )
|
||||||
// build a new property, passing in the other members to compare
|
// build a new property, passing in the other members to compare
|
||||||
// against for preventing nonsensical overrides
|
// against for preventing nonsensical overrides
|
||||||
member_builder.buildProp(
|
member_builder.buildProp(
|
||||||
prop_init, null, name, value, keywords, members
|
prop_init, null, name, value, keywords, base
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -511,7 +511,7 @@ var extend = ( function( extending )
|
||||||
{
|
{
|
||||||
member_builder.buildMethod(
|
member_builder.buildMethod(
|
||||||
members, null, name, func, keywords, getMethodInstance,
|
members, null, name, func, keywords, getMethodInstance,
|
||||||
class_id
|
class_id, base
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( is_abstract )
|
if ( is_abstract )
|
||||||
|
@ -541,6 +541,8 @@ var extend = ( function( extending )
|
||||||
|
|
||||||
new_class.prototype = prototype;
|
new_class.prototype = prototype;
|
||||||
new_class.constructor = new_class;
|
new_class.constructor = new_class;
|
||||||
|
new_class.___$$props$$ = prop_init;
|
||||||
|
new_class.___$$methods$$ = members;
|
||||||
|
|
||||||
// important: call after setting prototype
|
// important: call after setting prototype
|
||||||
setupProps( new_class, abstract_methods, class_id );
|
setupProps( new_class, abstract_methods, class_id );
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var util = require( './util' ),
|
var util = require( './util' ),
|
||||||
|
fallback = util.definePropertyFallback(),
|
||||||
visibility = [ 'public', 'protected', 'private' ];
|
visibility = [ 'public', 'protected', 'private' ];
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,13 +68,16 @@ exports.initMembers = function( mpublic, mprotected, mprivate )
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
exports.buildMethod = function(
|
exports.buildMethod = function(
|
||||||
members, meta, name, value, keywords, instCallback, cid
|
members, meta, name, value, keywords, instCallback, cid, base
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var prev;
|
// TODO: We can improve performance by not scanning each one individually
|
||||||
|
// every time this method is called
|
||||||
|
var prev_data = scanMembers( members, name, base ),
|
||||||
|
prev = ( prev_data ) ? prev_data.member : null;
|
||||||
|
|
||||||
// search for any previous instances of this member
|
// search for any previous instances of this member
|
||||||
if ( prev = scanMembers( members, name ) )
|
if ( prev )
|
||||||
{
|
{
|
||||||
// disallow overriding properties with methods
|
// disallow overriding properties with methods
|
||||||
if ( !( prev instanceof Function ) )
|
if ( !( prev instanceof Function ) )
|
||||||
|
@ -102,6 +106,14 @@ exports.buildMethod = function(
|
||||||
"with that of its supertype"
|
"with that of its supertype"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not permit visibility de-escalation
|
||||||
|
if ( prev_data.visibility < getVisibilityValue( keywords ) )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Cannot de-escalate visibility of method '" + name + "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dest = getMemberVisibility( members, keywords );
|
var dest = getMemberVisibility( members, keywords );
|
||||||
|
@ -138,20 +150,33 @@ exports.buildMethod = function(
|
||||||
*
|
*
|
||||||
* @param {Object.<string,boolean>} keywords parsed keywords
|
* @param {Object.<string,boolean>} keywords parsed keywords
|
||||||
|
|
||||||
* @param {Object=} cmp optional second member object to scan
|
* @param {Object=} base optional base object to scan
|
||||||
*
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
exports.buildProp = function( members, meta, name, value, keywords, cmp )
|
exports.buildProp = function( members, meta, name, value, keywords, base )
|
||||||
{
|
{
|
||||||
|
// TODO: We can improve performance by not scanning each one individually
|
||||||
|
// every time this method is called
|
||||||
|
var prev_data = scanMembers( members, name, base ),
|
||||||
|
prev = ( prev_data ) ? prev_data.member : null;
|
||||||
|
|
||||||
// disallow overriding methods with properties
|
// disallow overriding methods with properties
|
||||||
if ( scanMembers( members, name, cmp ) instanceof Function )
|
if ( prev instanceof Function )
|
||||||
{
|
{
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
"Cannot override method '" + name + "' with property"
|
"Cannot override method '" + name + "' with property"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not permit visibility de-escalation
|
||||||
|
if ( prev && ( prev_data.visibility < getVisibilityValue( keywords ) ) )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Cannot de-escalate visibility of property '" + name + "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getMemberVisibility( members, keywords )[ name ] = value;
|
getMemberVisibility( members, keywords )[ name ] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -262,11 +287,12 @@ function getMemberVisibility( members, keywords )
|
||||||
* @param {{public: Object, protected: Object, private: Object}} members
|
* @param {{public: Object, protected: Object, private: Object}} members
|
||||||
*
|
*
|
||||||
* @param {string} name member to locate
|
* @param {string} name member to locate
|
||||||
* @param {Object=} cmp optional second member object to scan
|
* @param {Object=} base optional base object to scan
|
||||||
*
|
*
|
||||||
* @return {*} member, if located, otherwise undefined
|
* @return {Object} Array of member and number corresponding to visibility,
|
||||||
|
* level if located, otherwise an empty object
|
||||||
*/
|
*/
|
||||||
function scanMembers( members, name, cmp )
|
function scanMembers( members, name, base )
|
||||||
{
|
{
|
||||||
var i = visibility.length,
|
var i = visibility.length,
|
||||||
member = null;
|
member = null;
|
||||||
|
@ -276,19 +302,29 @@ function scanMembers( members, name, cmp )
|
||||||
{
|
{
|
||||||
if ( member = members[ visibility[ i ] ][ name ] )
|
if ( member = members[ visibility[ i ] ][ name ] )
|
||||||
{
|
{
|
||||||
return member;
|
return {
|
||||||
|
member: member,
|
||||||
|
visibility: ( ( fallback ) ? 0 : i ),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if a second comparison object was given, try again using it instead of
|
// if a second comparison object was given, try again using it instead of
|
||||||
// the original members object
|
// the original members object
|
||||||
if ( cmp !== undefined )
|
if ( base !== undefined )
|
||||||
{
|
{
|
||||||
return scanMembers( cmp, name );
|
var base_methods = base.___$$methods$$,
|
||||||
|
base_props = base.___$$props$$;
|
||||||
|
|
||||||
|
// scan the base's methods and properties, if they are available
|
||||||
|
return ( base_methods && scanMembers( base_methods, name ) )
|
||||||
|
|| ( base_props && scanMembers( base_props, name ) )
|
||||||
|
|| null
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// nothing was found
|
// nothing was found
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -378,3 +414,34 @@ function overrideMethod( super_method, new_method, instCallback, cid )
|
||||||
return override;
|
return override;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the visibility level as a numeric value, where 0 is public and 2 is
|
||||||
|
* private
|
||||||
|
*
|
||||||
|
* @param {Object} keywords keywords to scan for visibility level
|
||||||
|
*
|
||||||
|
* @return {number} visibility level as a numeric value
|
||||||
|
*/
|
||||||
|
function getVisibilityValue( keywords )
|
||||||
|
{
|
||||||
|
if ( fallback )
|
||||||
|
{
|
||||||
|
// if we have to fall back, we don't support levels of visibility
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if ( keywords[ 'protected' ] )
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if ( keywords[ 'private' ] )
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// default is public
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -240,7 +240,7 @@ exports.propParse = function( data, options )
|
||||||
// if an 'each' callback was provided, pass the data before parsing it
|
// if an 'each' callback was provided, pass the data before parsing it
|
||||||
if ( callbackEach )
|
if ( callbackEach )
|
||||||
{
|
{
|
||||||
callbackEach.call( callbackEach, name, value );
|
callbackEach.call( callbackEach, name, value, keywords );
|
||||||
}
|
}
|
||||||
|
|
||||||
// getter/setter
|
// getter/setter
|
||||||
|
|
|
@ -503,3 +503,109 @@ var common = require( './common' ),
|
||||||
);
|
);
|
||||||
} )();
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visibility escalation (protected -> private) should be permitted
|
||||||
|
*/
|
||||||
|
( function testCanEscalateMemberVisibility()
|
||||||
|
{
|
||||||
|
// escalate
|
||||||
|
assert.doesNotThrow( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'protected foo': 'bar',
|
||||||
|
'protected baz': function() {},
|
||||||
|
} ).extend( {
|
||||||
|
'public foo': 'bar',
|
||||||
|
'public baz': function() {},
|
||||||
|
} );
|
||||||
|
}, Error, "Can escalate visibility of subtype members" );
|
||||||
|
|
||||||
|
// same level of visibility
|
||||||
|
assert.doesNotThrow( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'protected foo': 'bar',
|
||||||
|
'protected baz': function() {},
|
||||||
|
} ).extend( {
|
||||||
|
'protected foo': 'bar',
|
||||||
|
'protected baz': function() {},
|
||||||
|
} );
|
||||||
|
}, Error, "Can retain level of visibility for subtype members" );
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We should /not/ be able to de-escalate member visibility
|
||||||
|
* (public -> {protected,private}
|
||||||
|
*/
|
||||||
|
( function testCannotDeescalateMemberVisibility()
|
||||||
|
{
|
||||||
|
// public -> protected
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'public foo': 'bar',
|
||||||
|
} ).extend( {
|
||||||
|
'protected foo': 'bar',
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype props to protected" );
|
||||||
|
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'public baz': function() {},
|
||||||
|
} ).extend( {
|
||||||
|
'protected baz': function() {},
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype methods to protected" );
|
||||||
|
|
||||||
|
|
||||||
|
// public -> private
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'public foo': 'bar',
|
||||||
|
} ).extend( {
|
||||||
|
'private foo': 'bar',
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype props to private" );
|
||||||
|
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'public baz': function() {},
|
||||||
|
} ).extend( {
|
||||||
|
'private baz': function() {},
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype methods to private" );
|
||||||
|
|
||||||
|
|
||||||
|
// protected -> private
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'protected foo': 'bar',
|
||||||
|
} ).extend( {
|
||||||
|
'private foo': 'bar',
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype props to private2" );
|
||||||
|
|
||||||
|
assert.throws( function()
|
||||||
|
{
|
||||||
|
Class(
|
||||||
|
{
|
||||||
|
'protected baz': function() {},
|
||||||
|
} ).extend( {
|
||||||
|
'private baz': function() {},
|
||||||
|
} );
|
||||||
|
}, Error, "Cannot de-escalate visibility of subtype methods to private2" );
|
||||||
|
} )();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue