// Licensed Materials - Property of IBM
//
// IBM Watson Analytics
//
// (C) Copyright IBM Corp. 2015
//
// US Government Users Restricted Rights - Use, duplication or
// disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
module.exports = ( function( Error, decl )
{
"use strict";
/**
* Interprets a line of a stack trace and tries to extract information from it.
* @param {string} _line The line to interpret
* @memberof module:barejs.Exception~
* @private
*/
/*istanbul ignore next: NodeJS has the Error.captureStackTrace method natively so this function will not be hit*/
function splitStackLine( _line )
{
/*
Expected input looks like:
myFunction@http://my.domain.com:8080/my/site/script.js:120:8 (Firefox, Safari)
@http://my.domain.com:8080/my/site/script.js:120:8 (Firefox)
http://my.domain.com:8080/my/site/script.js:120:8 (Safari)
*/
if ( !_line )
return null;
var line = String( _line );
var result = new Array( 4 );
// Try to find a name (can be empty string)
var match = line.match( /^([^@]*)@/ );
result[0] = null;
if ( match )
{
result[0] = match[1] || null;
// Chop off the <functionName>@ part
line = line.substr( match[0].length );
}
// Try to find line and column number
match = line.match( /(:[0-9]+)?:([0-9]+)$/ );
if ( match )
{
if ( match[1] )
{
result[2] = parseInt( match[1].substr( 1 ), 10 );
result[3] = parseInt( match[2], 10 ) - 1;
}
else
{
result[2] = parseInt( match[2], 10 );
result[3] = null;
}
// Chop off the :LineNumber:ColumnNumber part
line = line.substr( 0, line.length - match[0].length );
}
// The result should be the filename
result[1] = line;
return result;
}
/*istanbul ignore next: NodeJS has the Error.captureStackTrace method natively so this function will not be hit*/
function processStack( _obj, _ctor, _err )
{
var fileName, lineNumber, columnNumber; // Additional metadata
var line, match; // used to parse the stack entries
var stack = _err.stack.split( "\n" ); // Split version of _err.stack
// Initialize fileName etc. to null
fileName = lineNumber = columnNumber = null;
if ( _ctor.name )
{
for ( line = 0; line < stack.length; ++line )
{
if ( stack[line].startsWith( _ctor.name + "@" ) )
break;
}
// Never remove the last entry
if ( line >= ( stack.length - 1 ) )
line = -1;
}
else
{
// We know for a fact the lowest entry can be ignored, unless it's the only entry
line = ( stack.length === 1 ) ? -1 : 0;
}
// If we get here and line >= 0, we can remove those entries
if ( line >= 0 )
{
stack = stack.slice( line + 1 );
match = splitStackLine( stack[0] );
if ( match )
{
fileName = match[1];
lineNumber = match[2];
columnNumber = match[3];
}
}
decl.defineProperty( _obj, "stack", { configurable: true, value: stack.join( "\n" ) } );
if ( fileName )
{
decl.defineProperties( _obj,
{
fileName: { configurable: true, value: fileName },
lineNumber: { configurable: true, value: lineNumber },
columnNumber: { configurable: true, value: columnNumber }
} );
}
}
/*istanbul ignore next: NodeJS has the Error.captureStackTrace method natively so this function will not be hit*/
function captureStackTrace( _obj, _ctor, _err )
{
if ( "stack" in _err )
processStack( _obj, _ctor, _err );
// if ( "opera#sourceLoc" in _err ) // Opera support?
}
/**
* The Exception constructor will set up the exception with a name and stack property. You can pass the "creator function"
* as second argument, this is the topmost function that will be ignored from the stack. It defaults to the constructed
* object's constructor, which ensures the "Exception" and parent constructors never show up in the stack.
* @class module:barejs.Exception
* @param {string} _message The message that describes the exception.
* @param {function} [_creatorFn] Optional: function to exclude from the call stack.
* Defaults to the this.constructor function.
*
* @classdesc Exception creates a normalized base class for creating custom Error (Exception) classes to throw.
* It handles determining the stack trace for the created exception in a cross browser way.
* This class relies on the constructor property being set correctly, otherwise sub-class constructors
* may show up in the stack trace. Using {@link module:barejs.decl#declareClass decl.declareClass}
* to define base classes ensures the constructor property is set correctly.
*
* Sub classes should also set the name property on the prototype to the name of the exception, for example:
*
* function MyCustomError( _message, _myAdditionalData )
* {
* Exception.call( this, _message );
*
* this.myAdditionalData = _myAdditionalData;
* }
*
* decl.declareClass( MyCustomError, Exception,
* {
* // Setting the name ensures our custom error looks and behaves as expected
* // Avoid using MyCustomError.name (named function name) as the name may get
* // mangled by a minifier/uglifier.
* name: "MyCustomError",
*
* myAdditionalData: null
* } );
*
*/
function Exception( _message/*, _creatorFn*/ )
{
if ( !this || !( this instanceof Exception ) )
throw new TypeError( "Invalid context for Exception. Did you forget the new keyword?" );
var fn = arguments[1] || this.constructor;
if ( typeof fn !== "function" )
throw new TypeError( "_creatorFn must be omitted, null or a function." );
/*istanbul ignore else: NodeJS has the Error.captureStackTrace method natively*/
if ( Error.captureStackTrace )
Error.captureStackTrace( this, fn );
else
captureStackTrace( this, fn, new Error() );
decl.defineProperty( this, "message", { configurable: true, writable: true, value: _message } );
}
return decl.declareClass( Exception, Error,
/** @lends module:barejs.Exception# */
{
/**
* The name of the Exception type. Base classes are supposed to set the correct name on the prototype too.
* It is recommended not to use the constructor function's name for this, as that might get obfuscated by
* a minifier, or the name property may not be supported at all.
* @member {string}
*/
name: { configurable: true, writable: true, value: "Exception" }
} );
// End of module
}( Error, require( "./decl" ) ) );