1
0
Fork 0

Validation warnings now stored in state object

This will allow for additional processing before actually triggering the
warnings. For the sake of this commit, though, we just keep with existing
functionality.
perfodd
Mike Gerwitz 2014-01-30 23:13:52 -05:00
parent dac4b9b3a1
commit 1b323ed80b
11 changed files with 247 additions and 105 deletions

View File

@ -451,6 +451,9 @@ exports.prototype.buildMembers = function buildMembers(
smethods = static_members.methods, smethods = static_members.methods,
sprops = static_members.props, sprops = static_members.props,
// holds member builder state
state = {},
_self = this _self = this
; ;
@ -527,7 +530,7 @@ exports.prototype.buildMembers = function buildMembers(
var used = _self._memberBuilder.buildMethod( var used = _self._memberBuilder.buildMethod(
dest, null, name, func, keywords, instLookup, dest, null, name, func, keywords, instLookup,
class_id, base class_id, base, state
); );
// do nothing more if we didn't end up using this definition // do nothing more if we didn't end up using this definition
@ -556,6 +559,9 @@ exports.prototype.buildMembers = function buildMembers(
} }
}, },
} ); } );
// process accumulated member state
this._memberBuilder.end( state );
} }

View File

@ -95,6 +95,10 @@ exports.initMembers = function( mpublic, mprotected, mprivate )
* Copies a method to the appropriate member prototype, depending on * Copies a method to the appropriate member prototype, depending on
* visibility, and assigns necessary metadata from keywords * visibility, and assigns necessary metadata from keywords
* *
* The provided ``member run'' state object is required and will be
* initialized automatically if it has not been already. For the first
* member of a run, the object should be empty.
*
* @param {__visobj} members * @param {__visobj} members
* @param {!Object} meta metadata container * @param {!Object} meta metadata container
* @param {string} name property name * @param {string} name property name
@ -108,10 +112,12 @@ exports.initMembers = function( mpublic, mprotected, mprivate )
* @param {number} cid class id * @param {number} cid class id
* @param {Object=} base optional base object to scan * @param {Object=} base optional base object to scan
* *
* @param {Object} state member run state object
*
* @return {undefined} * @return {undefined}
*/ */
exports.buildMethod = function( exports.buildMethod = function(
members, meta, name, value, keywords, instCallback, cid, base members, meta, name, value, keywords, instCallback, cid, base, state
) )
{ {
// TODO: We can improve performance by not scanning each one individually // TODO: We can improve performance by not scanning each one individually
@ -125,7 +131,7 @@ exports.buildMethod = function(
// ensure that the declaration is valid (keywords make sense, argument // ensure that the declaration is valid (keywords make sense, argument
// length, etc) // length, etc)
this._validate.validateMethod( this._validate.validateMethod(
name, value, keywords, prev_data, prev_keywords name, value, keywords, prev_data, prev_keywords, state
); );
// we might be overriding an existing method // we might be overriding an existing method
@ -492,3 +498,19 @@ exports._getVisibilityValue = function( keywords )
} }
} }
/**
* End member run and perform post-processing on state data
*
* A ``member run'' should consist of the members required for a particular
* object (class/interface/etc). This action will perform validation
* post-processing if a validator is available.
*
* @param {Object} state member run state
*
* @return {undefined}
*/
exports.end = function( state )
{
this._validate && this._validate.end( state );
};

View File

