Version 1
[yaffs-website] / node_modules / gulp-util / lib / PluginError.js
1 var util = require('util');
2 var arrayDiffer = require('array-differ');
3 var arrayUniq = require('array-uniq');
4 var chalk = require('chalk');
5 var objectAssign = require('object-assign');
6
7 var nonEnumberableProperties = ['name', 'message', 'stack'];
8 var propertiesNotToDisplay = nonEnumberableProperties.concat(['plugin', 'showStack', 'showProperties', '__safety', '_stack']);
9
10 // wow what a clusterfuck
11 var parseOptions = function(plugin, message, opt) {
12   opt = opt || {};
13   if (typeof plugin === 'object') {
14     opt = plugin;
15   } else {
16     if (message instanceof Error) {
17       opt.error = message;
18     } else if (typeof message === 'object') {
19       opt = message;
20     } else {
21       opt.message = message;
22     }
23     opt.plugin = plugin;
24   }
25
26   return objectAssign({
27     showStack: false,
28     showProperties: true
29   }, opt);
30 };
31
32 function PluginError(plugin, message, opt) {
33   if (!(this instanceof PluginError)) throw new Error('Call PluginError using new');
34
35   Error.call(this);
36
37   var options = parseOptions(plugin, message, opt);
38   var self = this;
39
40   // if options has an error, grab details from it
41   if (options.error) {
42     // These properties are not enumerable, so we have to add them explicitly.
43     arrayUniq(Object.keys(options.error).concat(nonEnumberableProperties))
44       .forEach(function(prop) {
45         self[prop] = options.error[prop];
46       });
47   }
48
49   var properties = ['name', 'message', 'fileName', 'lineNumber', 'stack', 'showStack', 'showProperties', 'plugin'];
50
51   // options object can override
52   properties.forEach(function(prop) {
53     if (prop in options) this[prop] = options[prop];
54   }, this);
55
56   // defaults
57   if (!this.name) this.name = 'Error';
58
59   if (!this.stack) {
60     // Error.captureStackTrace appends a stack property which relies on the toString method of the object it is applied to.
61     // Since we are using our own toString method which controls when to display the stack trace if we don't go through this
62     // safety object, then we'll get stack overflow problems.
63     var safety = {
64       toString: function() {
65         return this._messageWithDetails() + '\nStack:';
66       }.bind(this)
67     };
68     Error.captureStackTrace(safety, arguments.callee || this.constructor);
69     this.__safety = safety;
70   }
71
72   if (!this.plugin) throw new Error('Missing plugin name');
73   if (!this.message) throw new Error('Missing error message');
74 }
75
76 util.inherits(PluginError, Error);
77
78 PluginError.prototype._messageWithDetails = function() {
79   var messageWithDetails = 'Message:\n    ' + this.message;
80   var details = this._messageDetails();
81
82   if (details !== '') {
83     messageWithDetails += '\n' + details;
84   }
85
86   return messageWithDetails;
87 };
88
89 PluginError.prototype._messageDetails = function() {
90   if (!this.showProperties) {
91     return '';
92   }
93
94   var properties = arrayDiffer(Object.keys(this), propertiesNotToDisplay);
95
96   if (properties.length === 0) {
97     return '';
98   }
99
100   var self = this;
101   properties = properties.map(function stringifyProperty(prop) {
102     return '    ' + prop + ': ' + self[prop];
103   });
104
105   return 'Details:\n' + properties.join('\n');
106 };
107
108 PluginError.prototype.toString = function () {
109   var sig = chalk.red(this.name) + ' in plugin \'' + chalk.cyan(this.plugin) + '\'';
110   var detailsWithStack = function(stack) {
111     return this._messageWithDetails() + '\nStack:\n' + stack;
112   }.bind(this);
113
114   var msg;
115   if (this.showStack) {
116     if (this.__safety) { // There is no wrapped error, use the stack captured in the PluginError ctor
117       msg = this.__safety.stack;
118     } else if (this._stack) {
119       msg = detailsWithStack(this._stack);
120     } else { // Stack from wrapped error
121       msg = detailsWithStack(this.stack);
122     }
123   } else {
124     msg = this._messageWithDetails();
125   }
126
127   return sig + '\n' + msg;
128 };
129
130 module.exports = PluginError;