Source: Exception.js

// 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" ) ) );