1
0
Fork 0

Fail intelligently if provided class in place of definition object

This is intended mainly to handle cases where the user forgets the second
argument when extending a class:

  Class.extend( Base )  // missing second argument (definition object)

* lib/ClassBuilder.js
  (exports.isClass): Move from lib/class.js.
  (exports.prototype.build): Throw error if definition object is a class.
* lib/class.js (module.exports.isClass): Reference `ClassBuilder.isClass'.
* test/Class/ExtendTest.js: Add respective test case.
master
Mike Gerwitz 2017-11-04 01:07:06 -04:00
parent 55f47a3c5a
commit 60920f18a6
Signed by: mikegerwitz
GPG Key ID: 8C917B7F5DC51BA2
3 changed files with 66 additions and 16 deletions

View File

@ -305,6 +305,30 @@ exports.isInstanceOf = function( type, instance )
}
/**
* Determines whether the provided object is a class created through ease.js
*
* @param {Object} obj object to test
*
* @return {boolean} true if class (created through ease.js), otherwise
* false
*/
module.exports.isClass = function( obj )
{
obj = obj || _dummyclass;
var meta = module.exports.getMeta( obj );
// TODO: we're checking a random field on the meta object; do something
// proper
return ( ( ( meta !== null ) && meta.implemented )
|| ( obj.prototype instanceof module.exports.ClassBase ) )
? true
: false
;
};
/**
* Wrapper around ECMAScript instanceof check
*
@ -383,6 +407,15 @@ exports.prototype.build = function extend( _, __ )
|| {}
;
// respond intelligently if the definition object is mistakenly a class
if ( module.exports.isClass( props ) )
{
throw TypeError( ( an > 1 )
? "Expected class definition, but found class " + props.toString()
: "Missing second argument to extend class " + props.toString()
);
}
// prevent extending final classes (TODO: abstract this check)
if ( base.___$$final$$ === true )
{

View File

@ -171,26 +171,11 @@ var _dummyinst = { constructor: { prototype: {} } };
/**
* Determines whether the provided object is a class created through ease.js
*
* TODO: delegate to ClassBuilder
*
* @param {Object} obj object to test
*
* @return {boolean} true if class (created through ease.js), otherwise false
*/
module.exports.isClass = function( obj )
{
obj = obj || _dummyclass;
var meta = ClassBuilder.getMeta( obj );
// TODO: we're checking a random field on the meta object; do something
// proper
return ( ( ( meta !== null ) && meta.implemented )
|| ( obj.prototype instanceof ClassBuilder.ClassBase ) )
? true
: false
;
};
module.exports.isClass = ClassBuilder.isClass;
/**

View File

@ -468,4 +468,36 @@ require( 'common' ).testCase(
// protected foo was recognized
this.assertOk( called );
},
/**
* When a single argument is provided to `#extend' in the form
* `C.extend(o)', it is assumed that `o' is the definition
* object. However, it's also possible that the user provided the class
* to extend but _forgot_ the definition object.
*
* In this case, prior to this change, the supertype was treated as the
* definition object, which yielded some pretty confusing results. It's
* better to help the user out and provide a clear indication of what
* went wrong.
*/
'Extending with one argument that is a class yields an error': function()
{
var _self = this;
// missing second argument
this.assertThrows( function()
{
_self.Sut.extend( _self.Sut( 'Whoops', {} ) );
}, TypeError );
// class provided as second argument (>_>)
this.assertThrows( function()
{
_self.Sut.extend(
_self.Sut( 'Base', {} ),
_self.Sut( 'Wth', {} )
);
}, TypeError );
}
} );