diff --git a/lib/ClassBuilder.js b/lib/ClassBuilder.js
index bb413d0..11bda6b 100644
--- a/lib/ClassBuilder.js
+++ b/lib/ClassBuilder.js
@@ -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
;
diff --git a/lib/MemberBuilder.js b/lib/MemberBuilder.js
index 777fcf6..8c394a1 100644
--- a/lib/MemberBuilder.js
+++ b/lib/MemberBuilder.js
@@ -30,7 +30,6 @@
*/
var util = require( './util' ),
- Warning = require( './warn' ).Warning,
visibility = [ 'public', 'protected', 'private' ]
;
diff --git a/lib/Trait.js b/lib/Trait.js
index febefb9..4bd17c2 100644
--- a/lib/Trait.js
+++ b/lib/Trait.js
@@ -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 );
};
}
diff --git a/lib/class.js b/lib/class.js
index 9410dfb..d7b3884 100644
--- a/lib/class.js
+++ b/lib/class.js
@@ -19,16 +19,31 @@
* along with this program. If not, see .
*/
+/**
+ * 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 );
};
diff --git a/lib/interface.js b/lib/interface.js
index e7a7dff..a8225c0 100644
--- a/lib/interface.js
+++ b/lib/interface.js
@@ -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;
}
diff --git a/lib/util.js b/lib/util.js
index 5627118..de59fb5 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -52,6 +52,9 @@ var can_define_prop = ( function()
} )();
+exports.Global = require( './util/Global' );
+
+
/**
* Freezes an object if freezing is supported
*
diff --git a/lib/util/Global.js b/lib/util/Global.js
new file mode 100644
index 0000000..597d47e
--- /dev/null
+++ b/lib/util/Global.js
@@ -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 .
+ */
+
+// 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;
+
diff --git a/lib/util/Symbol.js b/lib/util/Symbol.js
new file mode 100644
index 0000000..2b6aa6d
--- /dev/null
+++ b/lib/util/Symbol.js
@@ -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 .
+ *
+ * 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;
+
diff --git a/lib/util/symbol/FallbackSymbol.js b/lib/util/symbol/FallbackSymbol.js
new file mode 100644
index 0000000..a74236a
--- /dev/null
+++ b/lib/util/symbol/FallbackSymbol.js
@@ -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 .
+ *
+ * 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;
+
diff --git a/lib/warn.js b/lib/warn.js
index ddc6bbb..31185f2 100644
--- a/lib/warn.js
+++ b/lib/warn.js
@@ -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 .
*/
-/**
- * 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;
-
diff --git a/lib/warn/DismissiveHandler.js b/lib/warn/DismissiveHandler.js
new file mode 100644
index 0000000..c54a64d
--- /dev/null
+++ b/lib/warn/DismissiveHandler.js
@@ -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 .
+ */
+
+
+/**
+ * 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;
+
diff --git a/lib/warn/LogHandler.js b/lib/warn/LogHandler.js
new file mode 100644
index 0000000..9934b50
--- /dev/null
+++ b/lib/warn/LogHandler.js
@@ -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 .
+ */
+
+
+/**
+ * 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;
+
diff --git a/lib/warn/ThrowHandler.js b/lib/warn/ThrowHandler.js
new file mode 100644
index 0000000..9ac8c79
--- /dev/null
+++ b/lib/warn/ThrowHandler.js
@@ -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 .
+ */
+
+
+/**
+ * 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;
+
diff --git a/lib/warn/Warning.js b/lib/warn/Warning.js
new file mode 100644
index 0000000..521a6f0
--- /dev/null
+++ b/lib/warn/Warning.js
@@ -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 .
+ */
+
+
+/**
+ * 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;
+
diff --git a/test/ClassBuilder/ConstTest.js b/test/ClassBuilder/ConstTest.js
index 5bc5b95..b9881bf 100644
--- a/test/ClassBuilder/ConstTest.js
+++ b/test/ClassBuilder/ConstTest.js
@@ -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()
)
},
diff --git a/test/ClassBuilder/FinalTest.js b/test/ClassBuilder/FinalTest.js
index d171c7c..c2e429e 100644
--- a/test/ClassBuilder/FinalTest.js
+++ b/test/ClassBuilder/FinalTest.js
@@ -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()
diff --git a/test/ClassBuilder/MemberRestrictionTest.js b/test/ClassBuilder/MemberRestrictionTest.js
index eefe9a9..fa5b798 100644
--- a/test/ClassBuilder/MemberRestrictionTest.js
+++ b/test/ClassBuilder/MemberRestrictionTest.js
@@ -291,6 +291,7 @@ require( 'common' ).testCase(
build = this.require( 'MemberBuilder' )();
var sut = this.Sut(
+ this.require( 'warn' ).DismissiveHandler(),
build,
this.require( 'VisibilityObjectFactoryFactory' )
.fromEnvironment()
diff --git a/test/ClassBuilder/StaticTest.js b/test/ClassBuilder/StaticTest.js
index f2f6a5f..063dabb 100644
--- a/test/ClassBuilder/StaticTest.js
+++ b/test/ClassBuilder/StaticTest.js
@@ -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 ),
diff --git a/test/ClassBuilder/VisibilityTest.js b/test/ClassBuilder/VisibilityTest.js
index d84706f..b98102e 100644
--- a/test/ClassBuilder/VisibilityTest.js
+++ b/test/ClassBuilder/VisibilityTest.js
@@ -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 ),
diff --git a/test/Util/GlobalTest.js b/test/Util/GlobalTest.js
new file mode 100644
index 0000000..1b350ef
--- /dev/null
+++ b/test/Util/GlobalTest.js
@@ -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 .
+ */
+
+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() {} )
+ );
+ },
+} );
+
diff --git a/test/Util/IndexTest.js b/test/Util/IndexTest.js
new file mode 100644
index 0000000..d719a56
--- /dev/null
+++ b/test/Util/IndexTest.js
@@ -0,0 +1,41 @@
+/**
+ * Tests utility module entry point
+ *
+ * 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 .
+ *
+ * N.B. Despite this saying that it tests the index (i.e. entry point), this
+ * is not yet the case; it will be in the future, though.
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'util' );
+ },
+
+
+ 'Exposes Global prototype': function()
+ {
+ this.assertStrictEqual(
+ this.Sut.Global,
+ this.require( 'util/Global' )
+ );
+ },
+} );
+
diff --git a/test/Util/SymbolTest.js b/test/Util/SymbolTest.js
new file mode 100644
index 0000000..3748240
--- /dev/null
+++ b/test/Util/SymbolTest.js
@@ -0,0 +1,43 @@
+/**
+ * Tests symbol subset
+ *
+ * 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 .
+ *
+ * N.B. Despite this saying that it tests the index (i.e. entry point), this
+ * is not yet the case; it will be in the future, though.
+ */
+
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'util/Symbol' );
+ },
+
+
+ /**
+ * We don't care about the details of this; just make sure that we fail
+ * in an environment that seems to confuse us.
+ */
+ 'Exports a function': function()
+ {
+ this.assertOk( typeof this.Sut === 'function' );
+ },
+} );
+
diff --git a/test/Util/symbol/FallbackSymbolTest.js b/test/Util/symbol/FallbackSymbolTest.js
new file mode 100644
index 0000000..21fb236
--- /dev/null
+++ b/test/Util/symbol/FallbackSymbolTest.js
@@ -0,0 +1,76 @@
+/**
+ * Tests pre-ES6 fallback symbol subset
+ *
+ * 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 .
+ */
+
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'util/symbol/FallbackSymbol' );
+ },
+
+
+ /**
+ * Symbols are used to create an object fields that is accessible only
+ * to the holder of a reference to the symbol used to create that field.
+ * Since this fallback is intended to be used in environments that do
+ * not support symbols, the alternative is to return a random string
+ * that is highly unlikely to exist in practice. However, we must also
+ * return an object to allow for instanceof checks. See below test for
+ * more details.
+ */
+ 'Constructor returns an instance of Symbol': function()
+ {
+ var result = this.Sut();
+ this.assertOk( result instanceof this.Sut );
+ },
+
+
+ /**
+ * The generated string should be unique for each call, making it
+ * unlikely that its value can be guessed. Of course, this relies on the
+ * assumption that the runtime's PRNG is reliable and that it has not
+ * been maliciously rewritten.
+ *
+ * Note that we don't test the various implementation details, as that
+ * is intended to be opaque (see SUT source for details).
+ */
+ 'Generated string varies with each call': function()
+ {
+ var gen = {},
+ i = 32;
+
+ while ( i-- )
+ {
+ var result = this.Sut();
+ if ( gen[ result ] )
+ {
+ this.fail( result, '' );
+ }
+
+ gen[ result ] = true;
+ }
+
+ // this prevents the test from being marked as incomplete
+ this.assertOk( 'passed' );
+ },
+} );
+
diff --git a/test/WarnHandlersTest.js b/test/WarnHandlersTest.js
deleted file mode 100644
index c2753c0..0000000
--- a/test/WarnHandlersTest.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/**
- * Tests core warning handlers
- *
- * 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 .
- */
-
-try { void console } catch ( e ) { console = undefined; }
-
-require( 'common' ).testCase(
-{
- caseSetUp: function()
- {
- // XXX: this has global state
- this.Sut = this.require( 'warn' );
- },
-
-
- setUp: function()
- {
- this.stubwarn = this.Sut.Warning( Error( 'gninraw' ) );
- },
-
-
- /**
- * The log warning handler should log warnings to the console
- */
- '`log\' warning handler logs messages to console': function()
- {
- var _self = this,
- logged = false;
-
- // mock console
- this.Sut.setConsole( {
- warn: function( message )
- {
- // should prefix with `Warning: '
- _self.assertEqual(
- ( 'Warning: ' + _self.stubwarn.message ),
- message
- );
-
- logged = true;
- },
- } );
-
- // call handler with the warning
- this.Sut.handlers.log( this.stubwarn );
-
- this.assertOk( logged, true,
- "Message should be logged to console"
- );
-
- // restore console (TODO: will not be necessary once global state is
- // removed)
- this.Sut.setConsole( console );
- },
-
-
- /**
- * Some environments may not have a console reference, or they may not
- * have console.warn. In this case, we just want to make sure we don't
- * throw an error when attempting to invoke undefined, or access a
- * property of undefined.
- */
- '`log\' warning handler handles missing console': function()
- {
- var Sut = this.Sut;
-
- // destroy it
- Sut.setConsole( undefined );
-
- // attempt to log
- var _self = this;
- this.assertDoesNotThrow( function()
- {
- Sut.handlers.log( _self.warnstub );
- } );
-
- // restore console
- Sut.setConsole( console );
- },
-
-
- /**
- * Furthermore, an environment may implement console.log(), but not
- * console.warn(). By default, we use warn(), so let's ensure we can
- * fall back to log() if warn() is unavailable.
- */
- '`log\' warning handler falls back to log if warn is missing':
- function()
- {
- var given = '';
-
- this.Sut.setConsole( {
- log: function( message )
- {
- given = message;
- }
- } );
-
- // attempt to log
- this.Sut.handlers.log( this.stubwarn );
-
- this.assertEqual( ( 'Warning: ' + this.stubwarn.message ), given,
- "Should fall back to log() and log proper message"
- );
-
- // restore console
- this.Sut.setConsole( console );
- },
-
-
- /**
- * The throwError warning handler should throw the wrapped error as an
- * exception
- */
- '`throwError\' warning handler throws wrapped error': function()
- {
- try
- {
- this.Sut.handlers.throwError( this.stubwarn );
- }
- catch ( e )
- {
- this.assertStrictEqual( e, this.stubwarn.getError(),
- "Wrapped exception should be thrown"
- );
-
- return;
- }
-
- this.assertFail( "Wrapped exception should be thrown" );
- },
-
-
- /**
- * The 'dismiss' error handler is a pretty basic concept: simply do
- * nothing. We don't want to log, we don't want to throw anything, we
- * just want to pretend nothing ever happened and move on our merry way.
- * This is intended for use in production environments where such
- * warnings are expected to already have been worked out and would only
- * confuse/concern the user.
- */
- '`dismiss\' warning handler does nothing': function()
- {
- var Sut = this.Sut;
-
- // destroy the console to ensure nothing is logged
- Sut.setConsole( undefined );
-
- // no errors should occur because it should not do anything.
- var _self = this;
- this.assertDoesNotThrow( function()
- {
- Sut.handlers.dismiss( _self.warnstub );
- } );
-
- // restore console
- Sut.setConsole( console );
- },
-} );
diff --git a/test/WarnTest.js b/test/WarnTest.js
deleted file mode 100644
index 7206155..0000000
--- a/test/WarnTest.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Tests warning system implementation
- *
- * 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 .
- */
-
-try { void console } catch ( e ) { console = undefined; }
-
-require( 'common' ).testCase(
-{
- setUp: function()
- {
- // XXX: this uses global state; remove that requirement.
- this.Sut = this.require( 'warn' );
- },
-
-
- /**
- * The default warning handler should be the 'log' handler; this is a
- * friendly compromise that will allow the developer to be warned of
- * potential issues without affecting program execution.
- */
- 'Default warning handler is `log\'': function()
- {
- var called = false;
-
- // stub it
- this.Sut.setConsole( {
- warn: function()
- {
- called = true;
- },
- } );
-
- this.Sut.handle( this.Sut.Warning( Error( 'foo' ) ) );
- this.assertOk( called );
-
- // restore console (TODO: this will not be necessary once reliance
- // on global state is removed)
- this.Sut.setConsole( console );
- },
-
-
- /**
- * The warning handler can be altered at runtime; ensure we can set it
- * and call it appropriately. We do not need to use one of the
- * pre-defined handlers.
- */
- 'Can set and call arbitrary warning handler': function()
- {
- var given,
- warning = this.Sut.Warning( Error( 'foo' ) );
-
- // set a stub warning handler
- this.Sut.setHandler( function( warn )
- {
- given = warn;
- } );
-
- // trigger the handler
- this.Sut.handle( warning );
- this.assertDeepEqual( given, warning );
- },
-} );
diff --git a/test/common.js b/test/common.js
index 64119dd..845b73b 100644
--- a/test/common.js
+++ b/test/common.js
@@ -47,6 +47,6 @@ exports.require = function( module )
*
* @return {udnefined}
*/
-exports.testCase = require( __dirname + '/inc-testcase.js' );
+exports.testCase = require( './inc-testcase.js' );
diff --git a/test/warn/DismissiveHandlerTest.js b/test/warn/DismissiveHandlerTest.js
new file mode 100644
index 0000000..aef8653
--- /dev/null
+++ b/test/warn/DismissiveHandlerTest.js
@@ -0,0 +1,57 @@
+/**
+ * Tests 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 .
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'warn/DismissiveHandler' );
+ this.Warning = this.require( 'warn/Warning' );
+ },
+
+
+ 'Can be instantiated without `new` keyword': function()
+ {
+ this.assertOk( this.Sut() instanceof this.Sut );
+ },
+
+
+ /**
+ * Simply do nothing. We don't want to log, we don't want to throw
+ * anything, we just want to pretend nothing ever happened and move on
+ * our merry way. This is intended for use in production environments
+ * where such warnings are expected to already have been worked out and
+ * would only confuse/concern the user.
+ *
+ * Now, testing whether it does anything or not is difficult, since it
+ * could do, well, anything; that said, we are not passing it anything
+ * via the ctor, so assuming that it does not rely on or manipulate
+ * global state, we need only ensure that no exceptions are thrown.
+ */
+ 'Does nothing': function()
+ {
+ var _self = this;
+ this.assertDoesNotThrow( function()
+ {
+ _self.Sut().handle( _self.Warning( Error( "Ignore me!" ) ) );
+ } );
+ },
+} );
diff --git a/test/warn/LogHandlerTest.js b/test/warn/LogHandlerTest.js
new file mode 100644
index 0000000..46c84c1
--- /dev/null
+++ b/test/warn/LogHandlerTest.js
@@ -0,0 +1,126 @@
+/**
+ * Tests 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 .
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'warn/LogHandler' );
+ this.Warning = this.require( 'warn/Warning' );
+ },
+
+
+ setUp: function()
+ {
+ this.stubwarn = this.Warning( Error( 'gninraw' ) );
+ },
+
+
+ 'Can be instantiated without `new` keyword': function()
+ {
+ this.assertOk( this.Sut() instanceof this.Sut );
+ },
+
+
+ /**
+ * Warnings should be logged to the provided console. By default, the
+ * `warn` method is used (see below tests for fallbacks).
+ */
+ 'Logs messages to console': function()
+ {
+ var _self = this,
+ logged = false;
+
+ // mock console
+ this.Sut( {
+ warn: function( message )
+ {
+ // should prefix with `Warning: '
+ _self.assertEqual(
+ ( 'Warning: ' + _self.stubwarn.message ),
+ message
+ );
+
+ logged = true;
+ },
+ } ).handle( this.stubwarn );
+
+ this.assertOk( logged, true,
+ "Message should be logged to console"
+ );
+ },
+
+
+ /**
+ * Some environments may not have a console reference, or they may not
+ * have console.warn. In this case, we just want to make sure we don't
+ * throw an error when attempting to invoke undefined, or access a
+ * property of undefined.
+ */
+ 'Ignores missing console': function()
+ {
+ var _self = this;
+ this.assertDoesNotThrow( function()
+ {
+ _self.Sut( undefined ).handle( _self.warnstub );
+ } );
+ },
+
+
+ /**
+ * Furthermore, an environment may implement `console.log`, but not
+ * `console.warn`. By default, we use `warn`, so let's ensure we can
+ * fall back to `log` if `warn` is unavailable.
+ */
+ 'Falls back to log if warn is missing': function()
+ {
+ var given = '';
+
+ this.Sut( {
+ log: function( message )
+ {
+ given = message;
+ }
+ } ).handle( this.stubwarn );
+
+ this.assertEqual( ( 'Warning: ' + this.stubwarn.message ), given,
+ "Should fall back to log() and log proper message"
+ );
+ },
+
+
+ /**
+ * If both `console.warn` and `console.log` are defined (which is very
+ * likely to be the case), the former should take precedence.
+ */
+ '`warn` takes precedence over `log`': function()
+ {
+ var log = warn = false;
+
+ this.Sut( {
+ warn: function() { warn = true },
+ log: function() { log = true },
+ } ).handle( this.stubwarn );
+
+ this.assertOk( warn );
+ this.assertOk( !log );
+ },
+} );
diff --git a/test/warn/ThrowHandlerTest.js b/test/warn/ThrowHandlerTest.js
new file mode 100644
index 0000000..d0ce1d5
--- /dev/null
+++ b/test/warn/ThrowHandlerTest.js
@@ -0,0 +1,60 @@
+/**
+ * Tests 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 .
+ */
+
+require( 'common' ).testCase(
+{
+ caseSetUp: function()
+ {
+ this.Sut = this.require( 'warn/ThrowHandler' );
+ this.Warning = this.require( 'warn/Warning' );
+ },
+
+
+ 'Can be instantiated without `new` keyword': function()
+ {
+ this.assertOk( this.Sut() instanceof this.Sut );
+ },
+
+
+ /**
+ * The wrapped error should be thrown as an exception; this effectively
+ * undoes the warning wrapper.
+ */
+ '`throwError\' warning handler throws wrapped error': function()
+ {
+ var warn = this.Warning( Error( 'gninraw' ) );
+
+ try
+ {
+ this.Sut().handle( warn );
+ }
+ catch ( e )
+ {
+ this.assertStrictEqual( e, warn.getError(),
+ "Wrapped exception should be thrown"
+ );
+
+ return;
+ }
+
+ this.assertFail( "Wrapped exception should be thrown" );
+ },
+} );
diff --git a/test/WarningTest.js b/test/warn/WarningTest.js
similarity index 100%
rename from test/WarningTest.js
rename to test/warn/WarningTest.js
diff --git a/tools/combine b/tools/combine
index 9b804d5..378c393 100755
--- a/tools/combine
+++ b/tools/combine
@@ -33,8 +33,13 @@ RMTRAIL="$PATH_TOOLS/rmtrail"
cat_modules=$(
cd "$PATH_TOOLS/../" &&
grep -rIo ' require(.*)' lib/ \
- | sed "s/^lib\///;s/\.js://;s/require( *'\.\/\(.*\)'.*/\1/" \
- | node tools/combine-order.js
+ | sed " \
+ s/^lib\/\(\(.\+\/\)\?[^/]\+\)\.js: /\1 \2/;
+ s/require( *'\(.*\)'.*/\1/;
+ s/[^/\]\+\/\.\.//g;
+ s/\( \|\/\)\.\//\1/g;
+ " \
+ | node tools/combine-order.js \
) || {
echo "Failed to get module list" >&2
exit 3
@@ -122,6 +127,7 @@ for module in $cat_modules; do
echo "( function( module, __dirname )"
echo "{"
echo " var exports = module.exports = {};"
+ echo " __cwd = '$( dirname "$module" )';"
# add the module, removing trailing commas
cat $filename | $RMTRAIL
@@ -166,6 +172,7 @@ if [ "$INC_TEST" ]; then
echo "( function( module, __dirname )"
echo "{"
echo " var exports = module.exports = {};"
+ echo " __cwd = '.';"
# write out current test to make debugging easier in browsers with very
# little debugging support
diff --git a/tools/combine-test.tpl b/tools/combine-test.tpl
index 574bb79..7f44107 100644
--- a/tools/combine-test.tpl
+++ b/tools/combine-test.tpl
@@ -26,7 +26,7 @@ module.common = module['test/common'] = { exports: {
testCase: function()
{
- return require( 'test/inc-testcase' ).apply( this, arguments );
+ return require( '/test/inc-testcase' ).apply( this, arguments );
}
} };
diff --git a/tools/combine.tpl b/tools/combine.tpl
index e97e7e4..16a83f2 100644
--- a/tools/combine.tpl
+++ b/tools/combine.tpl
@@ -15,7 +15,7 @@
*/
var easejs = {};
-( function( ns_exports )
+( function( ns_exports, __cwd )
{
/**
* CommonJS module exports
@@ -42,15 +42,26 @@ var easejs = {};
*/
var require = function( module_id )
{
- // remove the './' directory prefix (every module is currently included
- // via a relative path), stupidly remove ../'s and remove .js extensions
- var id_clean = module_id.replace( /^\.?\/|[^/]*?\/\.\.\/|\.js$/, '' );
+ // anything that is not an absolute require path will be prefixed
+ // with __cwd, which is set by the combined module; this allows
+ // including relative paths (but note that this also means that
+ // modules that perform ad-hoc conditional requires after another
+ // module has been processed may not work properly; we don't do
+ // this, though)
+ var id_norm = ( module_id.substr( 0, 1 ) === '/' )
+ ? module_id
+ : __cwd + '/' + module_id;
+
+ // strip `../`, poorly strip `./` (for example, it would also strip
+ // `foo./`, but we know that this won't ever be the case with our
+ // files), and strip leading `/`
+ var id_clean = id_norm.replace( /([^\/]+\/\.\.\/|\.\/|^\/)/g, '' );
// attempt to retrieve the module
var mod = module[ id_clean ];
if ( mod === undefined )
{
- throw "[ease.js] Undefined module: " + module_id;
+ throw "[ease.js] Undefined module: " + id_clean;
}
return mod.exports;
@@ -65,5 +76,5 @@ var easejs = {};
ns_exports.Interface = module['interface'].exports;
ns_exports.Trait = module['Trait'].exports;
ns_exports.version = module['version'].exports;
-} )( easejs );
+} )( easejs, '.' );