Added private member name conflict validations
parent
50da560c1f
commit
e41495c0d1
|
@ -209,13 +209,18 @@ exports.buildGetterSetter = function(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var prev_data = scanMembers( members, name, base ),
|
var prev_data = scanMembers( members, name, base ),
|
||||||
prev_keywords = null
|
prev_keywords = ( ( prev_data && prev_data.get )
|
||||||
|
? prev_data.get.___$$keywords$$
|
||||||
|
: null
|
||||||
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
this._validate.validateGetterSetter(
|
this._validate.validateGetterSetter(
|
||||||
name, {}, keywords, prev_data, prev_keywords
|
name, {}, keywords, prev_data, prev_keywords
|
||||||
);
|
);
|
||||||
|
|
||||||
|
get.___$$keywords$$ = keywords;
|
||||||
|
|
||||||
Object.defineProperty(
|
Object.defineProperty(
|
||||||
getMemberVisibility( members, keywords, name ),
|
getMemberVisibility( members, keywords, name ),
|
||||||
name,
|
name,
|
||||||
|
|
|
@ -98,6 +98,15 @@ exports.prototype.validateMethod = function(
|
||||||
// search for any previous instances of this member
|
// search for any previous instances of this member
|
||||||
if ( prev )
|
if ( prev )
|
||||||
{
|
{
|
||||||
|
// perform this check first, as it will make more sense than those that
|
||||||
|
// follow, should this condition be satisfied
|
||||||
|
if ( prev_keywords[ 'private' ] )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Private member name '" + name + "' conflicts with supertype"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// disallow overriding properties with methods
|
// disallow overriding properties with methods
|
||||||
if ( !( typeof prev === 'function' ) )
|
if ( !( typeof prev === 'function' ) )
|
||||||
{
|
{
|
||||||
|
@ -191,12 +200,34 @@ exports.prototype.validateProperty = function(
|
||||||
{
|
{
|
||||||
var prev = ( prev_data ) ? prev_data.member : null;
|
var prev = ( prev_data ) ? prev_data.member : null;
|
||||||
|
|
||||||
// disallow overriding methods with properties
|
// do not permit visibility de-escalation
|
||||||
if ( typeof prev === 'function' )
|
if ( prev )
|
||||||
{
|
{
|
||||||
throw new TypeError(
|
// perform this check first, as it will make more sense than those that
|
||||||
"Cannot override method '" + name + "' with property"
|
// follow, should this condition be satisfied
|
||||||
);
|
if ( prev_keywords[ 'private' ] )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Private member name '" + name + "' conflicts with supertype"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// disallow overriding methods with properties
|
||||||
|
if ( typeof prev === 'function' )
|
||||||
|
{
|
||||||
|
throw new TypeError(
|
||||||
|
"Cannot override method '" + name + "' with property"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this._getVisibilityValue( prev_keywords )
|
||||||
|
< this._getVisibilityValue( keywords )
|
||||||
|
)
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Cannot de-escalate visibility of property '" + name + "'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not allow overriding getters/setters
|
// do not allow overriding getters/setters
|
||||||
|
@ -207,18 +238,6 @@ exports.prototype.validateProperty = function(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not permit visibility de-escalation
|
|
||||||
if ( prev &&
|
|
||||||
( this._getVisibilityValue( prev_keywords )
|
|
||||||
< this._getVisibilityValue( keywords )
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
throw TypeError(
|
|
||||||
"Cannot de-escalate visibility of property '" + name + "'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// abstract properties do not make sense
|
// abstract properties do not make sense
|
||||||
if ( keywords[ 'abstract' ] )
|
if ( keywords[ 'abstract' ] )
|
||||||
{
|
{
|
||||||
|
@ -267,6 +286,15 @@ exports.prototype.validateGetterSetter = function(
|
||||||
|
|
||||||
if ( prev || prev_gs )
|
if ( prev || prev_gs )
|
||||||
{
|
{
|
||||||
|
// perform this check first, as it will make more sense than those that
|
||||||
|
// follow, should this condition be satisfied
|
||||||
|
if ( prev_keywords && prev_keywords[ 'private' ] )
|
||||||
|
{
|
||||||
|
throw TypeError(
|
||||||
|
"Private member name '" + name + "' conflicts with supertype"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// To speed up the system we'll simply check for a getter/setter, rather
|
// To speed up the system we'll simply check for a getter/setter, rather
|
||||||
// than checking separately for methods/properties. This is at the
|
// than checking separately for methods/properties. This is at the
|
||||||
// expense of more detailed error messages. They'll live.
|
// expense of more detailed error messages. They'll live.
|
||||||
|
|
|
@ -35,7 +35,7 @@ require( 'common' ).testCase(
|
||||||
|
|
||||||
this.quickFailureTest = shared.quickFailureTest;
|
this.quickFailureTest = shared.quickFailureTest;
|
||||||
|
|
||||||
this.quickVisChangeTest = function( start, override, failtest )
|
this.quickVisChangeTest = function( start, override, failtest, failstr )
|
||||||
{
|
{
|
||||||
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
||||||
function( name, startobj, overrideobj )
|
function( name, startobj, overrideobj )
|
||||||
|
@ -45,7 +45,8 @@ require( 'common' ).testCase(
|
||||||
{ get: function() {}, set: function() {} },
|
{ get: function() {}, set: function() {} },
|
||||||
startobj
|
startobj
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
failstr
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -117,4 +118,20 @@ require( 'common' ).testCase(
|
||||||
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], false );
|
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], false );
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See property/method tests for more information. This is not strictly
|
||||||
|
* necessary (since getters/setters can exist only in an ES5+ environment),
|
||||||
|
* but it's provided for consistency. It's also easy to remove this feature
|
||||||
|
* without breaking BC. The reverse is untrue.
|
||||||
|
*/
|
||||||
|
'Cannot redeclare private getters/setters in subtypes': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
shared.privateNamingConflictTest( function( cur )
|
||||||
|
{
|
||||||
|
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], true, 'conflict' );
|
||||||
|
} );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -42,7 +42,7 @@ require( 'common' ).testCase(
|
||||||
this.quickFailureTest = shared.quickFailureTest;
|
this.quickFailureTest = shared.quickFailureTest;
|
||||||
|
|
||||||
|
|
||||||
this.quickVisChangeTest = function( start, override, failtest )
|
this.quickVisChangeTest = function( start, override, failtest, failstr )
|
||||||
{
|
{
|
||||||
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
||||||
function( name, startobj, overrideobj )
|
function( name, startobj, overrideobj )
|
||||||
|
@ -57,7 +57,8 @@ require( 'common' ).testCase(
|
||||||
{ member: function() {} },
|
{ member: function() {} },
|
||||||
startobj
|
startobj
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
failstr
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -354,5 +355,24 @@ require( 'common' ).testCase(
|
||||||
'Override warning should contain method name'
|
'Override warning should contain method name'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait - what? That doesn't make sense from an OOP perspective, now does
|
||||||
|
* it! Unfortunately, we're forced into this restriction in order to
|
||||||
|
* properly support fallback to pre-ES5 environments where the visibility
|
||||||
|
* object is a single layer, rather than three. With this impl, all members
|
||||||
|
* are public and private name conflicts would result in supertypes and
|
||||||
|
* subtypes altering eachothers' private members (see manual for more
|
||||||
|
* information).
|
||||||
|
*/
|
||||||
|
'Cannot redeclare private members in subtypes': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
shared.privateNamingConflictTest( function( cur )
|
||||||
|
{
|
||||||
|
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], true, 'conflict' );
|
||||||
|
} );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ require( 'common' ).testCase(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.quickVisChangeTest = function( start, override, failtest )
|
this.quickVisChangeTest = function( start, override, failtest, failstr )
|
||||||
{
|
{
|
||||||
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
shared.quickVisChangeTest.call( _self, start, override, failtest,
|
||||||
function( name, startobj, overrideobj )
|
function( name, startobj, overrideobj )
|
||||||
|
@ -50,7 +50,8 @@ require( 'common' ).testCase(
|
||||||
{ member: 'foo' },
|
{ member: 'foo' },
|
||||||
startobj
|
startobj
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
failstr
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -169,5 +170,24 @@ require( 'common' ).testCase(
|
||||||
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], false );
|
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], false );
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait - what? That doesn't make sense from an OOP perspective, now does
|
||||||
|
* it! Unfortunately, we're forced into this restriction in order to
|
||||||
|
* properly support fallback to pre-ES5 environments where the visibility
|
||||||
|
* object is a single layer, rather than three. With this impl, all members
|
||||||
|
* are public and private name conflicts would result in supertypes and
|
||||||
|
* subtypes altering eachothers' private members (see manual for more
|
||||||
|
* information).
|
||||||
|
*/
|
||||||
|
'Cannot redeclare private properties in subtypes': function()
|
||||||
|
{
|
||||||
|
var _self = this;
|
||||||
|
shared.privateNamingConflictTest( function( cur )
|
||||||
|
{
|
||||||
|
_self.quickVisChangeTest( cur[ 0 ], cur[ 1 ], true, 'conflict' );
|
||||||
|
} );
|
||||||
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ exports.quickFailureTest = function( name, identifier, action )
|
||||||
// to aid in debugging, the error message should contain the
|
// to aid in debugging, the error message should contain the
|
||||||
// name of the method
|
// name of the method
|
||||||
_self.assertOk( ( e.message.search( name ) !== -1 ),
|
_self.assertOk( ( e.message.search( name ) !== -1 ),
|
||||||
'Error message should contain method name'
|
'Error message should contain member name'
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -139,12 +139,12 @@ exports.quickKeywordTest = function( type, keywords, identifier, prev )
|
||||||
exports.visEscalationTest = function( test )
|
exports.visEscalationTest = function( test )
|
||||||
{
|
{
|
||||||
var tests = [
|
var tests = [
|
||||||
[ 'private', 'protected' ],
|
[ 'protected', 'public' ],
|
||||||
[ 'protected', 'public' ],
|
[ 'public', 'public' ],
|
||||||
|
|
||||||
[ 'public', 'public' ],
|
|
||||||
[ 'protected', 'protected' ],
|
[ 'protected', 'protected' ],
|
||||||
[ 'private', 'private' ]
|
|
||||||
|
// note: private/private is intentionally omitted; see private naming
|
||||||
|
// conflict test
|
||||||
];
|
];
|
||||||
|
|
||||||
for ( var i = 0, len = tests.length; i < len; i++ )
|
for ( var i = 0, len = tests.length; i < len; i++ )
|
||||||
|
@ -155,6 +155,22 @@ exports.visEscalationTest = function( test )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.privateNamingConflictTest = function( test )
|
||||||
|
{
|
||||||
|
var tests = [
|
||||||
|
[ 'private', 'private' ],
|
||||||
|
[ 'private', 'protected' ],
|
||||||
|
[ 'private',' public' ],
|
||||||
|
];
|
||||||
|
|
||||||
|
var i = tests.length;
|
||||||
|
while ( i-- )
|
||||||
|
{
|
||||||
|
test( tests[ i ] );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a simple visibility change test using access modifiers
|
* Performs a simple visibility change test using access modifiers
|
||||||
*
|
*
|
||||||
|
@ -166,9 +182,13 @@ exports.visEscalationTest = function( test )
|
||||||
*
|
*
|
||||||
* @param {function()} func test function
|
* @param {function()} func test function
|
||||||
*
|
*
|
||||||
|
* @param {string} failstr string to check for in failure string
|
||||||
|
*
|
||||||
* @return {undefined}
|
* @return {undefined}
|
||||||
*/
|
*/
|
||||||
exports.quickVisChangeTest = function( start, override, failtest, func )
|
exports.quickVisChangeTest = function(
|
||||||
|
start, override, failtest, func, failstr
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var _self = this,
|
var _self = this,
|
||||||
name = 'foo',
|
name = 'foo',
|
||||||
|
@ -187,7 +207,7 @@ exports.quickVisChangeTest = function( start, override, failtest, func )
|
||||||
|
|
||||||
if ( failtest )
|
if ( failtest )
|
||||||
{
|
{
|
||||||
this.quickFailureTest( name, 'de-escalate', testfun );
|
this.quickFailureTest( name, ( failstr || 'de-escalate' ), testfun );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue