@@ -65,7 +65,15 @@ const kFormatForStdout = Symbol('kFormatForStdout'); |
65 | 65 | const kGetInspectOptions = Symbol('kGetInspectOptions'); |
66 | 66 | const kColorMode = Symbol('kColorMode'); |
67 | 67 | const kIsConsole = Symbol('kIsConsole'); |
68 | | - |
| 68 | +const kWriteToConsole = Symbol('kWriteToConsole'); |
| 69 | +const kBindProperties = Symbol('kBindProperties'); |
| 70 | +const kBindStreamsEager = Symbol('kBindStreamsEager'); |
| 71 | +const kBindStreamsLazy = Symbol('kBindStreamsLazy'); |
| 72 | +const kUseStdout = Symbol('kUseStdout'); |
| 73 | +const kUseStderr = Symbol('kUseStderr'); |
| 74 | + |
| 75 | +// This constructor is not used to construct the global console. |
| 76 | +// It's exported for backwards compatibility. |
69 | 77 | function Console(options /* or: stdout, stderr, ignoreErrors = true */) { |
70 | 78 | // We have to test new.target here to see if this function is called |
71 | 79 | // with new, because we need to define a custom instanceof to accommodate |
@@ -74,7 +82,6 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) { |
74 | 82 | return new Console(...arguments); |
75 | 83 | } |
76 | 84 |
|
77 | | - this[kIsConsole] = true; |
78 | 85 | if (!options || typeof options.write === 'function') { |
79 | 86 | options = { |
80 | 87 | stdout: options, |
@@ -97,37 +104,9 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) { |
97 | 104 | throw new ERR_CONSOLE_WRITABLE_STREAM('stderr'); |
98 | 105 | } |
99 | 106 |
|
100 | | - const prop = { |
101 | | - writable: true, |
102 | | - enumerable: false, |
103 | | - configurable: true |
104 | | - }; |
105 | | - Object.defineProperty(this, '_stdout', { ...prop, value: stdout }); |
106 | | - Object.defineProperty(this, '_stderr', { ...prop, value: stderr }); |
107 | | - Object.defineProperty(this, '_ignoreErrors', { |
108 | | - ...prop, |
109 | | - value: Boolean(ignoreErrors), |
110 | | - }); |
111 | | - Object.defineProperty(this, '_times', { ...prop, value: new Map() }); |
112 | | - Object.defineProperty(this, '_stdoutErrorHandler', { |
113 | | - ...prop, |
114 | | - value: createWriteErrorHandler(stdout), |
115 | | - }); |
116 | | - Object.defineProperty(this, '_stderrErrorHandler', { |
117 | | - ...prop, |
118 | | - value: createWriteErrorHandler(stderr), |
119 | | - }); |
120 | | - |
121 | 107 | if (typeof colorMode !== 'boolean' && colorMode !== 'auto') |
122 | 108 | throw new ERR_INVALID_ARG_VALUE('colorMode', colorMode); |
123 | 109 |
|
124 | | - // Corresponds to https://console.spec.whatwg.org/#count-map |
125 | | - this[kCounts] = new Map(); |
126 | | - this[kColorMode] = colorMode; |
127 | | - |
128 | | - Object.defineProperty(this, kGroupIndent, { writable: true }); |
129 | | - this[kGroupIndent] = ''; |
130 | | - |
131 | 110 | // Bind the prototype functions to this Console instance |
132 | 111 | var keys = Object.keys(Console.prototype); |
133 | 112 | for (var v = 0; v < keys.length; v++) { |
@@ -137,14 +116,92 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) { |
137 | 116 | // from the prototype chain of the subclass. |
138 | 117 | this[k] = this[k].bind(this); |
139 | 118 | } |
| 119 | + |
| 120 | + this[kBindStreamsEager](stdout, stderr); |
| 121 | + this[kBindProperties](ignoreErrors, colorMode); |
140 | 122 | } |
141 | 123 |
|
| 124 | +const consolePropAttributes = { |
| 125 | + writable: true, |
| 126 | + enumerable: false, |
| 127 | + configurable: true |
| 128 | +}; |
| 129 | + |
| 130 | +// Fixup global.console instanceof global.console.Console |
| 131 | +Object.defineProperty(Console, Symbol.hasInstance, { |
| 132 | + value(instance) { |
| 133 | + return instance[kIsConsole]; |
| 134 | + } |
| 135 | +}); |
| 136 | + |
| 137 | +// Eager version for the Console constructor |
| 138 | +Console.prototype[kBindStreamsEager] = function(stdout, stderr) { |
| 139 | + Object.defineProperties(this, { |
| 140 | + '_stdout': { ...consolePropAttributes, value: stdout }, |
| 141 | + '_stderr': { ...consolePropAttributes, value: stderr } |
| 142 | + }); |
| 143 | +}; |
| 144 | + |
| 145 | +// Lazily load the stdout and stderr from an object so we don't |
| 146 | +// create the stdio streams when they are not even accessed |
| 147 | +Console.prototype[kBindStreamsLazy] = function(object) { |
| 148 | + let stdout; |
| 149 | + let stderr; |
| 150 | + Object.defineProperties(this, { |
| 151 | + '_stdout': { |
| 152 | + enumerable: false, |
| 153 | + configurable: true, |
| 154 | + get() { |
| 155 | + if (!stdout) stdout = object.stdout; |
| 156 | + return stdout; |
| 157 | + }, |
| 158 | + set(value) { stdout = value; } |
| 159 | + }, |
| 160 | + '_stderr': { |
| 161 | + enumerable: false, |
| 162 | + configurable: true, |
| 163 | + get() { |
| 164 | + if (!stderr) { stderr = object.stderr; } |
| 165 | + return stderr; |
| 166 | + }, |
| 167 | + set(value) { stderr = value; } |
| 168 | + } |
| 169 | + }); |
| 170 | +}; |
| 171 | + |
| 172 | +Console.prototype[kBindProperties] = function(ignoreErrors, colorMode) { |
| 173 | + Object.defineProperties(this, { |
| 174 | + '_stdoutErrorHandler': { |
| 175 | + ...consolePropAttributes, |
| 176 | + value: createWriteErrorHandler(this, kUseStdout) |
| 177 | + }, |
| 178 | + '_stderrErrorHandler': { |
| 179 | + ...consolePropAttributes, |
| 180 | + value: createWriteErrorHandler(this, kUseStderr) |
| 181 | + }, |
| 182 | + '_ignoreErrors': { |
| 183 | + ...consolePropAttributes, |
| 184 | + value: Boolean(ignoreErrors) |
| 185 | + }, |
| 186 | + '_times': { ...consolePropAttributes, value: new Map() } |
| 187 | + }); |
| 188 | + |
| 189 | + // TODO(joyeecheung): use consolePropAttributes for these |
| 190 | + // Corresponds to https://console.spec.whatwg.org/#count-map |
| 191 | + this[kCounts] = new Map(); |
| 192 | + this[kColorMode] = colorMode; |
| 193 | + this[kIsConsole] = true; |
| 194 | + this[kGroupIndent] = ''; |
| 195 | +}; |
| 196 | + |
142 | 197 | // Make a function that can serve as the callback passed to `stream.write()`. |
143 | | -function createWriteErrorHandler(stream) { |
| 198 | +function createWriteErrorHandler(instance, streamSymbol) { |
144 | 199 | return (err) => { |
145 | 200 | // This conditional evaluates to true if and only if there was an error |
146 | 201 | // that was not already emitted (which happens when the _write callback |
147 | 202 | // is invoked asynchronously). |
| 203 | + const stream = streamSymbol === kUseStdout ? |
| 204 | + instance._stdout : instance._stderr; |
148 | 205 | if (err !== null && !stream._writableState.errorEmitted) { |
149 | 206 | // If there was an error, it will be emitted on `stream` as |
150 | 207 | // an `error` event. Adding a `once` listener will keep that error |
@@ -158,7 +215,15 @@ function createWriteErrorHandler(stream) { |
158 | 215 | }; |
159 | 216 | } |
160 | 217 |
|
161 | | -function write(ignoreErrors, stream, string, errorhandler, groupIndent) { |
| 218 | +Console.prototype[kWriteToConsole] = function(streamSymbol, string) { |
| 219 | + const ignoreErrors = this._ignoreErrors; |
| 220 | + const groupIndent = this[kGroupIndent]; |
| 221 | + |
| 222 | + const useStdout = streamSymbol === kUseStdout; |
| 223 | + const stream = useStdout ? this._stdout : this._stderr; |
| 224 | + const errorHandler = useStdout ? |
| 225 | + this._stdoutErrorHandler : this._stderrErrorHandler; |
| 226 | + |
162 | 227 | if (groupIndent.length !== 0) { |
163 | 228 | if (string.indexOf('\n') !== -1) { |
164 | 229 | string = string.replace(/\n/g, `\n${groupIndent}`); |
@@ -176,7 +241,7 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) { |
176 | 241 | // Add and later remove a noop error handler to catch synchronous errors. |
177 | 242 | stream.once('error', noop); |
178 | 243 |
|
179 | | - stream.write(string, errorhandler); |
| 244 | + stream.write(string, errorHandler); |
180 | 245 | } catch (e) { |
181 | 246 | // Console is a debugging utility, so it swallowing errors is not desirable |
182 | 247 | // even in edge cases such as low stack space. |
@@ -186,7 +251,7 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) { |
186 | 251 | } finally { |
187 | 252 | stream.removeListener('error', noop); |
188 | 253 | } |
189 | | -} |
| 254 | +}; |
190 | 255 |
|
191 | 256 | const kColorInspectOptions = { colors: true }; |
192 | 257 | const kNoColorInspectOptions = {}; |
@@ -212,23 +277,17 @@ Console.prototype[kFormatForStderr] = function(args) { |
212 | 277 | }; |
213 | 278 |
|
214 | 279 | Console.prototype.log = function log(...args) { |
215 | | - write(this._ignoreErrors, |
216 | | - this._stdout, |
217 | | - this[kFormatForStdout](args), |
218 | | - this._stdoutErrorHandler, |
219 | | - this[kGroupIndent]); |
| 280 | + this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); |
220 | 281 | }; |
| 282 | + |
221 | 283 | Console.prototype.debug = Console.prototype.log; |
222 | 284 | Console.prototype.info = Console.prototype.log; |
223 | 285 | Console.prototype.dirxml = Console.prototype.log; |
224 | 286 |
|
225 | 287 | Console.prototype.warn = function warn(...args) { |
226 | | - write(this._ignoreErrors, |
227 | | - this._stderr, |
228 | | - this[kFormatForStderr](args), |
229 | | - this._stderrErrorHandler, |
230 | | - this[kGroupIndent]); |
| 288 | + this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args)); |
231 | 289 | }; |
| 290 | + |
232 | 291 | Console.prototype.error = Console.prototype.warn; |
233 | 292 |
|
234 | 293 | Console.prototype.dir = function dir(object, options) { |
@@ -237,11 +296,7 @@ Console.prototype.dir = function dir(object, options) { |
237 | 296 | ...this[kGetInspectOptions](this._stdout), |
238 | 297 | ...options |
239 | 298 | }; |
240 | | - write(this._ignoreErrors, |
241 | | - this._stdout, |
242 | | - util.inspect(object, options), |
243 | | - this._stdoutErrorHandler, |
244 | | - this[kGroupIndent]); |
| 299 | + this[kWriteToConsole](kUseStdout, util.inspect(object, options)); |
245 | 300 | }; |
246 | 301 |
|
247 | 302 | Console.prototype.time = function time(label = 'default') { |
@@ -301,7 +356,7 @@ Console.prototype.trace = function trace(...args) { |
301 | 356 | Console.prototype.assert = function assert(expression, ...args) { |
302 | 357 | if (!expression) { |
303 | 358 | args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`; |
304 | | - this.warn(this[kFormatForStderr](args)); |
| 359 | + this.warn(...args); // the arguments will be formatted in warn() again |
305 | 360 | } |
306 | 361 | }; |
307 | 362 |
|
@@ -363,7 +418,6 @@ const valuesKey = 'Values'; |
363 | 418 | const indexKey = '(index)'; |
364 | 419 | const iterKey = '(iteration index)'; |
365 | 420 |
|
366 | | - |
367 | 421 | const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v); |
368 | 422 |
|
369 | 423 | // https://console.spec.whatwg.org/#table |
@@ -490,38 +544,31 @@ function noop() {} |
490 | 544 | // we cannot actually use `new Console` to construct the global console. |
491 | 545 | // Therefore, the console.Console.prototype is not |
492 | 546 | // in the global console prototype chain anymore. |
| 547 | + |
| 548 | +// TODO(joyeecheung): |
| 549 | +// - Move the Console constructor into internal/console.js |
| 550 | +// - Move the global console creation code along with the inspector console |
| 551 | +// wrapping code in internal/bootstrap/node.js into a separate file. |
| 552 | +// - Make this file a simple re-export of those two files. |
493 | 553 | // This is only here for v11.x conflict resolution. |
494 | 554 | const globalConsole = Object.create(Console.prototype); |
495 | | -const tempConsole = new Console({ |
496 | | - stdout: process.stdout, |
497 | | - stderr: process.stderr |
498 | | -}); |
499 | 555 |
|
500 | 556 | // Since Console is not on the prototype chain of the global console, |
501 | 557 | // the symbol properties on Console.prototype have to be looked up from |
502 | | -// the global console itself. |
503 | | -for (const prop of Object.getOwnPropertySymbols(Console.prototype)) { |
504 | | - globalConsole[prop] = Console.prototype[prop]; |
505 | | -} |
506 | | - |
507 | | -// Reflect.ownKeys() is used here for retrieving Symbols |
508 | | -for (const prop of Reflect.ownKeys(tempConsole)) { |
509 | | - const desc = { ...(Reflect.getOwnPropertyDescriptor(tempConsole, prop)) }; |
510 | | - // Since Console would bind method calls onto the instance, |
511 | | - // make sure the methods are called on globalConsole instead of |
512 | | - // tempConsole. |
513 | | - if (typeof Console.prototype[prop] === 'function') { |
514 | | - desc.value = Console.prototype[prop].bind(globalConsole); |
| 558 | +// the global console itself. In addition, we need to make the global |
| 559 | +// console a namespace by binding the console methods directly onto |
| 560 | +// the global console with the receiver fixed. |
| 561 | +for (const prop of Reflect.ownKeys(Console.prototype)) { |
| 562 | + if (prop === 'constructor') { continue; } |
| 563 | + const desc = Reflect.getOwnPropertyDescriptor(Console.prototype, prop); |
| 564 | + if (typeof desc.value === 'function') { // fix the receiver |
| 565 | + desc.value = desc.value.bind(globalConsole); |
515 | 566 | } |
516 | 567 | Reflect.defineProperty(globalConsole, prop, desc); |
517 | 568 | } |
518 | 569 |
|
519 | | -globalConsole.Console = Console; |
520 | | - |
521 | | -Object.defineProperty(Console, Symbol.hasInstance, { |
522 | | - value(instance) { |
523 | | - return instance[kIsConsole]; |
524 | | - } |
525 | | -}); |
| 570 | +globalConsole[kBindStreamsLazy](process); |
| 571 | +globalConsole[kBindProperties](true, 'auto'); |
526 | 572 |
|
527 | 573 | module.exports = globalConsole; |
| 574 | +module.exports.Console = Console; |