1
0
Fork 0

Began encapsulating internal metadata with symbols

This is a move to solve a couple issues:
  - We shouldn't be adding a ton of non-enumerable stuff to an object---
    defineProperty is slow; and
  - One object containing all internal data (at least in general; more on
    that later) makes ignoring it, copying it, or otherwise operating upon
    it a much easier and cleaner task.
protolib
Mike Gerwitz 2014-07-09 23:55:19 -04:00
commit 1458d79ee1
No known key found for this signature in database
GPG Key ID: F22BB8158EE30EAB
33 changed files with 1192 additions and 514 deletions

View File

@ -25,8 +25,8 @@
*/
var util = require( './util' ),
warn = require( './warn' ),
Warning = warn.Warning,
Warning = require( './warn' ).Warning,
Symbol = require( './util/Symbol' ),
hasOwn = Object.prototype.hasOwnProperty,
@ -74,7 +74,18 @@ var util = require( './util' ),
'__construct': true,
'toString': true,
'__toString': true,
};
},
/**
* Symbol used to encapsulate internal data
*
* Note that this is intentionally generated *outside* the ClassBuilder
* instance; this ensures that it is properly encapsulated and will not
* be exposed on the Classbuilder instance (which would defeat the
* purpose).
*/
_priv = Symbol()
;
/**
@ -90,15 +101,23 @@ var util = require( './util' ),
* @constructor
*/
module.exports = exports =
function ClassBuilder( member_builder, visibility_factory )
function ClassBuilder( warn_handler, member_builder, visibility_factory )
{
// allow ommitting the 'new' keyword
if ( !( this instanceof exports ) )
{
// module.exports for Closure Compiler
return new module.exports( member_builder, visibility_factory );
return new module.exports(
warn_handler, member_builder, visibility_factory
);
}
/**
* Determines how warnings should be handled
* @type {WarningHandler}
*/
this._warnHandler = warn_handler;
/**
* Used for building class members
* @type {Object}
@ -214,11 +233,11 @@ exports.getForcedPublicMethods = function()
*
* @param {Function|Object} cls class from which to retrieve metadata
*
* @return {__class_meta}
* @return {__class_meta} or null if unavailable
*/
exports.getMeta = function( cls )
{
return cls.___$$meta$$ || {};
return ( cls[ _priv ] || {} ).meta || null;
}
@ -319,12 +338,14 @@ exports.prototype.build = function extend( _, __ )
props: this._memberBuilder.initMembers(),
},
meta = exports.getMeta( base ) || {},
abstract_methods =
util.clone( exports.getMeta( base ).abstractMethods )
util.clone( meta.abstractMethods )
|| { __length: 0 },
virtual_members =
util.clone( exports.getMeta( base ).virtualMembers )
util.clone( meta.virtualMembers )
|| {}
;
@ -333,7 +354,7 @@ exports.prototype.build = function extend( _, __ )
{
throw Error(
"Cannot extend final class " +
( base.___$$meta$$.name || '(anonymous)' )
( base[ _priv ].meta.name || '(anonymous)' )
);
}
@ -366,7 +387,7 @@ exports.prototype.build = function extend( _, __ )
// properties initialized by the ctor are implicitly public; otherwise,
// proxying will fail to take place
// TODO: see Class.isA TODO
if ( prototype.___$$vis$$ === undefined )
if ( ( prototype[ _priv ] || {} ).vis === undefined )
{
this._discoverProtoProps( prototype, prop_init );
}
@ -396,7 +417,7 @@ exports.prototype.build = function extend( _, __ )
// intercept warnings /only/
if ( e instanceof Warning )
{
warn.handle( e );
this._warnHandler.handle( e );
}
else
{
@ -790,16 +811,19 @@ function validateAbstract( ctor, cname, abstract_methods, auto )
*/
exports.prototype.createCtor = function( cname, abstract_methods, members )
{
// concrete class
var new_class;
if ( abstract_methods.__length === 0 )
{
return this.createConcreteCtor( cname, members );
new_class = this.createConcreteCtor( cname, members );
}
// abstract class
else
{
return this.createAbstractCtor( cname );
new_class = this.createAbstractCtor( cname );
}
util.defineSecureProp( new_class, _priv, {} );
return new_class;
}
@ -858,7 +882,9 @@ exports.prototype.createConcreteCtor = function( cname, members )
// handle internal trait initialization logic, if provided
if ( typeof this.___$$tctor$$ === 'function' )
{
this.___$$tctor$$.call( this );
// FIXME: we're exposing _priv to something that can be
// malicously set by the user; encapsulate tctor
this.___$$tctor$$.call( this, _priv );
}
// call the constructor, if one was provided
@ -982,7 +1008,8 @@ exports.prototype._attachPropInit = function(
inherit = !!inherit;
var iid = this.__iid,
parent = prototype.___$$parent$$;
parent = prototype.___$$parent$$,
vis = this[ _priv ].vis;
// first initialize the parent's properties, so that ours will overwrite
// them
@ -998,7 +1025,7 @@ exports.prototype._attachPropInit = function(
// this will return our property proxy, if supported by our environment,
// otherwise just a normal object with everything merged in
var inst_props = _self._visFactory.createPropProxy(
this, this.___$$vis$$, properties[ 'public' ]
this, vis, properties[ 'public' ]
);
// Copies all public and protected members into inst_props and stores
@ -1006,7 +1033,7 @@ exports.prototype._attachPropInit = function(
// chain and is returned. This is stored in a property referenced by the
// class id, so that the private members can be swapped on each method
// request, depending on calling context.
var vis = this.___$$vis$$[ cid ] = _self._visFactory.setup(
var vis = vis[ cid ] = _self._visFactory.setup(
inst_props, properties, members
);
@ -1230,21 +1257,13 @@ function createMeta( func, cparent )
// copy the parent prototype's metadata if it exists (inherit metadata)
if ( parent_meta )
{
func.___$$meta$$ = util.clone( parent_meta, true );
}
else
{
// create empty
func.___$$meta$$ = {
implemented: [],
};
return func[ _priv ].meta = util.clone( parent_meta, true );
}
// store the metadata in the prototype as well (inconsiderable overhead;
// it's just a reference)
func.prototype.___$$meta$$ = func.___$$meta$$;
return func.___$$meta$$;
// create empty
return func[ _priv ].meta = {
implemented: [],
};
}
@ -1295,8 +1314,12 @@ function initInstance( instance )
var prot = function() {};
prot.prototype = instance;
// initialize our *own* private metadata store; do not use the
// prototype's
util.defineSecureProp( instance, _priv, {} );
// add the visibility objects to the data object for this class instance
instance.___$$vis$$ = new prot();
instance[ _priv ].vis = new prot();
}
@ -1350,9 +1373,10 @@ exports.getMethodInstance = function( inst, cid )
}
var iid = inst.__iid,
data = inst.___$$vis$$;
priv = inst[ _priv ],
data;
return ( iid && data )
return ( iid && priv && ( data = priv.vis ) )
? data[ cid ]
: null
;

View File

@ -30,7 +30,6 @@
*/
var util = require( './util' ),
Warning = require( './warn' ).Warning,
visibility = [ 'public', 'protected', 'private' ]
;

View File

@ -673,12 +673,19 @@ function addTraitInst( T, dfn, tc, base )
* This will lazily create the concrete trait class if it does not already
* exist, which saves work if the trait is never used.
*
* @param {Object} tc trait class list
* @param {Class} base target supertype
* Note that the private symbol used to encapsulate class data must be
* passed to this function to provide us access to implementation details
* that we really shouldn't be messing around with. :) In particular, we
* need access to the protected visibility object, and there is [currently]
* no API for doing so.
*
* @param {Object} tc trait class list
* @param {Class} base target supertype
* @param {Symbol} privsym symbol used as key for encapsulated data
*
* @return {undefined}
*/
function tctor( tc, base )
function tctor( tc, base, privsym )
{
// instantiate all traits and assign the object to their
// respective fields
@ -693,11 +700,11 @@ function tctor( tc, base )
// (but not private); in return, we will use its own protected
// visibility object to gain access to its protected members...quite
// the intimate relationship
this[ f ] = C( base, this.___$$vis$$ ).___$$vis$$;
this[ f ] = C( base, this[ privsym ].vis )[ privsym ].vis;
}
// if we are a subtype, be sure to initialize our parent's traits
this.__super && this.__super();
this.__super && this.__super( privsym );
};
@ -714,9 +721,9 @@ function tctor( tc, base )
*/
function createTctor( tc, base )
{
return function()
return function( privsym )
{
return tctor.call( this, tc, base );
return tctor.call( this, tc, base, privsym );
};
}

View File

@ -19,16 +19,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Console to use for logging
*
* This reference allows an alternative console to be used. Must contain
* warn() or log() methods.
*
* TODO: This needs to be moved into a facade, once more refactoring can be
* done; it was moved out of warn during its refactoring.
*
* @type {Object}
*/
var _console = ( typeof console !== 'undefined' ) ? console : undefined;
var util = require( './util' ),
ClassBuilder = require( './ClassBuilder' ),
warn = require( './warn' ),
Warning = warn.Warning,
warn = require( './warn' ),
Warning = warn.Warning,
log_handler = warn.LogHandler( _console ),
MethodWrapperFactory = require( './MethodWrapperFactory' ),
wrappers = require( './MethodWrappers' ).standard,
class_builder = ClassBuilder(
log_handler,
require( './MemberBuilder' )(
MethodWrapperFactory( wrappers.wrapNew ),
MethodWrapperFactory( wrappers.wrapOverride ),
@ -36,7 +51,7 @@ var util = require( './util' ),
require( './MemberBuilderValidator' )(
function( warning )
{
warn.handle( Warning( warning ) );
log_handler.handle( Warning( warning ) );
}
)
),
@ -154,6 +169,8 @@ 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
@ -167,10 +184,11 @@ module.exports.isClass = function( obj )
return false;
}
// TODO: this just checks one of many internal fields; we need something
// more formal (cannot use a strict ClassBase check because it will fail
// when extending prototypes)
return ( ( obj.prototype.___$$vis$$ !== undefined )
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
@ -182,6 +200,8 @@ module.exports.isClass = function( obj )
* Determines whether the provided object is an instance of a class created
* through ease.js
*
* TODO: delegate to ClassBuilder
*
* @param {Object} obj object to test
*
* @return {boolean} true if instance of class (created through ease.js),
@ -191,16 +211,8 @@ module.exports.isClassInstance = function( obj )
{
obj = obj || _dummyinst;
if ( !obj.constructor || !obj.constructor.prototype )
{
return false;
}
// TODO: see isClass TODO
return ( ( obj.constructor.prototype.___$$vis$$ !== undefined )
|| ( obj instanceof ClassBuilder.ClassBase ) )
? true
: false;
// if the constructor is a class, then we must be an instance!
return module.exports.isClass( obj.constructor );
};

View File

@ -492,10 +492,13 @@ function attachInstanceOf( iface )
*/
function _isInstanceOf( type, instance )
{
// we are interested in the class's metadata, not the instance's
var proto = instance.constructor;
// if no metadata are available, then our remaining checks cannot be
// performed
var meta;
if ( !instance.__cid || !( meta = ClassBuilder.getMeta( instance ) ) )
if ( !instance.__cid || !( meta = ClassBuilder.getMeta( proto ) ) )
{
return false;
}

View File

@ -52,6 +52,9 @@ var can_define_prop = ( function()
} )();
exports.Global = require( './util/Global' );
/**
* Freezes an object if freezing is supported
*

120
lib/util/Global.js 100644
View File

@ -0,0 +1,120 @@
/**
* Global scope handling
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// retrieve global scope; works with ES5 strict mode
(0,eval)( 'var _the_global=this' );
// prototype to allow us to augment the global scope for our own purposes
// without polluting the global scope
function _G() {}
_G.prototype = _the_global;
/**
* Provides access to and augmentation of global variables
*
* This provides a static method to consistently provide access to the
* object representing the global scope, regardless of environment. Through
* instantiation, its API permits augmenting a local object whose prototype
* is the global scope, providing alternatives to variables that do not
* exist.
*/
function Global()
{
// allows omitting `new` keyword, consistent with ease.js style
if ( !( this instanceof Global ) )
{
return new Global();
}
// allows us to extend the global object without actually polluting the
// global scope
this._global = new _G();
}
/**
* Provides consistent access to the global scope through all ECMAScript
* versions, for any root variable name, and works with ES5 strict mode.
*
* As an example, Node.js exposes the variable `root` to represent global
* scope, but browsers expose `window`. Further, ES5 strict mode will provde
* an error when checking whether `typeof SomeGlobalVar === 'undefined'`.
*
* @return {Object} global object
*/
Global.expose = function()
{
return _the_global;
};
Global.prototype = {
/**
* Provide a value for the provided global variable name if it is not
* defined
*
* A function returning the value to assign to NAME should be provided,
* ensuring that the alternative is never even evaluated unless it is
* needed.
*
* The global scope will not be polluted with this alternative;
* consequently, you must access the value using the `get` method.
*
* @param {string} name global variable name
* @param {function()} f function returning value to assign
*
* @return {Global} self
*/
provideAlt: function( name, f )
{
if ( typeof this._global[ name ] !== 'undefined' )
{
return;
}
this._global[ name ] = f();
return this;
},
/**
* Retrieve global value or provided alternative
*
* This will take into account values provided via `provideAlt`; if no
* alternative was provided, the request will be deleagated to the
* global variable NAME, which may or may not be undefined.
*
* No error will be thrown if NAME is not globally defined.
*
* @param {string} name global variable name
*
* @return {*} value associated with global variable NAME or
* its provided alternative
*/
get: function( name )
{
return this._global[ name ];
},
};
module.exports = Global;

31
lib/util/Symbol.js 100644
View File

@ -0,0 +1,31 @@
/**
* Forward-compatible subset of ES6 Symbol
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This is *not* intended to be a complete implementation; it merely
* performs what is needed for ease.js, preferring the benefits of the ES6
* Symbol implementation while falling back to sane ES5 and ES3 options.
*/
// to be used if there is no global Symbol available
var FallbackSymbol = require( './symbol/FallbackSymbol' );
var _root = require( './Global' ).expose();
module.exports = _root.Symbol || FallbackSymbol;

View File

@ -0,0 +1,88 @@
/**
* Forward-compatible subset of ES6 Symbol for pre-ES6 environments
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This is *not* intended to be a complete implementation; it merely
* performs what is needed for ease.js. In particular, this pre-ES6
* implementation will simply generate a random string to be used as a key;
* the caller is expected to add the key to the destination object as
* non-enumerable, if supported by the environment.
*/
// ensures that, so long as these methods have not been overwritten by the
// time ease.js is loaded, we will maintain a proper reference
var _random = Math.random,
_floor = Math.floor;
// prefix used for all generated symbol strings (this string is highly
// unlikely to exist in practice); it will produce a string containing a
// non-printable ASCII character that is *not* the null byte
var _root = ' ' + String.fromCharCode(
_floor( _random() * 10 ) % 31 + 1
) + '$';
/**
* Generate a pseudo-random string (with a common prefix) to be used as an
* object key
*
* The returned key is unique so long as Math.{random,floor} are reliable.
* This will be true so long as (1) the runtime provides a reliable
* implementation and (2) Math.{floor,random} have not been overwritten at
* the time that this module is loaded. This module stores an internal
* reference to this methods, so malicious code loaded after this module
* will not be able to compromise the return value.
*
* Note that the returned string is not wholly random: a common prefix is
* used to ensure that collisions with other keys on objects is highly
* unlikely; you should not rely on this behavior, though, as it is an
* implementation detail that may change in the future.
*
* @return {string} pseudo-random string with common prefix
*/
function FallbackSymbol()
{
if ( !( this instanceof FallbackSymbol ) )
{
return new FallbackSymbol();
}
this.___$$id$$ = ( _root + _floor( _random() * 1e8 ) );
}
FallbackSymbol.prototype = {
/**
* Return random identifier
*
* This is convenient, as it allows us to both treat the symbol as an
* object of type FallbackSymbol and use the symbol as a key (since
* doing so will automatically call this method).
*
* @return {string} random identifier
*/
toString: function()
{
return this.___$$id$$;
},
};
module.exports = FallbackSymbol;

View File

@ -1,7 +1,7 @@
/**
* ease.js warning system
*
* Copyright (C) 2011, 2012, 2013 Free Software Foundation, Inc.
* Copyright (C) 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
@ -19,184 +19,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Active warning handler
* @type {?function( Warning )}
*/
var _handler = null;
module.exports = {
Warning: require( './warn/Warning' ),
/**
* Console to use for logging
*
* This reference allows an alternative console to be used. Must contain warn()
* or log() methods.
*
* @type {Object}
*/
var _console = ( typeof console !== 'undefined' ) ? console : undefined;
/**
* Permits wrapping an exception as a warning
*
* Warnings are handled differently by the system, depending on the warning
* level that has been set.
*
* @param {Error} e exception (error) to wrap
*
* @return {Warning} new warning instance
*
* @constructor
*/
var Warning = exports.Warning = function( e )
{
// allow instantiation without use of 'new' keyword
if ( !( this instanceof Warning ) )
{
return new Warning( e );
}
// ensure we're wrapping an exception
if ( !( e instanceof Error ) )
{
throw TypeError( "Must provide exception to wrap" );
}
Error.prototype.constructor.call( this, e.message );
// copy over the message for convenience
this.message = e.message;
this.name = 'Warning';
this._error = e;
this.stack = e.stack &&
e.stack.replace( /^.*?\n+/,
this.name + ': ' + this.message + "\n"
);
DismissiveHandler: require( './warn/DismissiveHandler' ),
LogHandler: require( './warn/LogHandler' ),
ThrowHandler: require( './warn/ThrowHandler' ),
};
// ensures the closest compatibility...just be careful not to modify Warning's
// prototype
Warning.prototype = Error();
Warning.prototype.constructor = Warning;
Warning.prototype.name = 'Warning';
/**
* Return the error wrapped by the warning
*
* @return {Error} wrapped error
*/
Warning.prototype.getError = function()
{
return this._error;
};
/**
* Core warning handlers
* @type {Object}
*/
exports.handlers = {
/**
* Logs message to console
*
* Will attempt to log using console.warn(), falling back to console.log()
* if necessary and aborting entirely if neither is available.
*
* This is useful as a default option to bring problems to the developer's
* attention without affecting the control flow of the software.
*
* @param {Warning} warning to log
*
* @return {undefined}
*/
log: function( warning )
{
var dest;
_console && ( dest = _console.warn || _console.log ) &&
dest.call( _console, ( 'Warning: ' + warning.message ) );
},
/**
* Throws the error associated with the warning
*
* This handler is useful for development and will ensure that problems are
* brought to the attention of the developer.
*
* @param {Warning} warning to log
*
* @return {undefined}
*/
throwError: function( warning )
{
throw warning.getError();
},
/**
* Ignores warnings
*
* This is useful in a production environment where (a) warnings will affect
* the reputation of the software or (b) warnings may provide too much
* insight into the software. If using this option, you should always
* develop in a separate environment so that the system may bring warnings
* to your attention.
*
* @param {Warning} warning to log
*
* @return {undefined}
*/
dismiss: function( warning )
{
// do nothing
},
};
/**
* Sets the active warning handler
*
* You may use any of the predefined warning handlers or pass your own function.
*
* @param {function( Warning )} handler warning handler
*
* @return {undefined}
*/
exports.setHandler = function( handler )
{
_handler = handler;
};
/**
* Handles a warning using the active warning handler
*
* @param {Warning} warning warning to handle
*
* @return {undefined}
*/
exports.handle = function( warning )
{
_handler( warning );
}
/**
* Sets active console
*
* @param {Object} console containing warn() or log() method
*
* @return {undefined}
*/
exports.setConsole = function( console )
{
_console = console;
};
// set the default handler
_handler = exports.handlers.log;

View File

@ -0,0 +1,51 @@
/**
* Dismissive warning handler
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Warning handler that dismisses (ignores) all warnings
*
* This is useful in a production environment.
*/
function DismissiveHandler()
{
if ( !( this instanceof DismissiveHandler ) )
{
return new DismissiveHandler();
}
}
DismissiveHandler.prototype = {
/**
* Handle a warning
*
* @param {Warning} warning warning to handle
* @return {undefined}
*/
handle: function( warning )
{
// intentionally do nothing
},
}
module.exports = DismissiveHandler;

View File

@ -0,0 +1,64 @@
/**
* Logging warning handler
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Warning handler that logs all warnings to a console
*
* @param {Object} console console with a warn or log method
*/
function LogHandler( console )
{
if ( !( this instanceof LogHandler ) )
{
return new LogHandler( console );
}
this._console = console || {};
}
LogHandler.prototype = {
/**
* Handle a warning
*
* Will attempt to log using console.warn(), falling back to
* console.log() if necessary and aborting entirely if neither is
* available.
*
* This is useful as a default option to bring problems to the
* developer's attention without affecting the control flow of the
* software.
*
* @param {Warning} warning warning to handle
* @return {undefined}
*/
handle: function( warning )
{
var dest = this._console.warn || this._console.log;
dest && dest.call( this._console,
'Warning: ' + warning.message
);
},
}
module.exports = LogHandler;

View File

@ -0,0 +1,54 @@
/**
* Throwing warning handler
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Warning handler that throws all warnings as exceptions
*/
function ThrowHandler()
{
if ( !( this instanceof ThrowHandler ) )
{
return new ThrowHandler();
}
}
ThrowHandler.prototype = {
/**
* Handle a warning
*
* Throws the error associated with the warning.
*
* This handler is useful for development and will ensure that problems
* are brought to the attention of the developer.
*
* @param {Warning} warning warning to handle
* @return {undefined}
*/
handle: function( warning )
{
throw warning.getError();
},
}
module.exports = ThrowHandler;

View File

@ -0,0 +1,81 @@
/**
* Warning prototype
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Permits wrapping an exception as a warning
*
* Warnings are handled differently by the system, depending on the warning
* level that has been set.
*
* @param {Error} e exception (error) to wrap
*
* @return {Warning} new warning instance
*
* @constructor
*/
function Warning( e )
{
// allow instantiation without use of 'new' keyword
if ( !( this instanceof Warning ) )
{
return new Warning( e );
}
// ensure we're wrapping an exception
if ( !( e instanceof Error ) )
{
throw TypeError( "Must provide exception to wrap" );
}
Error.prototype.constructor.call( this, e.message );
// copy over the message for convenience
this.message = e.message;
this.name = 'Warning';
this._error = e;
this.stack = e.stack &&
e.stack.replace( /^.*?\n+/,
this.name + ': ' + this.message + "\n"
);
};
// ensures the closest compatibility...just be careful not to modify Warning's
// prototype
Warning.prototype = Error();
Warning.prototype.constructor = Warning;
Warning.prototype.name = 'Warning';
/**
* Return the error wrapped by the warning
*
* @return {Error} wrapped error
*/
Warning.prototype.getError = function()
{
return this._error;
};
module.exports = Warning;

View File

@ -23,8 +23,6 @@ require( 'common' ).testCase(
{
caseSetUp: function()
{
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.Sut = this.require( 'ClassBuilder' );
this.MethodWrapperFactory = this.require( 'MethodWrapperFactory' );
@ -34,14 +32,17 @@ require( 'common' ).testCase(
setUp: function()
{
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.builder = this.Sut(
this.require( '/MemberBuilder' )(
this.require( 'warn' ).DismissiveHandler(),
this.require( 'MemberBuilder' )(
this.MethodWrapperFactory( this.wrappers.wrapNew ),
this.MethodWrapperFactory( this.wrappers.wrapOverride ),
this.MethodWrapperFactory( this.wrappers.wrapProxy ),
this.getMock( 'MemberBuilderValidator' )
),
this.require( '/VisibilityObjectFactoryFactory' )
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()
)
},

View File

@ -73,6 +73,7 @@ require( 'common' ).testCase(
{
// XXX: clean up this mess.
var builder = this.require( 'ClassBuilder' )(
this.require( 'warn' ).DismissiveHandler(),
this.require( 'MemberBuilder' )(),
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()

View File

@ -291,6 +291,7 @@ require( 'common' ).testCase(
build = this.require( 'MemberBuilder' )();
var sut = this.Sut(
this.require( 'warn' ).DismissiveHandler(),
build,
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()

View File

@ -26,8 +26,6 @@ require( 'common' ).testCase(
{
this.fallback = this.require( 'util' ).definePropertyFallback();
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.ClassBuilder = this.require( 'ClassBuilder' );
this.MemberBuilder = this.require( 'MemberBuilder' );
this.MethodWrapperFactory = this.require( 'MethodWrapperFactory' );
@ -38,7 +36,10 @@ require( 'common' ).testCase(
setUp: function()
{
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.builder = this.ClassBuilder(
this.require( 'warn' ).DismissiveHandler(),
this.MemberBuilder(
this.MethodWrapperFactory( this.wrappers.wrapNew ),
this.MethodWrapperFactory( this.wrappers.wrapOverride ),

View File

@ -25,8 +25,6 @@ require( 'common' ).testCase(
{
caseSetUp: function()
{
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.Sut = this.require( 'ClassBuilder' );
this.MethodWrapperFactory = this.require( 'MethodWrapperFactory' );
@ -37,7 +35,10 @@ require( 'common' ).testCase(
setUp: function()
{
// XXX: get rid of this disgusting mess; we're mid-refactor and all
// these dependencies should not be necessary for testing
this.builder = this.Sut(
this.require( 'warn' ).DismissiveHandler(),
this.require( '/MemberBuilder' )(
this.MethodWrapperFactory( this.wrappers.wrapNew ),
this.MethodWrapperFactory( this.wrappers.wrapOverride ),

View File

@ -0,0 +1,143 @@
/**
* Tests global scope handling
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.
*
* ease.js is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var _global = this;
require( 'common' ).testCase(
{
caseSetUp: function()
{
this.Sut = this.require( 'util/Global' );
this.gobj = this.Sut.expose();
this.uniq = '___$$easejs$globaltest$$';
},
/**
* Check common environments and ensure that the returned object is
* strictly equal to the global object for that environment. For
* environments that we do *not* know about, just check for a common
* object that must exist in ES3 and above.
*/
'Global object represents environment global object': function()
{
switch ( true )
{
// browser
case _global.window:
this.assertStrictEqual( this.gobj, _global.window );
break;
// Node.js
case _global.root:
this.assertStrictEqual( this.gobj, _global.root );
break;
// something else; we'll just check for something that should
// exist in >=ES3
default:
this.assertStrictEqual( this.gobj.Array, Array );
}
},
/**
* Since ease.js makes use of ECMAScript features when they are
* available, it must also find a way to gracefully degrade to support
* less fortunate environments; the ability to define alternative
* definitions is key to that.
*/
'Providing alternative will set value if name does not exist':
function()
{
var sut = this.Sut();
var field = this.uniq,
value = { _: 'easejsOK' };
sut.provideAlt( field, function() { return value; } );
this.assertStrictEqual( sut.get( field ), value );
},
/**
* It is also important that our own definitions do not pollute the
* global scope; reasons for this are not just to be polite, but also
* because other code/libraries may provide their own definitions that
* we would not want to interfere with. (Indeed, we'd also want to use
* those definitions, if they already exist before provideAlt is
* called.)
*/
'Providing alternative will not pollute the global scope': function()
{
this.Sut().provideAlt( this.uniq, function() { return {} } );
this.assertEqual( this.gobj[ this.uniq ], undefined );
},
/**
* Our alternatives are unneeded if the object we are providing an
* alternative for is already defined.
*/
'Providing alternative will not modify global if name exists':
function()
{
var sut = this.Sut();
// a field that must exist in ES3+
var field = 'Array',
orig = this.gobj[ field ];
sut.provideAlt( field, function() { return {}; } );
this.assertStrictEqual( sut.get( field ), orig );
},
/**
* Once an alternative is defined, it shall be treated as though the
* value were defined globally; providing additional alternatives should
* therefore have no effect.
*/
'Providing alternative twice will not modify first alternative':
function()
{
var sut = this.Sut();
field = this.uniq,
expected = { _: 'easejsOK' };
// first should provide alternative, second should do nothing
sut.provideAlt( field, function() { return expected; } );
sut.provideAlt( field, function() { return 'oops'; } );
this.assertStrictEqual( sut.get( field ), expected );
},
'provideAlt returns self for method chaining': function()
{
var sut = this.Sut();
this.assertStrictEqual( sut,
sut.provideAlt( 'foo', function() {} )
);
},
} );

View File

@ -0,0 +1,41 @@
/**
* Tests utility module entry point
*
* Copyright (C) 2014 Free Software Foundation, Inc.
*
* This file is part of GNU ease.js.