@ -32,10 +32,71 @@ module.exports = exports = function MemberBuilderValidator( warn_handler )
/** /**
* Validates a method declaration, ensuring that keywords are valid, overrides * Initialize validation state if not already done
* make sense, etc.
* *
* Throws exception on validation failure * @param {Object} state validation state
*
* @return {Object} provided state object STATE
*/
exports.prototype._initState = function( state )
{
if ( state.__vready ) return state;
state.warn = {};
state.__vready = true;
return state;
};
/**
* Perform post-processing on and invalidate validation state
*
* All queued warnings will be triggered.
*
* @param {Object} state validation state
*
* @return {undefined}
*/
exports.prototype.end = function( state )
{
// trigger warnings
for ( var f in state.warn )
{
var warns = state.warn[ f ];
for ( var id in warns )
{
this._warningHandler( warns[ id ] );
}
}
state.__vready = false;
};
/**
* Enqueue warning within validation state
*
* @param {Object} state validation state
* @param {string} member member name
* @param {string} id warning identifier
* @param {Warning} warn warning
*
* @return {undefined}
*/
function _addWarn( state, member, id, warn )
{
( state.warn[ member ] = state.warn[ member ] || {} )[ id ] = warn;
}
/**
* Validates a method declaration, ensuring that keywords are valid,
* overrides make sense, etc.
*
* Throws exception on validation failure. Warnings are stored in the state
* object for later processing. The state object will be initialized if it
* has not been already; for the initial validation, the state object should
* be empty.
* *
* @param {string} name method name * @param {string} name method name
* @param {*} value method value * @param {*} value method value
@ -45,12 +106,16 @@ module.exports = exports = function MemberBuilderValidator( warn_handler )
* @param {Object} prev_data data of member being overridden * @param {Object} prev_data data of member being overridden
* @param {Object} prev_keywords keywords of member being overridden * @param {Object} prev_keywords keywords of member being overridden
* *
* @param {Object} state pre-initialized state object
*
* @return {undefined} * @return {undefined}
*/ */
exports.prototype.validateMethod = function( exports.prototype.validateMethod = function(
name, value, keywords, prev_data, prev_keywords name, value, keywords, prev_data, prev_keywords, state
) )
{ {
this._initState( state );
var prev = ( prev_data ) ? prev_data.member : null; var prev = ( prev_data ) ? prev_data.member : null;
if ( keywords[ 'abstract' ] ) if ( keywords[ 'abstract' ] )
@ -202,7 +267,7 @@ exports.prototype.validateMethod = function(
// but it shouldn't stop the class definition (it doesn't adversely // but it shouldn't stop the class definition (it doesn't adversely
// affect the functionality of the class, unless of course the method // affect the functionality of the class, unless of course the method
// attempts to reference a supertype) // attempts to reference a supertype)
this._warningHandler( Error( _addWarn( state, name, 'no', Error(
"Method '" + name + "Method '" + name +
"' using 'override' keyword without super method" "' using 'override' keyword without super method"
) ); ) );

View File

@ -190,6 +190,9 @@ var extend = ( function( extending )
prototype = new base(), prototype = new base(),
iname = '', iname = '',
// holds validation state
vstate = {},
members = member_builder.initMembers( members = member_builder.initMembers(
prototype, prototype, prototype prototype, prototype, prototype
) )
@ -235,7 +238,8 @@ var extend = ( function( extending )
} }
member_builder.buildMethod( member_builder.buildMethod(
members, null, name, value, keywords members, null, name, value, keywords,
null, 0, {}, vstate
); );
}, },
} ); } );

View File

@ -38,16 +38,6 @@ require( 'common' ).testCase(
}, },
setUp: function()
{
this.builder = this.Sut(
this.require( 'MemberBuilder' )(),
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()
);
},
/** /**
* It's always useful to be able to quickly reference a list of reserved * It's always useful to be able to quickly reference a list of reserved
* members so that an implementer can programatically handle runtime * members so that an implementer can programatically handle runtime
@ -288,4 +278,32 @@ require( 'common' ).testCase(
_self.AbstractClass( dfn ); _self.AbstractClass( dfn );
} ); } );
}, },
/**
* During the course of processing, certain data are accumulated into
* the member builder state; this state must be post-processed to
* complete anything that may be pending.
*/
'Member builder state is ended after processing': function()
{
var _self = this,
build = this.require( 'MemberBuilder' )();
var sut = this.Sut(
build,
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()
);
// TODO: test that we're passed the right state
var called = false;
build.end = function( state )
{
called = true;
};
sut.build( {} );
this.assertOk( called );
},
} ); } );

View File

@ -30,30 +30,32 @@ require( 'common' ).testCase(
{ {
var _self = this; var _self = this;
this.testArgs = function( args, name, value, keywords ) this.testArgs = function( args, name, value, keywords, state )
{ {
shared.testArgs( _self, args, name, value, keywords, function( shared.testArgs( _self, args, name, value, keywords, state,
prev_default, pval_given, pkey_given function(
) prev_default, pval_given, pkey_given
{ )
var expected = _self.members[ 'public' ][ name ];
if ( !expected )
{ {
return prev_default; var expected = _self.members[ 'public' ][ name ];
}
return { if ( !expected )
value: { {
expected: expected, return prev_default;
given: pval_given.member, }
},
keywords: { return {
expected: null, // XXX value: {
given: pkey_given, expected: expected,
}, given: pval_given.member,
}; },
} ); keywords: {
expected: null, // XXX
given: pkey_given,
},
};
}
);
}; };
}, },

View File

