UNPKG

4.41 kBJavaScriptView Raw
1var util = require('util');
2var colors = require('ansi-colors');
3
4var nonEnum = ['message', 'name', 'stack'];
5var ignored = new Set(
6 nonEnum.concat([
7 '__safety',
8 '_stack',
9 'plugin',
10 'showProperties',
11 'showStack',
12 'domain',
13 'domainEmitter',
14 'domainThrown',
15 ])
16);
17var props = [
18 'fileName',
19 'lineNumber',
20 'message',
21 'name',
22 'plugin',
23 'showProperties',
24 'showStack',
25 'stack',
26];
27
28function PluginError(plugin, message, options) {
29 if (!(this instanceof PluginError)) {
30 return new PluginError(plugin, message, options);
31 }
32
33 Error.call(this);
34 var opts = setDefaults(plugin, message, options);
35 var self = this;
36
37 // If opts has an error, get details from it
38 if (typeof opts.error === 'object') {
39 var keys = new Set(Object.keys(opts.error).concat(nonEnum));
40
41 // These properties are not enumerable, so we have to add them explicitly.
42 keys.forEach(function (prop) {
43 self[prop] = opts.error[prop];
44 });
45 }
46
47 // Opts object can override
48 props.forEach(function (prop) {
49 if (prop in opts) {
50 this[prop] = opts[prop];
51 }
52 }, this);
53
54 // Defaults
55 if (!this.stack) {
56 /**
57 * `Error.captureStackTrace` appends a stack property which
58 * relies on the toString method of the object it is applied to.
59 *
60 * Since we are using our own toString method which controls when
61 * to display the stack trace, if we don't go through this safety
62 * object we'll get stack overflow problems.
63 */
64
65 var safety = {};
66 safety.toString = function () {
67 return this._messageWithDetails() + '\nStack:';
68 }.bind(this);
69
70 Error.captureStackTrace(safety, arguments.callee || this.constructor);
71 this.__safety = safety;
72 }
73 if (!this.plugin) {
74 throw new Error('Missing plugin name');
75 }
76 if (!this.message) {
77 throw new Error('Missing error message');
78 }
79}
80
81util.inherits(PluginError, Error);
82
83/**
84 * Output a formatted message with details
85 */
86
87PluginError.prototype._messageWithDetails = function () {
88 var msg = 'Message:\n ' + this.message;
89 var details = this._messageDetails();
90 if (details !== '') {
91 msg += '\n' + details;
92 }
93 return msg;
94};
95
96/**
97 * Output actual message details
98 */
99
100PluginError.prototype._messageDetails = function () {
101 if (!this.showProperties) {
102 return '';
103 }
104
105 var props = Object.keys(this).filter(function (key) {
106 return !ignored.has(key);
107 });
108 var len = props.length;
109
110 if (len === 0) {
111 return '';
112 }
113
114 var res = '';
115 var i = 0;
116 while (len--) {
117 var prop = props[i++];
118 res += ' ';
119 res += prop + ': ' + this[prop];
120 res += '\n';
121 }
122 return 'Details:\n' + res;
123};
124
125/**
126 * Override the `toString` method
127 */
128
129PluginError.prototype.toString = function () {
130 var detailsWithStack = function (stack) {
131 return this._messageWithDetails() + '\nStack:\n' + stack;
132 }.bind(this);
133
134 var msg = '';
135 if (this.showStack) {
136 // If there is no wrapped error, use the stack captured in the PluginError ctor
137 if (this.__safety) {
138 msg = this.__safety.stack;
139 } else if (this._stack) {
140 msg = detailsWithStack(this._stack);
141 } else {
142 // Stack from wrapped error
143 msg = detailsWithStack(this.stack);
144 }
145 return message(msg, this);
146 }
147
148 msg = this._messageWithDetails();
149 return message(msg, this);
150};
151
152// Format the output message
153function message(msg, thisArg) {
154 var sig = colors.red(thisArg.name);
155 sig += ' in plugin ';
156 sig += '"' + colors.cyan(thisArg.plugin) + '"';
157 sig += '\n';
158 sig += msg;
159 return sig;
160}
161
162/**
163 * Set default options based on arguments.
164 */
165
166function setDefaults(plugin, message, opts) {
167 if (typeof plugin === 'object') {
168 return defaults(plugin);
169 }
170 if (message instanceof Error) {
171 opts = Object.assign({}, opts, { error: message });
172 } else if (typeof message === 'object') {
173 opts = Object.assign({}, message);
174 } else {
175 opts = Object.assign({}, opts, { message: message });
176 }
177 opts.plugin = plugin;
178 return defaults(opts);
179}
180
181/**
182 * Extend default options with:
183 *
184 * - `showStack`: default=false
185 * - `showProperties`: default=true
186 *
187 * @param {Object} `opts` Options to extend
188 * @return {Object}
189 */
190
191function defaults(opts) {
192 return Object.assign(
193 {
194 showStack: false,
195 showProperties: true,
196 },
197 opts
198 );
199}
200
201/**
202 * Expose `PluginError`
203 */
204
205module.exports = PluginError;