diff options
Diffstat (limited to 'src/js/builtins/ConsoleObject.ts')
-rw-r--r-- | src/js/builtins/ConsoleObject.ts | 840 |
1 files changed, 820 insertions, 20 deletions
diff --git a/src/js/builtins/ConsoleObject.ts b/src/js/builtins/ConsoleObject.ts index 45746459a..b48593154 100644 --- a/src/js/builtins/ConsoleObject.ts +++ b/src/js/builtins/ConsoleObject.ts @@ -1,18 +1,55 @@ $overriddenName = "[Symbol.asyncIterator]"; export function asyncIterator(this: Console) { - const Iterator = async function* ConsoleAsyncIterator() { - const stream = Bun.stdin.stream(); - var reader = stream.getReader(); + var stream = Bun.stdin.stream(); - // TODO: use builtin - var decoder = new (globalThis as any).TextDecoder("utf-8", { fatal: false }) as TextDecoder; - var deferredError; - var indexOf = Bun.indexOfLine; + var decoder = new TextDecoder("utf-8", { fatal: false }); + var indexOf = Bun.indexOfLine; + var actualChunk: Uint8Array; + var i: number = -1; + var idx: number; + var last: number; + var done: boolean; + var value: Uint8Array[]; + var value_len: number; + var pendingChunk: Uint8Array | undefined; + async function* ConsoleAsyncIterator() { + var reader = stream.getReader(); + var deferredError; try { + if (i !== -1) { + last = i + 1; + i = indexOf(actualChunk, last); + + while (i !== -1) { + yield decoder.decode(actualChunk.subarray(last, i)); + last = i + 1; + i = indexOf(actualChunk, last); + } + + for (idx++; idx < value_len; idx++) { + actualChunk = value[idx]; + if (pendingChunk) { + actualChunk = Buffer.concat([pendingChunk, actualChunk]); + pendingChunk = undefined; + } + + last = 0; + // TODO: "\r", 0x4048, 0x4049, 0x404A, 0x404B, 0x404C, 0x404D, 0x404E, 0x404F + i = indexOf(actualChunk, last); + while (i !== -1) { + yield decoder.decode(actualChunk.subarray(last, i)); + last = i + 1; + i = indexOf(actualChunk, last); + } + i = -1; + + pendingChunk = actualChunk.subarray(last); + } + actualChunk = undefined!; + } + while (true) { - var done, value; - var pendingChunk; const firstResult = reader.readMany(); if ($isPromise(firstResult)) { ({ done, value } = await firstResult); @@ -27,26 +64,29 @@ export function asyncIterator(this: Console) { return; } - var actualChunk; // we assume it was given line-by-line - for (const chunk of value) { - actualChunk = chunk; + for (idx = 0, value_len = value.length; idx < value_len; idx++) { + actualChunk = value[idx]; if (pendingChunk) { - actualChunk = Buffer.concat([pendingChunk, chunk]); - pendingChunk = null; + actualChunk = Buffer.concat([pendingChunk, actualChunk]); + pendingChunk = undefined; } - var last = 0; + last = 0; // TODO: "\r", 0x4048, 0x4049, 0x404A, 0x404B, 0x404C, 0x404D, 0x404E, 0x404F - var i = indexOf(actualChunk, last); + i = indexOf(actualChunk, last); while (i !== -1) { + // This yield may end the function, in that case we need to be able to recover state + // if the iterator was fired up again. yield decoder.decode(actualChunk.subarray(last, i)); last = i + 1; i = indexOf(actualChunk, last); } + i = -1; pendingChunk = actualChunk.subarray(last); } + actualChunk = undefined!; } } catch (e) { deferredError = e; @@ -57,11 +97,11 @@ export function asyncIterator(this: Console) { throw deferredError; } } - }; + } const symbol = globalThis.Symbol.asyncIterator; - this[symbol] = Iterator; - return Iterator(); + this[symbol] = ConsoleAsyncIterator; + return ConsoleAsyncIterator(); } export function write(this: Console, input) { @@ -76,9 +116,769 @@ export function write(this: Console, input) { const count = $argumentCount(); for (var i = 1; i < count; i++) { - wrote += writer.write($argument(i)); + wrote += writer.write(arguments[i]); } writer.flush(true); return wrote; } + +// This is the `console.Console` constructor. It is mostly copied from Node. +// https://github.com/nodejs/node/blob/d2c7c367741bdcb6f7f77f55ce95a745f0b29fef/lib/internal/console/constructor.js +// Some parts are copied from imported files and inlined here. Not too much of a performance issue +// to do extra work at startup, since most people do not need `console.Console`. +// TODO: probably could extract `getStringWidth`; probably make that a native function. note how it is copied from `readline.js` +export function createConsoleConstructor(console: typeof globalThis.console) { + const { inspect, formatWithOptions } = require("node:util"); + const { isBuffer } = require("node:buffer"); + + const StringPrototypeIncludes = String.prototype.includes; + const RegExpPrototypeSymbolReplace = RegExp.prototype[Symbol.replace]; + const ArrayPrototypeUnshift = Array.prototype.unshift; + const StringPrototypeRepeat = String.prototype.repeat; + const StringPrototypeSlice = String.prototype.slice; + const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty; + const StringPrototypePadStart = String.prototype.padStart; + const StringPrototypeSplit = String.prototype.split; + const NumberPrototypeToFixed = Number.prototype.toFixed; + const StringPrototypeNormalize = String.prototype.normalize; + const StringPrototypeCodePointAt = String.prototype.codePointAt; + const ArrayPrototypeMap = Array.prototype.map; + const ArrayPrototypeJoin = Array.prototype.join; + const ArrayPrototypePush = Array.prototype.push; + + const kCounts = Symbol("counts"); + + const kSecond = 1000; + const kMinute = 60 * kSecond; + const kHour = 60 * kMinute; + + // Regex used for ansi escape code splitting + // Adopted from https://github.com/chalk/ansi-regex/blob/HEAD/index.js + // License: MIT, authors: @sindresorhus, Qix-, arjunmehta and LitoMore + // Matches all ansi escape code sequences in a string + var ansiPattern = + "[\\u001B\\u009B][[\\]()#;?]*" + + "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" + + "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" + + "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"; + var ansi = new RegExp(ansiPattern, "g"); + + /** + * Returns true if the character represented by a given + * Unicode code point is full-width. Otherwise returns false. + */ + var isFullWidthCodePoint = code => { + // Code points are partially derived from: + // https://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt + return ( + code >= 0x1100 && + (code <= 0x115f || // Hangul Jamo + code === 0x2329 || // LEFT-POINTING ANGLE BRACKET + code === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (code >= 0x3250 && code <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (code >= 0x4e00 && code <= 0xa4c6) || + // Hangul Jamo Extended-A + (code >= 0xa960 && code <= 0xa97c) || + // Hangul Syllables + (code >= 0xac00 && code <= 0xd7a3) || + // CJK Compatibility Ideographs + (code >= 0xf900 && code <= 0xfaff) || + // Vertical Forms + (code >= 0xfe10 && code <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (code >= 0xfe30 && code <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (code >= 0xff01 && code <= 0xff60) || + (code >= 0xffe0 && code <= 0xffe6) || + // Kana Supplement + (code >= 0x1b000 && code <= 0x1b001) || + // Enclosed Ideographic Supplement + (code >= 0x1f200 && code <= 0x1f251) || + // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff + // Emoticons 0x1f600 - 0x1f64f + (code >= 0x1f300 && code <= 0x1f64f) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (code >= 0x20000 && code <= 0x3fffd)) + ); + }; + + var isZeroWidthCodePoint = code => { + return ( + code <= 0x1f || // C0 control codes + (code >= 0x7f && code <= 0x9f) || // C1 control codes + (code >= 0x300 && code <= 0x36f) || // Combining Diacritical Marks + (code >= 0x200b && code <= 0x200f) || // Modifying Invisible Characters + // Combining Diacritical Marks for Symbols + (code >= 0x20d0 && code <= 0x20ff) || + (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors + (code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks + (code >= 0xe0100 && code <= 0xe01ef) + ); // Variation Selectors + }; + + function stripVTControlCharacters(str) { + return (RegExpPrototypeSymbolReplace as any).$call(ansi, str, ""); + } + + /** + * Returns the number of columns required to display the given string. + */ + var getStringWidth = function getStringWidth(str, removeControlChars = true) { + var width = 0; + + if (removeControlChars) str = stripVTControlCharacters(str); + str = StringPrototypeNormalize.$call(str, "NFC"); + for (var char of str) { + var code = StringPrototypeCodePointAt.$call(char, 0); + if (isFullWidthCodePoint(code)) { + width += 2; + } else if (!isZeroWidthCodePoint(code)) { + width++; + } + } + + return width; + }; + + const tableChars = { + middleMiddle: "─", + rowMiddle: "┼", + topRight: "┐", + topLeft: "┌", + leftMiddle: "├", + topMiddle: "┬", + bottomRight: "┘", + bottomLeft: "└", + bottomMiddle: "┴", + rightMiddle: "┤", + left: "│ ", + right: " │", + middle: " │ ", + }; + + const renderRow = (row, columnWidths) => { + let out = tableChars.left; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + const len = getStringWidth(cell); + const needed = (columnWidths[i] - len) / 2; + // round(needed) + ceil(needed) will always add up to the amount + // of spaces we need while also left justifying the output. + out += + (StringPrototypeRepeat as any).$call(" ", needed) + cell + StringPrototypeRepeat.$call(" ", Math.ceil(needed)); + if (i !== row.length - 1) out += tableChars.middle; + } + out += tableChars.right; + return out; + }; + + const table = (head, columns) => { + const columnWidths = ArrayPrototypeMap.$call(head, h => getStringWidth(h)) as number[]; + const longestColumn = Math.max(...(ArrayPrototypeMap as any).$call(columns, a => a.length)); + const rows: any = $newArrayWithSize(longestColumn); + + for (let i = 0; i < head.length; i++) { + const column = columns[i]; + for (let j = 0; j < longestColumn; j++) { + if (rows[j] === undefined) rows[j] = []; + const value = (rows[j][i] = ObjectPrototypeHasOwnProperty.$call(column, j) ? column[j] : ""); + const width = columnWidths[i] || 0; + const counted = getStringWidth(value); + columnWidths[i] = Math.max(width, counted); + } + } + + const divider = ArrayPrototypeMap.$call(columnWidths, i => + StringPrototypeRepeat.$call(tableChars.middleMiddle, i + 2), + ); + + let result = + tableChars.topLeft + + ArrayPrototypeJoin.$call(divider, tableChars.topMiddle) + + tableChars.topRight + + "\n" + + renderRow(head, columnWidths) + + "\n" + + tableChars.leftMiddle + + ArrayPrototypeJoin.$call(divider, tableChars.rowMiddle) + + tableChars.rightMiddle + + "\n"; + + for (const row of rows) result += `${renderRow(row, columnWidths)}\n`; + + result += + tableChars.bottomLeft + ArrayPrototypeJoin.$call(divider, tableChars.bottomMiddle) + tableChars.bottomRight; + + return result; + }; + + // Track amount of indentation required via `console.group()`. + const kGroupIndent = Symbol("kGroupIndent"); + const kGroupIndentationWidth = Symbol("kGroupIndentWidth"); + 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"); + + const optionsMap = new WeakMap<any, any>(); + function Console(this: any, 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 === undefined) { + return Reflect.construct(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", + inspectOptions, + groupIndentation, + } = options; + + if (!stdout || typeof stdout.write !== "function") { + // throw new ERR_CONSOLE_WRITABLE_STREAM("stdout"); + throw new TypeError("stdout is not a writable stream"); + } + if (!stderr || typeof stderr.write !== "function") { + // throw new ERR_CONSOLE_WRITABLE_STREAM("stderr"); + throw new TypeError("stderr is not a writable stream"); + } + + if (typeof colorMode !== "boolean" && colorMode !== "auto") { + // throw new ERR_INVALID_ARG_VALUE("colorMode", colorMode); + throw new TypeError("colorMode must be a boolean or 'auto'"); + } + + if (groupIndentation !== undefined) { + // validateInteger(groupIndentation, "groupIndentation", 0, kMaxGroupIndentation); + } + + if (inspectOptions !== undefined) { + // validateObject(inspectOptions, "options.inspectOptions"); + + if (inspectOptions.colors !== undefined && options.colorMode !== undefined) { + // throw new ERR_INCOMPATIBLE_OPTION_PAIR("options.inspectOptions.color", "colorMode"); + } + optionsMap.set(this, inspectOptions); + } + + // Bind the prototype functions to this Console instance + Object.keys(Console.prototype).forEach(key => { + // 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[key] = this[key].bind(this); + Object.defineProperty(this[key], "name", { + value: key, + }); + }); + + this[kBindStreamsEager](stdout, stderr); + this[kBindProperties](ignoreErrors, colorMode, groupIndentation); + } + + 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] || instance === console; + }, + }); + + const kColorInspectOptions = { colors: true }; + const kNoColorInspectOptions = {}; + + Object.defineProperties((Console.prototype = {}), { + [kBindStreamsEager]: { + ...consolePropAttributes, + // Eager version for the Console constructor + value: function (stdout, stderr) { + Object.defineProperties(this, { + "_stdout": { ...consolePropAttributes, value: stdout }, + "_stderr": { ...consolePropAttributes, value: stderr }, + }); + }, + }, + [kBindStreamsLazy]: { + ...consolePropAttributes, + // Lazily load the stdout and stderr from an object so we don't + // create the stdio streams when they are not even accessed + value: 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; + }, + }, + }); + }, + }, + [kBindProperties]: { + ...consolePropAttributes, + value: function (ignoreErrors, colorMode, groupIndentation = 2) { + 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() }, + // Corresponds to https://console.spec.whatwg.org/#count-map + [kCounts]: { ...consolePropAttributes, value: new Map() }, + [kColorMode]: { ...consolePropAttributes, value: colorMode }, + [kIsConsole]: { ...consolePropAttributes, value: true }, + [kGroupIndent]: { ...consolePropAttributes, value: "" }, + [kGroupIndentationWidth]: { + ...consolePropAttributes, + value: groupIndentation, + }, + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: "console", + }, + }); + }, + }, + [kWriteToConsole]: { + ...consolePropAttributes, + value: 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 (StringPrototypeIncludes.$call(string, "\n")) { + // ?! + string = (RegExpPrototypeSymbolReplace.$call as any)(/\n/g, string, `\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. + if (stream.listenerCount("error") === 0) 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); + } + }, + }, + [kGetInspectOptions]: { + ...consolePropAttributes, + value: function (stream) { + let color = this[kColorMode]; + if (color === "auto") { + if (process.env.FORCE_COLOR !== undefined) { + color = Bun.enableANSIColors; + } else { + color = stream.isTTY && (typeof stream.getColorDepth === "function" ? stream.getColorDepth() > 2 : true); + } + } + + const options = optionsMap.get(this); + if (options) { + if (options.colors === undefined) { + options.colors = color; + } + return options; + } + + return color ? kColorInspectOptions : kNoColorInspectOptions; + }, + }, + [kFormatForStdout]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stdout); + return formatWithOptions(opts, ...args); + }, + }, + [kFormatForStderr]: { + ...consolePropAttributes, + value: function (args) { + const opts = this[kGetInspectOptions](this._stderr); + return formatWithOptions(opts, ...args); + }, + }, + }); + + // 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.once("error", noop); + } + } + }; + } + + const consoleMethods: any = { + log(...args) { + this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args)); + }, + + warn(...args) { + this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args)); + }, + + dir(object, options) { + this[kWriteToConsole]( + kUseStdout, + inspect(object, { + customInspect: false, + ...this[kGetInspectOptions](this._stdout), + ...options, + }), + ); + }, + + 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()); + }, + + timeEnd(label = "default") { + // Coerces everything other than Symbol to a string + label = `${label}`; + const found = timeLogImpl(this, "timeEnd", label); + // trace(kTraceEnd, kTraceConsoleCategory, `time::${label}`, 0); + if (found) { + this._times.delete(label); + } + }, + + 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); + }, + + trace: function trace(...args) { + const err: Error = { + name: "Trace", + message: this[kFormatForStderr](args), + }; + Error.captureStackTrace(err, trace); + this.error(err.stack); + }, + + assert(expression, ...args) { + if (!expression) { + args[0] = `Assertion failed${args.length === 0 ? "" : `: ${args[0]}`}`; + // The arguments will be formatted in warn() again + this.warn.$apply(this, args); + } + }, + + // Defined by: https://console.spec.whatwg.org/#clear + clear() { + // It only makes sense to clear if _stdout is a TTY. + // Otherwise, do nothing. + if (this._stdout.isTTY && process.env.TERM !== "dumb") { + this._stdout.write("\x1b[2J"); + this._stdout.write("\x1b[0f"); + } + }, + + // Defined by: https://console.spec.whatwg.org/#count + 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 + 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}`); + }, + + group(...data) { + if (data.length > 0) { + this.log.$apply(this, data); + } + this[kGroupIndent] += StringPrototypeRepeat.$call(" ", this[kGroupIndentationWidth]); + }, + + groupEnd() { + this[kGroupIndent] = StringPrototypeSlice.$call( + this[kGroupIndent], + 0, + this[kGroupIndent].length - this[kGroupIndentationWidth], + ); + }, + + // https://console.spec.whatwg.org/#table + table(tabularData, properties) { + if (properties !== undefined) { + // validateArray(properties, "properties"); + } + + if (tabularData === null || typeof tabularData !== "object") return this.log(tabularData); + const final = (k, v) => this.log(table(k, v)); + + const _inspect = v => { + const depth = v !== null && typeof v === "object" && !isArray(v) && Object.keys(v).length > 2 ? -1 : 0; + const opt = { + depth, + maxArrayLength: 3, + breakLength: Infinity, + ...this[kGetInspectOptions](this._stdout), + }; + return inspect(v, opt); + }; + const getIndexArray = length => Array.from({ 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) { + ArrayPrototypePush.$call(keys, _inspect(tabularData[i * 2])); + ArrayPrototypePush.$call(values, _inspect(tabularData[i * 2 + 1])); + length++; + } + } else { + for (const { 0: k, 1: v } of tabularData) { + ArrayPrototypePush.$call(keys, _inspect(k)); + ArrayPrototypePush.$call(values, _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 || $isSet(tabularData); + if (setlike) { + const values = []; + let length = 0; + for (const v of tabularData as Set<any>) { + ArrayPrototypePush.$call(values, _inspect(v)); + length++; + } + return final([iterKey, valuesKey], [getIndexArray(length), values]); + } + + const map = { __proto__: null }; + let hasPrimitives = false; + const valuesKeyArray: any = []; + const indexKeyArray = Object.keys(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 || Object.keys(item); + for (const key of keys) { + map[key] ??= []; + if ((primitive && properties) || !ObjectPrototypeHasOwnProperty.$call(item, key)) map[key][i] = ""; + else map[key][i] = _inspect(item[key]); + } + } + } + + const keys = Object.keys(map); + const values = Object.values(map); + if (hasPrimitives) { + ArrayPrototypePush.$call(keys, valuesKey); + ArrayPrototypePush.$call(values, valuesKeyArray); + } + ArrayPrototypeUnshift.$call(keys, indexKey); + ArrayPrototypeUnshift.$call(values, indexKeyArray); + + return final(keys, values); + }, + }; + + // Returns true if label was found + function timeLogImpl(self, name, label, data?) { + const time = self._times.get(label); + if (time === undefined) { + process.emitWarning(`No such label '${label}' for console.${name}()`); + return false; + } + const duration = process.hrtime(time); + const ms = duration[0] * 1000 + duration[1] / 1e6; + + const formatted = formatTime(ms); + + if (data === undefined) { + self.log("%s: %s", label, formatted); + } else { + self.log("%s: %s", label, formatted, ...data); + } + return true; + } + + function pad(value) { + return StringPrototypePadStart.$call(`${value}`, 2, "0"); + } + + function formatTime(ms) { + let hours = 0; + let minutes = 0; + let seconds: string | number = 0; + + if (ms >= kSecond) { + if (ms >= kMinute) { + if (ms >= kHour) { + hours = Math.floor(ms / kHour); + ms = ms % kHour; + } + minutes = Math.floor(ms / kMinute); + ms = ms % kMinute; + } + seconds = ms / kSecond; + } + + if (hours !== 0 || minutes !== 0) { + ({ 0: seconds, 1: ms } = (StringPrototypeSplit.$call as any)(NumberPrototypeToFixed.$call(seconds, 3), ".")); + const res = hours !== 0 ? `${hours}:${pad(minutes)}` : minutes; + return `${res}:${pad(seconds)}.${ms} (${hours !== 0 ? "h:m" : ""}m:ss.mmm)`; + } + + if (seconds !== 0) { + return `${NumberPrototypeToFixed.$call(seconds, 3)}s`; + } + + return `${Number(NumberPrototypeToFixed.$call(ms, 3))}ms`; + } + + const keyKey = "Key"; + const valuesKey = "Values"; + const indexKey = "(index)"; + const iterKey = "(iteration index)"; + + const isArray = v => $isJSArray(v) || $isTypedArrayView(v) || isBuffer(v); + + function noop() {} + + for (const method of Reflect.ownKeys(consoleMethods)) Console.prototype[method] = consoleMethods[method]; + + Console.prototype.debug = Console.prototype.log; + Console.prototype.info = Console.prototype.log; + Console.prototype.dirxml = Console.prototype.log; + Console.prototype.error = Console.prototype.warn; + Console.prototype.groupCollapsed = Console.prototype.group; + + return Console; +} |