@ -27,30 +27,32 @@ require( 'common' ).testCase(
{ {
var _self = this; var _self = this;
this.testArgs = function( args, name, value, keywords ) this.testArgs = function( args, name, value, keywords, state )
{ {
shared.testArgs( _self, args, name, value, keywords, function( shared.testArgs( _self, args, name, value, keywords, state,
prev_default, pval_given, pkey_given function(
) prev_default, pval_given, pkey_given
{ )
var expected = _self.members[ 'public' ][ name ];
if ( !expected )
{ {
return prev_default; var expected = _self.members[ 'public' ][ name ];
}
return { if ( !expected )
value: { {
expected: expected, return prev_default;
given: pval_given.member, }
},
keywords: { return {
expected: expected.___$$keywords$$, // XXX value: {
given: pkey_given, expected: expected,
}, given: pval_given.member,
}; },
} ); keywords: {
expected: expected.___$$keywords$$, // XXX
given: pkey_given,
},
};
}
);
}; };
// simply intended to execute test two two perspectives // simply intended to execute test two two perspectives
@ -100,17 +102,19 @@ require( 'common' ).testCase(
name = 'foo', name = 'foo',
value = function() {}, value = function() {},
state = {},
keywords = {} keywords = {}
; ;
this.mockValidate.validateMethod = function() this.mockValidate.validateMethod = function()
{ {
called = true; called = true;
_self.testArgs( arguments, name, value, keywords ); _self.testArgs( arguments, name, value, keywords, state );
}; };
this.assertOk( this.sut.buildMethod( this.assertOk( this.sut.buildMethod(
this.members, {}, name, value, keywords, function() {}, 1, {} this.members, {}, name, value, keywords, function() {}, 1, {},
state
) ); ) );
this.assertEqual( true, called, 'validateMethod() was not called' ); this.assertEqual( true, called, 'validateMethod() was not called' );

View File

@ -27,30 +27,32 @@ require( 'common' ).testCase(
{ {
var _self = this; var _self = this;
this.testArgs = function( args, name, value, keywords ) this.testArgs = function( args, name, value, keywords, state )
{ {
shared.testArgs( _self, args, name, value, keywords, function( shared.testArgs( _self, args, name, value, keywords, state,
prev_default, pval_given, pkey_given function(
) prev_default, pval_given, pkey_given
{ )
var expected = _self.members[ 'public' ][ name ];
if ( !expected )
{ {
return prev_default; var expected = _self.members[ 'public' ][ name ];
}
return { if ( !expected )
value: { {
expected: expected[ 0 ], return prev_default;
given: pval_given.member[ 0 ], }
},
keywords: { return {
expected: expected[ 1 ], value: {
given: pkey_given, expected: expected[ 0 ],
}, given: pval_given.member[ 0 ],
}; },
} ); keywords: {
expected: expected[ 1 ],
given: pkey_given,
},
};
}
);
}; };
}, },

View File

@ -27,11 +27,14 @@
* @param {string} name member name * @param {string} name member name
* @param {*} value expected value * @param {*} value expected value
* @param {Object} keywords expected keywords * @param {Object} keywords expected keywords
* @param {function()} prevLookup function to use to look up prev member data * @param {Object} state validation state
* @param {function()} prevLookup function to look up prev member data
* *
* @return {undefined} * @return {undefined}
*/ */
exports.testArgs = function( testcase, args, name, value, keywords, prevLookup ) exports.testArgs = function(
testcase, args, name, value, keywords, state, prevLookup
)
{ {
var prev = { var prev = {
value: { expected: null, given: args[ 3 ] }, value: { expected: null, given: args[ 3 ] },
@ -41,24 +44,28 @@ exports.testArgs = function( testcase, args, name, value, keywords, prevLookup )
prev = prevLookup( prev, prev.value.given, prev.keywords.given ); prev = prevLookup( prev, prev.value.given, prev.keywords.given );
testcase.assertEqual( name, args[ 0 ], testcase.assertEqual( name, args[ 0 ],
'Incorrect name passed to validator' "Incorrect name passed to validator"
); );
testcase.assertDeepEqual( value, args[ 1 ], testcase.assertDeepEqual( value, args[ 1 ],
'Incorrect value passed to validator' "Incorrect value passed to validator"
); );
testcase.assertStrictEqual( keywords, args[ 2 ], testcase.assertStrictEqual( keywords, args[ 2 ],
'Incorrect keywords passed to validator' "Incorrect keywords passed to validator"
); );
testcase.assertStrictEqual( prev.value.expected, prev.value.given, testcase.assertStrictEqual( prev.value.expected, prev.value.given,
'Previous data should contain prev value if overriding, ' + "Previous data should contain prev value if overriding, " +
'otherwise null' "otherwise null"
); );
testcase.assertDeepEqual( prev.keywords.expected, prev.keywords.given, testcase.assertDeepEqual( prev.keywords.expected, prev.keywords.given,
'Previous keywords should contain prev keyword if ' + "Previous keywords should contain prev keyword if " +
'overriding, otherwise null' "overriding, otherwise null"
);
testcase.assertStrictEqual( state, args[ 5 ],
"State object was not passed to validator"
); );
}; };

View File

@ -51,13 +51,18 @@ require( 'common' ).testCase(
startobj.virtual = true; startobj.virtual = true;
overrideobj.override = true; overrideobj.override = true;
var state = {};
_self.sut.validateMethod( _self.sut.validateMethod(
name, name,
function() {}, function() {},
overrideobj, overrideobj,
{ member: function() {} }, { member: function() {} },
startobj startobj,
state
); );
_self.sut.end( state );
}, },
failstr failstr
); );
@ -128,7 +133,7 @@ require( 'common' ).testCase(
_self.sut.validateMethod( _self.sut.validateMethod(
name, function() {}, {}, name, function() {}, {},
{ get: function() {} }, { get: function() {} },
{} {}, {}
); );
} ); } );
@ -138,7 +143,7 @@ require( 'common' ).testCase(
_self.sut.validateMethod( _self.sut.validateMethod(
name, function() {}, {}, name, function() {}, {},
{ set: function() {} }, { set: function() {} },
{} {}, {}
); );
} ); } );
}, },
@ -161,7 +166,7 @@ require( 'common' ).testCase(
_self.sut.validateMethod( _self.sut.validateMethod(
name, function() {}, {}, name, function() {}, {},
{ member: 'immaprop' }, { member: 'immaprop' },
{} {}, {}
); );
} ); } );
}, },
@ -246,7 +251,8 @@ require( 'common' ).testCase(
// this function returns each of its arguments, otherwise // this function returns each of its arguments, otherwise
// they'll be optimized away by Closure Compiler. // they'll be optimized away by Closure Compiler.
{ member: function( a, b, c ) { return [a,b,c]; } }, { member: function( a, b, c ) { return [a,b,c]; } },
{ 'virtual': true } { 'virtual': true },
{}
); );
} ); } );
@ -262,7 +268,8 @@ require( 'common' ).testCase(
function() {}, function() {},
{ 'override': true }, { 'override': true },
{ member: parent_method }, { member: parent_method },
{ 'virtual': true } { 'virtual': true },
{}
); );
} ); } );
@ -277,7 +284,8 @@ require( 'common' ).testCase(
method, method,
{ 'override': true }, { 'override': true },
{ member: function( a, b, c ) {} }, { member: function( a, b, c ) {} },
{ 'virtual': true } { 'virtual': true },
{}
); );
}, Error ); }, Error );
}, },
@ -304,7 +312,8 @@ require( 'common' ).testCase(
function() {}, function() {},
{}, {},
{ member: amethod }, { member: amethod },
{ 'weak': true, 'abstract': true } { 'weak': true, 'abstract': true },
{}
); );
} ); } );
@ -316,7 +325,7 @@ require( 'common' ).testCase(
amethod, amethod,
{ 'weak': true, 'abstract': true }, { 'weak': true, 'abstract': true },
{ member: function() {} }, { member: function() {} },
{} {}, {}
); );
} ); } );
}, },
@ -448,7 +457,7 @@ require( 'common' ).testCase(
{ {
// provide function instead of string // provide function instead of string
_self.sut.validateMethod( _self.sut.validateMethod(
name, function() {}, { 'proxy': true }, {}, {} name, function() {}, { 'proxy': true }, {}, {}, {}
); );
} ); } );
}, },
@ -464,7 +473,7 @@ require( 'common' ).testCase(
this.assertDoesNotThrow( function() this.assertDoesNotThrow( function()
{ {
_self.sut.validateMethod( _self.sut.validateMethod(
'foo', 'dest', { 'proxy': true }, {}, {} 'foo', 'dest', { 'proxy': true }, {}, {}, {}
); );
}, TypeError ); }, TypeError );
}, },

View File

@ -87,6 +87,7 @@ exports.quickKeywordTest = function(
prev_obj = {}, prev_obj = {},
prev_data = prev_data || {}, prev_data = prev_data || {},
name = exports.testName, name = exports.testName,
state = {},
_self = this; _self = this;
// convert our convenient array into a keyword obj // convert our convenient array into a keyword obj
@ -114,7 +115,7 @@ exports.quickKeywordTest = function(
var val = ( keyword_obj[ 'proxy' ] ) ? 'proxyDest': function() {}; var val = ( keyword_obj[ 'proxy' ] ) ? 'proxyDest': function() {};
_self.sut[ type ]( _self.sut[ type ](
name, val, keyword_obj, prev_data, prev_obj name, val, keyword_obj, prev_data, prev_obj, state
); );
}; };
@ -126,6 +127,8 @@ exports.quickKeywordTest = function(
{ {
this.assertDoesNotThrow( testfunc ); this.assertDoesNotThrow( testfunc );
} }
this.sut.end( state );
}; };