189 lines
5.3 KiB
JavaScript
189 lines
5.3 KiB
JavaScript
/**
|
|
* Validation rules for members
|
|
*
|
|
* Copyright (C) 2010 Mike Gerwitz
|
|
*
|
|
* This file is part of ease.js.
|
|
*
|
|
* ease.js is free software: you can redistribute it and/or modify it under the
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* @author Mike Gerwitz
|
|
* @package core
|
|
*/
|
|
|
|
|
|
module.exports = exports = function MemberBuilderValidator()
|
|
{
|
|
// permit omitting 'new' keyword
|
|
if ( !( this instanceof module.exports ) )
|
|
{
|
|
return new module.exports();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Validates a method declaration, ensuring that keywords are valid, overrides
|
|
* make sense, etc.
|
|
*
|
|
* Throws exception on validation failure
|
|
*
|
|
* @param {string} name method name
|
|
* @param {*} value method value
|
|
*
|
|
* @param {Object.<string,boolean>} keywords parsed keywords
|
|
*
|
|
* @param {Object} prev_data data of member being overridden
|
|
* @param {Object} prev_keywords keywords of member being overridden
|
|
*
|
|
* @return {undefined}
|
|
*/
|
|
exports.prototype.validateMethod = function(
|
|
name, value, keywords, prev_data, prev_keywords
|
|
)
|
|
{
|
|
var prev = ( prev_data ) ? prev_data.member : null;
|
|
|
|
if ( keywords[ 'abstract' ] )
|
|
{
|
|
// do not permit private abstract methods (doesn't make sense, since
|
|
// they cannot be inherited/overridden)
|
|
if ( keywords[ 'private' ] )
|
|
{
|
|
throw TypeError(
|
|
"Method '" + name + "' cannot be both private and abstract"
|
|
);
|
|
}
|
|
}
|
|
|
|
// const doesn't make sense for methods; they're always immutable
|
|
if ( keywords[ 'const' ] )
|
|
{
|
|
throw TypeError(
|
|
"Cannot declare method '" + name + "' as constant; keyword is " +
|
|
"redundant"
|
|
);
|
|
}
|
|
|
|
// virtual static does not make sense, as static methods cannot be
|
|
// overridden
|
|
if ( keywords[ 'virtual' ] && ( keywords[ 'static' ] ) )
|
|
{
|
|
throw TypeError(
|
|
"Cannot declare static method '" + name + "' as virtual"
|
|
);
|
|
}
|
|
|
|
// do not allow overriding getters/setters
|
|
if ( prev_data && ( prev_data.get || prev_data.set ) )
|
|
{
|
|
throw TypeError(
|
|
"Cannot override getter/setter '" + name + "' with method"
|
|
);
|
|
}
|
|
|
|
// search for any previous instances of this member
|
|
if ( prev )
|
|
{
|
|
// disallow overriding properties with methods
|
|
if ( !( typeof prev === 'function' ) )
|
|
{
|
|
throw TypeError(
|
|
"Cannot override property '" + name + "' with method"
|
|
);
|
|
}
|
|
|
|
// disallow overriding non-virtual methods
|
|
if ( keywords[ 'override' ] && !( prev_keywords[ 'virtual' ] ) )
|
|
{
|
|
throw TypeError(
|
|
"Cannot override non-virtual method '" + name + "'"
|
|
);
|
|
}
|
|
|
|
// do not allow overriding concrete methods with abstract
|
|
if ( keywords[ 'abstract' ] && !( prev_keywords[ 'abstract' ] ) )
|
|
{
|
|
throw TypeError(
|
|
"Cannot override concrete method '" + name + "' with " +
|
|
"abstract method"
|
|
);
|
|
}
|
|
|
|
// ensure parameter list is at least the length of its supertype
|
|
if ( ( value.__length || value.length )
|
|
< ( prev.__length || prev.length )
|
|
)
|
|
{
|
|
throw TypeError(
|
|
"Declaration of method '" + name + "' must be compatible " +
|
|
"with that of its supertype"
|
|
);
|
|
}
|
|
|
|
// do not permit visibility deescalation
|
|
if ( this._getVisibilityValue( prev_keywords ) <
|
|
this._getVisibilityValue( keywords )
|
|
)
|
|
{
|
|
throw TypeError(
|
|
"Cannot de-escalate visibility of method '" + name + "'"
|
|
);
|
|
}
|
|
|
|
// if redefining a method that has already been implemented in the
|
|
// supertype, the default behavior is to "hide" the method of the
|
|
// supertype, unless otherwise specified
|
|
//
|
|
// IMPORTANT: do this last, to ensure we throw errors before warnings
|
|
if ( !( keywords[ 'override' ] )
|
|
{
|
|
if ( !( prev_keywords[ 'abstract' ] ) )
|
|
{
|
|
throw Warning( Error(
|
|
"Hiding method '" + name + "'; " +
|
|
"use 'new' if intended, or 'override' to override instead"
|
|
) );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
exports.prototype._getVisibilityValue = function( keywords )
|
|
{
|
|
if ( keywords[ 'protected' ] )
|
|
{
|
|
return 1;
|
|
}
|
|
else if ( keywords[ 'private' ] )
|
|
{
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
// default is public
|
|
return 0;
|
|
}
|
|
}
|
|
|