From 5c73a32fb32856e7369b8327f79bf912f485507e Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 29 Nov 2018 06:14:30 +0800 Subject: [PATCH] lib: move lib/console.js to lib/internal/console/constructor.js This is a broken commit: it's here so that git interpret this as a file move and preserve most of the history of the Console constructor. PR-URL: #24709 Reviewed-By: Gus Caplan Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- lib/console.js | 574 ---------------------------- lib/internal/console/constructor.js | 89 ++++- 2 files changed, 70 insertions(+), 593 deletions(-) delete mode 100644 lib/console.js diff --git a/lib/console.js b/lib/console.js deleted file mode 100644 index f0b9a74cfc7df8..00000000000000 --- a/lib/console.js +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -'use strict'; - -const { trace } = internalBinding('trace_events'); -const { - isStackOverflowError, - codes: { - ERR_CONSOLE_WRITABLE_STREAM, - ERR_INVALID_ARG_TYPE, - ERR_INVALID_ARG_VALUE, - }, -} = require('internal/errors'); -const { previewEntries } = internalBinding('util'); -const { Buffer: { isBuffer } } = require('buffer'); -const util = require('util'); -const { - isTypedArray, isSet, isMap, isSetIterator, isMapIterator, -} = util.types; -const kCounts = Symbol('counts'); - -const kTraceConsoleCategory = 'node,node.console'; -const kTraceCount = 'C'.charCodeAt(0); -const kTraceBegin = 'b'.charCodeAt(0); -const kTraceEnd = 'e'.charCodeAt(0); -const kTraceInstant = 'n'.charCodeAt(0); - -const { - keys: ObjectKeys, - values: ObjectValues, -} = Object; -const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty); - -const { - isArray: ArrayIsArray, - from: ArrayFrom, -} = Array; - -// Lazy loaded for startup performance. -let cliTable; - -// Track amount of indentation required via `console.group()`. -const kGroupIndent = Symbol('kGroupIndent'); -const kFormatForStderr = Symbol('kFormatForStderr'); -const kFormatForStdout = Symbol('kFormatForStdout'); -const kGetInspectOptions = Symbol('kGetInspectOptions'); -const kColorMode = Symbol('kColorMode'); -const kIsConsole = Symbol('kIsConsole'); -const kWriteToConsole = Symbol('kWriteToConsole'); -const kBindProperties = Symbol('kBindProperties'); -const kBindStreamsEager = Symbol('kBindStreamsEager'); -const kBindStreamsLazy = Symbol('kBindStreamsLazy'); -const kUseStdout = Symbol('kUseStdout'); -const kUseStderr = Symbol('kUseStderr'); - -// This constructor is not used to construct the global console. -// It's exported for backwards compatibility. -function Console(options /* or: stdout, stderr, ignoreErrors = true */) { - // We have to test new.target here to see if this function is called - // with new, because we need to define a custom instanceof to accommodate - // the global console. - if (!new.target) { - return new Console(...arguments); - } - - if (!options || typeof options.write === 'function') { - options = { - stdout: options, - stderr: arguments[1], - ignoreErrors: arguments[2] - }; - } - - const { - stdout, - stderr = stdout, - ignoreErrors = true, - colorMode = 'auto' - } = options; - - if (!stdout || typeof stdout.write !== 'function') { - throw new ERR_CONSOLE_WRITABLE_STREAM('stdout'); - } - if (!stderr || typeof stderr.write !== 'function') { - throw new ERR_CONSOLE_WRITABLE_STREAM('stderr'); - } - - if (typeof colorMode !== 'boolean' && colorMode !== 'auto') - throw new ERR_INVALID_ARG_VALUE('colorMode', colorMode); - - // Bind the prototype functions to this Console instance - var keys = Object.keys(Console.prototype); - for (var v = 0; v < keys.length; v++) { - var k = keys[v]; - // We have to bind the methods grabbed from the instance instead of from - // the prototype so that users extending the Console can override them - // from the prototype chain of the subclass. - this[k] = this[k].bind(this); - } - - this[kBindStreamsEager](stdout, stderr); - this[kBindProperties](ignoreErrors, colorMode); -} - -const consolePropAttributes = { - writable: true, - enumerable: false, - configurable: true -}; - -// Fixup global.console instanceof global.console.Console -Object.defineProperty(Console, Symbol.hasInstance, { - value(instance) { - return instance[kIsConsole]; - } -}); - -// Eager version for the Console constructor -Console.prototype[kBindStreamsEager] = function(stdout, stderr) { - Object.defineProperties(this, { - '_stdout': { ...consolePropAttributes, value: stdout }, - '_stderr': { ...consolePropAttributes, value: stderr } - }); -}; - -// Lazily load the stdout and stderr from an object so we don't -// create the stdio streams when they are not even accessed -Console.prototype[kBindStreamsLazy] = function(object) { - let stdout; - let stderr; - Object.defineProperties(this, { - '_stdout': { - enumerable: false, - configurable: true, - get() { - if (!stdout) stdout = object.stdout; - return stdout; - }, - set(value) { stdout = value; } - }, - '_stderr': { - enumerable: false, - configurable: true, - get() { - if (!stderr) { stderr = object.stderr; } - return stderr; - }, - set(value) { stderr = value; } - } - }); -}; - -Console.prototype[kBindProperties] = function(ignoreErrors, colorMode) { - Object.defineProperties(this, { - '_stdoutErrorHandler': { - ...consolePropAttributes, - value: createWriteErrorHandler(this, kUseStdout) - }, - '_stderrErrorHandler': { - ...consolePropAttributes, - value: createWriteErrorHandler(this, kUseStderr) - }, - '_ignoreErrors': { - ...consolePropAttributes, - value: Boolean(ignoreErrors) - }, - '_times': { ...consolePropAttributes, value: new Map() } - }); - - // TODO(joyeecheung): use consolePropAttributes for these - // Corresponds to https://console.spec.whatwg.org/#count-map - this[kCounts] = new Map(); - this[kColorMode] = colorMode; - this[kIsConsole] = true; - this[kGroupIndent] = ''; -}; - -// Make a function that can serve as the callback passed to `stream.write()`. -function createWriteErrorHandler(instance, streamSymbol) { - return (err) => { - // This conditional evaluates to true if and only if there was an error - // that was not already emitted (which happens when the _write callback - // is invoked asynchronously). - const stream = streamSymbol === kUseStdout ? - instance._stdout : instance._stderr; - if (err !== null && !stream._writableState.errorEmitted) { - // If there was an error, it will be emitted on `stream` as - // an `error` event. Adding a `once` listener will keep that error - // from becoming an uncaught exception, but since the handler is - // removed after the event, non-console.* writes won't be affected. - // we are only adding noop if there is no one else listening for 'error' - if (stream.listenerCount('error') === 0) { - stream.on('error', noop); - } - } - }; -} - -Console.prototype[kWriteToConsole] = function(streamSymbol, string) { - const ignoreErrors = this._ignoreErrors; - const groupIndent = this[kGroupIndent]; - - const useStdout = streamSymbol === kUseStdout; - const stream = useStdout ? this._stdout : this._stderr; - const errorHandler = useStdout ? - this._stdoutErrorHandler : this._stderrErrorHandler; - - if (groupIndent.length !== 0) { - if (string.indexOf('\n') !== -1) { - string = string.replace(/\n/g, `\n${groupIndent}`); - } - string = groupIndent + string; - } - string += '\n'; - - if (ignoreErrors === false) return stream.write(string); - - // There may be an error occurring synchronously (e.g. for files or TTYs - // on POSIX systems) or asynchronously (e.g. pipes on POSIX systems), so - // handle both situations. - try { - // Add and later remove a noop error handler to catch synchronous errors. - stream.once('error', noop); - - stream.write(string, errorHandler); - } catch (e) { - // Console is a debugging utility, so it swallowing errors is not desirable - // even in edge cases such as low stack space. - if (isStackOverflowError(e)) - throw e; - // Sorry, there's no proper way to pass along the error here. - } finally { - stream.removeListener('error', noop); - } -}; - -const kColorInspectOptions = { colors: true }; -const kNoColorInspectOptions = {}; -Console.prototype[kGetInspectOptions] = function(stream) { - let color = this[kColorMode]; - if (color === 'auto') { - color = stream.isTTY && ( - typeof stream.getColorDepth === 'function' ? - stream.getColorDepth() > 2 : true); - } - - return color ? kColorInspectOptions : kNoColorInspectOptions; -}; - -Console.prototype[kFormatForStdout] = function(args) { - const opts = this[kGetInspectOptions](this._stdout); - return util.formatWithOptions(opts, ...args); -}; - -Console.prototype[kFormatForStderr] = function(args) { - const opts = this[kGetInspectOptions](this._stderr); - return util.formatWithOptions(opts, ...args); -}; - -Console.prototype.log = function log(...args) { - this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); -}; - -Console.prototype.debug = Console.prototype.log; -Console.prototype.info = Console.prototype.log; -Console.prototype.dirxml = Console.prototype.log; - -Console.prototype.warn = function warn(...args) { - this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args)); -}; - -Console.prototype.error = Console.prototype.warn; - -Console.prototype.dir = function dir(object, options) { - options = { - customInspect: false, - ...this[kGetInspectOptions](this._stdout), - ...options - }; - this[kWriteToConsole](kUseStdout, util.inspect(object, options)); -}; - -Console.prototype.time = function time(label = 'default') { - // Coerces everything other than Symbol to a string - label = `${label}`; - if (this._times.has(label)) { - process.emitWarning(`Label '${label}' already exists for console.time()`); - return; - } - trace(kTraceBegin, kTraceConsoleCategory, `time::${label}`, 0); - this._times.set(label, process.hrtime()); -}; - -Console.prototype.timeEnd = function timeEnd(label = 'default') { - // Coerces everything other than Symbol to a string - label = `${label}`; - const hasWarned = timeLogImpl(this, 'timeEnd', label); - trace(kTraceEnd, kTraceConsoleCategory, `time::${label}`, 0); - if (!hasWarned) { - this._times.delete(label); - } -}; - -Console.prototype.timeLog = function timeLog(label = 'default', ...data) { - // Coerces everything other than Symbol to a string - label = `${label}`; - timeLogImpl(this, 'timeLog', label, data); - trace(kTraceInstant, kTraceConsoleCategory, `time::${label}`, 0); -}; - -// Returns true if label was not found -function timeLogImpl(self, name, label, data) { - const time = self._times.get(label); - if (!time) { - process.emitWarning(`No such label '${label}' for console.${name}()`); - return true; - } - const duration = process.hrtime(time); - const ms = duration[0] * 1000 + duration[1] / 1e6; - if (data === undefined) { - self.log('%s: %sms', label, ms.toFixed(3)); - } else { - self.log('%s: %sms', label, ms.toFixed(3), ...data); - } - return false; -} - -Console.prototype.trace = function trace(...args) { - const err = { - name: 'Trace', - message: this[kFormatForStderr](args) - }; - Error.captureStackTrace(err, trace); - this.error(err.stack); -}; - -Console.prototype.assert = function assert(expression, ...args) { - if (!expression) { - args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`; - this.warn(...args); // the arguments will be formatted in warn() again - } -}; - -// Defined by: https://console.spec.whatwg.org/#clear -Console.prototype.clear = function clear() { - // It only makes sense to clear if _stdout is a TTY. - // Otherwise, do nothing. - if (this._stdout.isTTY) { - // The require is here intentionally to avoid readline being - // required too early when console is first loaded. - const { cursorTo, clearScreenDown } = require('readline'); - cursorTo(this._stdout, 0, 0); - clearScreenDown(this._stdout); - } -}; - -// Defined by: https://console.spec.whatwg.org/#count -Console.prototype.count = function count(label = 'default') { - // Ensures that label is a string, and only things that can be - // coerced to strings. e.g. Symbol is not allowed - label = `${label}`; - const counts = this[kCounts]; - let count = counts.get(label); - if (count === undefined) - count = 1; - else - count++; - counts.set(label, count); - trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, count); - this.log(`${label}: ${count}`); -}; - -// Defined by: https://console.spec.whatwg.org/#countreset -Console.prototype.countReset = function countReset(label = 'default') { - const counts = this[kCounts]; - if (!counts.has(label)) { - process.emitWarning(`Count for '${label}' does not exist`); - return; - } - trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, 0); - counts.delete(`${label}`); -}; - -Console.prototype.group = function group(...data) { - if (data.length > 0) { - this.log(...data); - } - this[kGroupIndent] += ' '; -}; -Console.prototype.groupCollapsed = Console.prototype.group; - -Console.prototype.groupEnd = function groupEnd() { - this[kGroupIndent] = - this[kGroupIndent].slice(0, this[kGroupIndent].length - 2); -}; - -const keyKey = 'Key'; -const valuesKey = 'Values'; -const indexKey = '(index)'; -const iterKey = '(iteration index)'; - -const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v); - -// https://console.spec.whatwg.org/#table -Console.prototype.table = function(tabularData, properties) { - if (properties !== undefined && !ArrayIsArray(properties)) - throw new ERR_INVALID_ARG_TYPE('properties', 'Array', properties); - - if (tabularData === null || typeof tabularData !== 'object') - return this.log(tabularData); - - if (cliTable === undefined) cliTable = require('internal/cli_table'); - const final = (k, v) => this.log(cliTable(k, v)); - - const inspect = (v) => { - const opt = { depth: 0, maxArrayLength: 3 }; - if (v !== null && typeof v === 'object' && - !isArray(v) && ObjectKeys(v).length > 2) - opt.depth = -1; - Object.assign(opt, this[kGetInspectOptions](this._stdout)); - return util.inspect(v, opt); - }; - const getIndexArray = (length) => ArrayFrom({ length }, (_, i) => inspect(i)); - - const mapIter = isMapIterator(tabularData); - let isKeyValue = false; - let i = 0; - if (mapIter) { - const res = previewEntries(tabularData, true); - tabularData = res[0]; - isKeyValue = res[1]; - } - - if (isKeyValue || isMap(tabularData)) { - const keys = []; - const values = []; - let length = 0; - if (mapIter) { - for (; i < tabularData.length / 2; ++i) { - keys.push(inspect(tabularData[i * 2])); - values.push(inspect(tabularData[i * 2 + 1])); - length++; - } - } else { - for (const [k, v] of tabularData) { - keys.push(inspect(k)); - values.push(inspect(v)); - length++; - } - } - return final([ - iterKey, keyKey, valuesKey - ], [ - getIndexArray(length), - keys, - values, - ]); - } - - const setIter = isSetIterator(tabularData); - if (setIter) - tabularData = previewEntries(tabularData); - - const setlike = setIter || (mapIter && !isKeyValue) || isSet(tabularData); - if (setlike) { - const values = []; - let length = 0; - for (const v of tabularData) { - values.push(inspect(v)); - length++; - } - return final([setlike ? iterKey : indexKey, valuesKey], [ - getIndexArray(length), - values, - ]); - } - - const map = {}; - let hasPrimitives = false; - const valuesKeyArray = []; - const indexKeyArray = ObjectKeys(tabularData); - - for (; i < indexKeyArray.length; i++) { - const item = tabularData[indexKeyArray[i]]; - const primitive = item === null || - (typeof item !== 'function' && typeof item !== 'object'); - if (properties === undefined && primitive) { - hasPrimitives = true; - valuesKeyArray[i] = inspect(item); - } else { - const keys = properties || ObjectKeys(item); - for (const key of keys) { - if (map[key] === undefined) - map[key] = []; - if ((primitive && properties) || !hasOwnProperty(item, key)) - map[key][i] = ''; - else - map[key][i] = item == null ? item : inspect(item[key]); - } - } - } - - const keys = ObjectKeys(map); - const values = ObjectValues(map); - if (hasPrimitives) { - keys.push(valuesKey); - values.push(valuesKeyArray); - } - keys.unshift(indexKey); - values.unshift(indexKeyArray); - - return final(keys, values); -}; - -function noop() {} - -// See https://console.spec.whatwg.org/#console-namespace -// > For historical web-compatibility reasons, the namespace object -// > for console must have as its [[Prototype]] an empty object, -// > created as if by ObjectCreate(%ObjectPrototype%), -// > instead of %ObjectPrototype%. - -// Since in Node.js, the Console constructor has been exposed through -// require('console'), we need to keep the Console constructor but -// we cannot actually use `new Console` to construct the global console. -// Therefore, the console.Console.prototype is not -// in the global console prototype chain anymore. - -// TODO(joyeecheung): -// - Move the Console constructor into internal/console.js -// - Move the global console creation code along with the inspector console -// wrapping code in internal/bootstrap/node.js into a separate file. -// - Make this file a simple re-export of those two files. -// This is only here for v11.x conflict resolution. -const globalConsole = Object.create(Console.prototype); - -// Since Console is not on the prototype chain of the global console, -// the symbol properties on Console.prototype have to be looked up from -// the global console itself. In addition, we need to make the global -// console a namespace by binding the console methods directly onto -// the global console with the receiver fixed. -for (const prop of Reflect.ownKeys(Console.prototype)) { - if (prop === 'constructor') { continue; } - const desc = Reflect.getOwnPropertyDescriptor(Console.prototype, prop); - if (typeof desc.value === 'function') { // fix the receiver - desc.value = desc.value.bind(globalConsole); - } - Reflect.defineProperty(globalConsole, prop, desc); -} - -globalConsole[kBindStreamsLazy](process); -globalConsole[kBindProperties](true, 'auto'); - -module.exports = globalConsole; -module.exports.Console = Console; diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index dcfb5f4a70a826..f0b9a74cfc7df8 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -1,7 +1,25 @@ -'use strict'; +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -// The Console constructor is not actually used to construct the global -// console. It's exported for backwards compatibility. +'use strict'; const { trace } = internalBinding('trace_events'); const { @@ -54,6 +72,8 @@ const kBindStreamsLazy = Symbol('kBindStreamsLazy'); const kUseStdout = Symbol('kUseStdout'); const kUseStderr = Symbol('kUseStderr'); +// This constructor is not used to construct the global console. +// It's exported for backwards compatibility. function Console(options /* or: stdout, stderr, ignoreErrors = true */) { // We have to test new.target here to see if this function is called // with new, because we need to define a custom instanceof to accommodate @@ -271,11 +291,12 @@ Console.prototype.warn = function warn(...args) { Console.prototype.error = Console.prototype.warn; Console.prototype.dir = function dir(object, options) { - this[kWriteToConsole](kUseStdout, util.inspect(object, { + options = { customInspect: false, ...this[kGetInspectOptions](this._stdout), ...options - })); + }; + this[kWriteToConsole](kUseStdout, util.inspect(object, options)); }; Console.prototype.time = function time(label = 'default') { @@ -411,15 +432,11 @@ Console.prototype.table = function(tabularData, properties) { const final = (k, v) => this.log(cliTable(k, v)); const inspect = (v) => { - const depth = v !== null && - typeof v === 'object' && - !isArray(v) && - ObjectKeys(v).length > 2 ? -1 : 0; - const opt = { - depth, - maxArrayLength: 3, - ...this[kGetInspectOptions](this._stdout) - }; + const opt = { depth: 0, maxArrayLength: 3 }; + if (v !== null && typeof v === 'object' && + !isArray(v) && ObjectKeys(v).length > 2) + opt.depth = -1; + Object.assign(opt, this[kGetInspectOptions](this._stdout)); return util.inspect(v, opt); }; const getIndexArray = (length) => ArrayFrom({ length }, (_, i) => inspect(i)); @@ -516,8 +533,42 @@ Console.prototype.table = function(tabularData, properties) { function noop() {} -module.exports = { - Console, - kBindStreamsLazy, - kBindProperties -}; +// See https://console.spec.whatwg.org/#console-namespace +// > For historical web-compatibility reasons, the namespace object +// > for console must have as its [[Prototype]] an empty object, +// > created as if by ObjectCreate(%ObjectPrototype%), +// > instead of %ObjectPrototype%. + +// Since in Node.js, the Console constructor has been exposed through +// require('console'), we need to keep the Console constructor but +// we cannot actually use `new Console` to construct the global console. +// Therefore, the console.Console.prototype is not +// in the global console prototype chain anymore. + +// TODO(joyeecheung): +// - Move the Console constructor into internal/console.js +// - Move the global console creation code along with the inspector console +// wrapping code in internal/bootstrap/node.js into a separate file. +// - Make this file a simple re-export of those two files. +// This is only here for v11.x conflict resolution. +const globalConsole = Object.create(Console.prototype); + +// Since Console is not on the prototype chain of the global console, +// the symbol properties on Console.prototype have to be looked up from +// the global console itself. In addition, we need to make the global +// console a namespace by binding the console methods directly onto +// the global console with the receiver fixed. +for (const prop of Reflect.ownKeys(Console.prototype)) { + if (prop === 'constructor') { continue; } + const desc = Reflect.getOwnPropertyDescriptor(Console.prototype, prop); + if (typeof desc.value === 'function') { // fix the receiver + desc.value = desc.value.bind(globalConsole); + } + Reflect.defineProperty(globalConsole, prop, desc); +} + +globalConsole[kBindStreamsLazy](process); +globalConsole[kBindProperties](true, 'auto'); + +module.exports = globalConsole; +module.exports.Console = Console;