UNPKG

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