/** * 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 . * * @author Mike Gerwitz * @package core */ // XXX: remove dependency var Warning = require( __dirname + '/warn' ).Warning; 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.} 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[ 'new' ] || 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; } }