|  | var util = require('util'); | 
|  | var colors = require('ansi-colors'); | 
|  | var extend = require('extend-shallow'); | 
|  | var differ = require('arr-diff'); | 
|  | var union = require('arr-union'); | 
|  |  | 
|  | var nonEnum = ['message', 'name', 'stack']; | 
|  | var ignored = union(nonEnum, ['__safety', '_stack', 'plugin', 'showProperties', 'showStack']); | 
|  | var props = ['fileName', 'lineNumber', 'message', 'name', 'plugin', 'showProperties', 'showStack', 'stack']; | 
|  |  | 
|  | function PluginError(plugin, message, options) { | 
|  | if (!(this instanceof PluginError)) { | 
|  | throw new Error('Call PluginError using new'); | 
|  | } | 
|  |  | 
|  | Error.call(this); | 
|  | var opts = setDefaults(plugin, message, options); | 
|  | var self = this; | 
|  |  | 
|  | // If opts has an error, get details from it | 
|  | if (typeof opts.error === 'object') { | 
|  | var keys = union(Object.keys(opts.error), nonEnum); | 
|  |  | 
|  | // These properties are not enumerable, so we have to add them explicitly. | 
|  | keys.forEach(function(prop) { | 
|  | self[prop] = opts.error[prop]; | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Opts object can override | 
|  | props.forEach(function(prop) { | 
|  | if (prop in opts) { | 
|  | this[prop] = opts[prop]; | 
|  | } | 
|  | }, this); | 
|  |  | 
|  | // Defaults | 
|  | if (!this.name) { | 
|  | this.name = 'Error'; | 
|  | } | 
|  | if (!this.stack) { | 
|  |  | 
|  | /** | 
|  | * `Error.captureStackTrace` appends a stack property which | 
|  | * relies on the toString method of the object it is applied to. | 
|  | * | 
|  | * Since we are using our own toString method which controls when | 
|  | * to display the stack trace, if we don't go through this safety | 
|  | * object we'll get stack overflow problems. | 
|  | */ | 
|  |  | 
|  | var safety = {}; | 
|  | safety.toString = function() { | 
|  | return this._messageWithDetails() + '\nStack:'; | 
|  | }.bind(this); | 
|  |  | 
|  | Error.captureStackTrace(safety, arguments.callee || this.constructor); | 
|  | this.__safety = safety; | 
|  | } | 
|  | if (!this.plugin) { | 
|  | throw new Error('Missing plugin name'); | 
|  | } | 
|  | if (!this.message) { | 
|  | throw new Error('Missing error message'); | 
|  | } | 
|  | } | 
|  |  | 
|  | util.inherits(PluginError, Error); | 
|  |  | 
|  | /** | 
|  | * Output a formatted message with details | 
|  | */ | 
|  |  | 
|  | PluginError.prototype._messageWithDetails = function() { | 
|  | var msg = 'Message:\n    ' + this.message; | 
|  | var details = this._messageDetails(); | 
|  | if (details !== '') { | 
|  | msg += '\n' + details; | 
|  | } | 
|  | return msg; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Output actual message details | 
|  | */ | 
|  |  | 
|  | PluginError.prototype._messageDetails = function() { | 
|  | if (!this.showProperties) { | 
|  | return ''; | 
|  | } | 
|  |  | 
|  | var props = differ(Object.keys(this), ignored); | 
|  | var len = props.length; | 
|  |  | 
|  | if (len === 0) { | 
|  | return ''; | 
|  | } | 
|  |  | 
|  | var res = ''; | 
|  | var i = 0; | 
|  | while (len--) { | 
|  | var prop = props[i++]; | 
|  | res += '    '; | 
|  | res += prop + ': ' + this[prop]; | 
|  | res += '\n'; | 
|  | } | 
|  | return 'Details:\n' + res; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Override the `toString` method | 
|  | */ | 
|  |  | 
|  | PluginError.prototype.toString = function() { | 
|  | var detailsWithStack = function(stack) { | 
|  | return this._messageWithDetails() + '\nStack:\n' + stack; | 
|  | }.bind(this); | 
|  |  | 
|  | var msg = ''; | 
|  | if (this.showStack) { | 
|  | // If there is no wrapped error, use the stack captured in the PluginError ctor | 
|  | if (this.__safety) { | 
|  | msg = this.__safety.stack; | 
|  |  | 
|  | } else if (this._stack) { | 
|  | msg = detailsWithStack(this._stack); | 
|  |  | 
|  | } else { | 
|  | // Stack from wrapped error | 
|  | msg = detailsWithStack(this.stack); | 
|  | } | 
|  | return message(msg, this); | 
|  | } | 
|  |  | 
|  | msg = this._messageWithDetails(); | 
|  | return message(msg, this); | 
|  | }; | 
|  |  | 
|  | // Format the output message | 
|  | function message(msg, thisArg) { | 
|  | var sig = colors.red(thisArg.name); | 
|  | sig += ' in plugin '; | 
|  | sig += '"' + colors.cyan(thisArg.plugin) + '"'; | 
|  | sig += '\n'; | 
|  | sig += msg; | 
|  | return sig; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Set default options based on arguments. | 
|  | */ | 
|  |  | 
|  | function setDefaults(plugin, message, opts) { | 
|  | if (typeof plugin === 'object') { | 
|  | return defaults(plugin); | 
|  | } | 
|  | opts = opts || {}; | 
|  | if (message instanceof Error) { | 
|  | opts.error = message; | 
|  | } else if (typeof message === 'object') { | 
|  | opts = message; | 
|  | } else { | 
|  | opts.message = message; | 
|  | } | 
|  | opts.plugin = plugin; | 
|  | return defaults(opts); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Extend default options with: | 
|  | * | 
|  | *  - `showStack`: default=false | 
|  | *  - `showProperties`: default=true | 
|  | * | 
|  | * @param  {Object} `opts` Options to extend | 
|  | * @return {Object} | 
|  | */ | 
|  |  | 
|  | function defaults(opts) { | 
|  | return extend({ | 
|  | showStack: false, | 
|  | showProperties: true, | 
|  | }, opts); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Expose `PluginError` | 
|  | */ | 
|  |  | 
|  | module.exports = PluginError; |