diff options
-rw-r--r-- | cli/tests/node_compat/config.json | 5 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-console-group.js | 2 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-console-table.js | 2 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-console-tty-colors.js | 102 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-readline-position.js | 43 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js | 108 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-util-format.js | 35 | ||||
-rw-r--r-- | cli/tests/node_compat/test/parallel/test-util-inspect.js | 83 | ||||
-rw-r--r-- | cli/tests/unit_node/util_test.ts | 2 | ||||
-rw-r--r-- | ext/console/02_console.js | 13 | ||||
-rw-r--r-- | ext/node/polyfills/internal/event_target.mjs | 4 | ||||
-rw-r--r-- | ext/node/polyfills/internal/util/inspect.mjs | 2063 | ||||
-rw-r--r-- | tools/node_compat/TODO.md | 5 |
13 files changed, 2321 insertions, 146 deletions
diff --git a/cli/tests/node_compat/config.json b/cli/tests/node_compat/config.json index 43d9862b3..cdfbf4677 100644 --- a/cli/tests/node_compat/config.json +++ b/cli/tests/node_compat/config.json @@ -42,9 +42,7 @@ "test-child-process-stdio-inherit.js", "test-child-process-stdout-flush-exit.js", "test-child-process-stdout-flush.js", - "test-console-group.js", "test-console-instance.js", - "test-console-table.js", "test-crypto-hmac.js", "test-dgram-custom-lookup.js", "test-dgram-ipv6only.js", @@ -229,6 +227,7 @@ "test-console-no-swallow-stack-overflow.js", "test-console-sync-write-error.js", "test-console-table.js", + "test-console-tty-colors.js", "test-crypto-hmac.js", "test-crypto-prime.js", "test-dgram-close-during-bind.js", @@ -423,6 +422,7 @@ "test-readline-emit-keypress-events.js", "test-readline-interface-escapecodetimeout.js", "test-readline-keys.js", + "test-readline-position.js", "test-readline-reopen.js", "test-readline-set-raw-mode.js", "test-readline-undefined-columns.js", @@ -556,6 +556,7 @@ "test-stream2-push.js", "test-stream2-read-sync-stack.js", "test-stream2-readable-empty-buffer-no-eof.js", + "test-stream2-readable-from-list.js", "test-stream2-readable-legacy-drain.js", "test-stream2-readable-non-empty-end.js", "test-stream2-readable-wrap-destroy.js", diff --git a/cli/tests/node_compat/test/parallel/test-console-group.js b/cli/tests/node_compat/test/parallel/test-console-group.js index 6e49734eb..257317214 100644 --- a/cli/tests/node_compat/test/parallel/test-console-group.js +++ b/cli/tests/node_compat/test/parallel/test-console-group.js @@ -127,7 +127,6 @@ function teardown() { } // Check that multiline strings and object output are indented properly. -/* TODO(kt3k): Enable this { setup(); const expectedOut = 'not indented\n' + @@ -155,7 +154,6 @@ function teardown() { assert.strictEqual(stderr, expectedErr); teardown(); } -*/ // Check that the kGroupIndent symbol property is not enumerable { diff --git a/cli/tests/node_compat/test/parallel/test-console-table.js b/cli/tests/node_compat/test/parallel/test-console-table.js index 4e176e8cd..3aa34d7c1 100644 --- a/cli/tests/node_compat/test/parallel/test-console-table.js +++ b/cli/tests/node_compat/test/parallel/test-console-table.js @@ -175,7 +175,6 @@ test({ a: { a: 1, b: 2, c: 3 } }, ` └─────────┴───┴───┴───┘ `); -/* TODO(kt3k): Enable this test({ a: { a: { a: 1, b: 2, c: 3 } } }, ` ┌─────────┬──────────┐ │ (index) │ a │ @@ -183,7 +182,6 @@ test({ a: { a: { a: 1, b: 2, c: 3 } } }, ` │ a │ [Object] │ └─────────┴──────────┘ `); -*/ test({ a: [1, 2] }, ` ┌─────────┬───┬───┐ diff --git a/cli/tests/node_compat/test/parallel/test-console-tty-colors.js b/cli/tests/node_compat/test/parallel/test-console-tty-colors.js new file mode 100644 index 000000000..9676529ba --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-console-tty-colors.js @@ -0,0 +1,102 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const util = require('util'); +const { Writable } = require('stream'); +const { Console } = require('console'); + +function check(isTTY, colorMode, expectedColorMode, inspectOptions) { + const items = [ + 1, + { a: 2 }, + [ 'foo' ], + { '\\a': '\\bar' }, + ]; + + let i = 0; + const stream = new Writable({ + write: common.mustCall((chunk, enc, cb) => { + assert.strictEqual(chunk.trim(), + util.inspect(items[i++], { + colors: expectedColorMode, + ...inspectOptions + })); + cb(); + }, items.length), + decodeStrings: false + }); + stream.isTTY = isTTY; + + // Set ignoreErrors to `false` here so that we see assertion failures + // from the `write()` call happen. + const testConsole = new Console({ + stdout: stream, + ignoreErrors: false, + colorMode, + inspectOptions + }); + for (const item of items) { + testConsole.log(item); + } +} + +check(true, 'auto', true); +check(false, 'auto', false); +check(false, undefined, true, { colors: true, compact: false }); +check(true, 'auto', true, { compact: false }); +check(true, undefined, false, { colors: false }); +check(true, true, true); +check(false, true, true); +check(true, false, false); +check(false, false, false); + +// Check invalid options. +{ + const stream = new Writable({ + write: common.mustNotCall() + }); + + [0, 'true', null, {}, [], () => {}].forEach((colorMode) => { + const received = util.inspect(colorMode); + assert.throws( + () => { + new Console({ + stdout: stream, + ignoreErrors: false, + colorMode: colorMode + }); + }, + { + message: `The argument 'colorMode' is invalid. Received ${received}`, + code: 'ERR_INVALID_ARG_VALUE' + } + ); + }); + + [true, false, 'auto'].forEach((colorMode) => { + assert.throws( + () => { + new Console({ + stdout: stream, + ignoreErrors: false, + colorMode: colorMode, + inspectOptions: { + colors: false + } + }); + }, + { + message: 'Option "options.inspectOptions.color" cannot be used in ' + + 'combination with option "colorMode"', + code: 'ERR_INCOMPATIBLE_OPTION_PAIR' + } + ); + }); +} diff --git a/cli/tests/node_compat/test/parallel/test-readline-position.js b/cli/tests/node_compat/test/parallel/test-readline-position.js new file mode 100644 index 000000000..0ffdf4b18 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-readline-position.js @@ -0,0 +1,43 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const { PassThrough } = require('stream'); +const readline = require('readline'); +const assert = require('assert'); + +const ctrlU = { ctrl: true, name: 'u' }; + +common.skipIfDumbTerminal(); + +{ + const input = new PassThrough(); + const rl = readline.createInterface({ + terminal: true, + input: input, + prompt: '' + }); + + const tests = [ + [1, 'a'], + [2, 'ab'], + [2, '丁'], + [0, '\u0301'], // COMBINING ACUTE ACCENT + [1, 'a\u0301'], // á + [0, '\u20DD'], // COMBINING ENCLOSING CIRCLE + [2, 'a\u20DDb'], // a⃝b + [0, '\u200E'], // LEFT-TO-RIGHT MARK + ]; + + for (const [cursor, string] of tests) { + rl.write(string); + assert.strictEqual(rl.getCursorPos().cols, cursor); + rl.write(null, ctrlU); + } +} diff --git a/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js b/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js new file mode 100644 index 000000000..72c3b15aa --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-stream2-readable-from-list.js @@ -0,0 +1,108 @@ +// deno-fmt-ignore-file +// deno-lint-ignore-file + +// Copyright Joyent and Node contributors. All rights reserved. MIT license. +// Taken from Node 18.12.1 +// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually + +// 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. + +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const fromList = require('stream').Readable._fromList; +const BufferList = require('internal/streams/buffer_list'); +const util = require('util'); + +function bufferListFromArray(arr) { + const bl = new BufferList(); + for (let i = 0; i < arr.length; ++i) + bl.push(arr[i]); + return bl; +} + +{ + // Verify behavior with buffers + let list = [ Buffer.from('foog'), + Buffer.from('bark'), + Buffer.from('bazy'), + Buffer.from('kuel') ]; + list = bufferListFromArray(list); + + assert.strictEqual( + util.inspect([ list ], { compact: false }), + `[ + BufferList { + head: [Object], + tail: [Object], + length: 4 + } +]`); + + // Read more than the first element. + let ret = fromList(6, { buffer: list, length: 16 }); + assert.strictEqual(ret.toString(), 'foogba'); + + // Read exactly the first element. + ret = fromList(2, { buffer: list, length: 10 }); + assert.strictEqual(ret.toString(), 'rk'); + + // Read less than the first element. + ret = fromList(2, { buffer: list, length: 8 }); + assert.strictEqual(ret.toString(), 'ba'); + + // Read more than we have. + ret = fromList(100, { buffer: list, length: 6 }); + assert.strictEqual(ret.toString(), 'zykuel'); + + // all consumed. + assert.deepStrictEqual(list, new BufferList()); +} + +{ + // Verify behavior with strings + let list = [ 'foog', + 'bark', + 'bazy', + 'kuel' ]; + list = bufferListFromArray(list); + + // Read more than the first element. + let ret = fromList(6, { buffer: list, length: 16, decoder: true }); + assert.strictEqual(ret, 'foogba'); + + // Read exactly the first element. + ret = fromList(2, { buffer: list, length: 10, decoder: true }); + assert.strictEqual(ret, 'rk'); + + // Read less than the first element. + ret = fromList(2, { buffer: list, length: 8, decoder: true }); + assert.strictEqual(ret, 'ba'); + + // Read more than we have. + ret = fromList(100, { buffer: list, length: 6, decoder: true }); + assert.strictEqual(ret, 'zykuel'); + + // all consumed. + assert.deepStrictEqual(list, new BufferList()); +} diff --git a/cli/tests/node_compat/test/parallel/test-util-format.js b/cli/tests/node_compat/test/parallel/test-util-format.js index 3fd24863f..9d474c481 100644 --- a/cli/tests/node_compat/test/parallel/test-util-format.js +++ b/cli/tests/node_compat/test/parallel/test-util-format.js @@ -50,13 +50,11 @@ assert.strictEqual(util.format('foo', 'bar', 'baz'), 'foo bar baz'); assert.strictEqual(util.format(symbol), 'Symbol(foo)'); assert.strictEqual(util.format('foo', symbol), 'foo Symbol(foo)'); assert.strictEqual(util.format('%s', symbol), 'Symbol(foo)'); -// TODO(kt3k): Enable this -// assert.strictEqual(util.format('%j', symbol), 'undefined'); +assert.strictEqual(util.format('%j', symbol), 'undefined'); // Number format specifier assert.strictEqual(util.format('%d'), '%d'); assert.strictEqual(util.format('%d', 42.0), '42'); -/* TODO(kt3k): Enable this assert.strictEqual(util.format('%d', 42), '42'); assert.strictEqual(util.format('%d', '42'), '42'); assert.strictEqual(util.format('%d', '42.0'), '42'); @@ -137,7 +135,6 @@ assert.strictEqual(util.format('%f', Infinity), 'Infinity'); assert.strictEqual(util.format('%f', -Infinity), '-Infinity'); assert.strictEqual(util.format('%f %f', 42, 43), '42 43'); assert.strictEqual(util.format('%f %f', 42), '42 %f'); -*/ // String format specifier assert.strictEqual(util.format('%s'), '%s'); @@ -146,16 +143,16 @@ assert.strictEqual(util.format('%s', null), 'null'); assert.strictEqual(util.format('%s', 'foo'), 'foo'); assert.strictEqual(util.format('%s', 42), '42'); assert.strictEqual(util.format('%s', '42'), '42'); -// assert.strictEqual(util.format('%s', -0), '-0'); +assert.strictEqual(util.format('%s', -0), '-0'); assert.strictEqual(util.format('%s', '-0.0'), '-0.0'); assert.strictEqual(util.format('%s %s', 42, 43), '42 43'); assert.strictEqual(util.format('%s %s', 42), '42 %s'); -// assert.strictEqual(util.format('%s', 42n), '42n'); +assert.strictEqual(util.format('%s', 42n), '42n'); assert.strictEqual(util.format('%s', Symbol('foo')), 'Symbol(foo)'); assert.strictEqual(util.format('%s', true), 'true'); -// assert.strictEqual(util.format('%s', { a: [1, 2, 3] }), '{ a: [Array] }'); +assert.strictEqual(util.format('%s', { a: [1, 2, 3] }), '{ a: [Array] }'); assert.strictEqual(util.format('%s', { toString() { return 'Foo'; } }), 'Foo'); -// assert.strictEqual(util.format('%s', { toString: 5 }), '{ toString: 5 }'); +assert.strictEqual(util.format('%s', { toString: 5 }), '{ toString: 5 }'); assert.strictEqual(util.format('%s', () => 5), '() => 5'); assert.strictEqual(util.format('%s', Infinity), 'Infinity'); assert.strictEqual(util.format('%s', -Infinity), '-Infinity'); @@ -243,10 +240,10 @@ assert.strictEqual(util.format('%s', -Infinity), '-Infinity'); // JSON format specifier assert.strictEqual(util.format('%j'), '%j'); -// assert.strictEqual(util.format('%j', 42), '42'); -// assert.strictEqual(util.format('%j', '42'), '"42"'); -// assert.strictEqual(util.format('%j %j', 42, 43), '42 43'); -// assert.strictEqual(util.format('%j %j', 42), '42 %j'); +assert.strictEqual(util.format('%j', 42), '42'); +assert.strictEqual(util.format('%j', '42'), '"42"'); +assert.strictEqual(util.format('%j %j', 42, 43), '42 43'); +assert.strictEqual(util.format('%j %j', 42), '42 %j'); // Object format specifier const obj = { @@ -268,8 +265,7 @@ const nestedObj2 = { }; assert.strictEqual(util.format('%o'), '%o'); assert.strictEqual(util.format('%o', 42), '42'); -// assert.strictEqual(util.format('%o', 'foo'), '\'foo\''); -/* +assert.strictEqual(util.format('%o', 'foo'), '\'foo\''); assert.strictEqual( util.format('%o', obj), '{\n' + @@ -340,11 +336,9 @@ assert.strictEqual( ' [prototype]: { [constructor]: [Circular *1] }\n' + ' }\n' + '} %o'); -*/ assert.strictEqual(util.format('%O'), '%O'); assert.strictEqual(util.format('%O', 42), '42'); -/* TODO(kt3k): Enable this assert.strictEqual(util.format('%O', 'foo'), '\'foo\''); assert.strictEqual( util.format('%O', obj), @@ -359,7 +353,6 @@ assert.strictEqual( assert.strictEqual( util.format('%O %O', obj), '{ foo: \'bar\', foobar: 1, func: [Function: func] } %O'); -*/ // Various format specifiers assert.strictEqual(util.format('%%s%s', 'foo'), '%sfoo'); @@ -382,7 +375,6 @@ assert.strictEqual(util.format('%i:%i'), '%i:%i'); assert.strictEqual(util.format('%f:%f', 12, 30), '12:30'); assert.strictEqual(util.format('%f:%f', 12), '12:%f'); assert.strictEqual(util.format('%f:%f'), '%f:%f'); -/* TODO(kt3k): Enable this assert.strictEqual(util.format('o: %j, a: %j', {}, []), 'o: {}, a: []'); assert.strictEqual(util.format('o: %j, a: %j', {}), 'o: {}, a: %j'); assert.strictEqual(util.format('o: %j, a: %j'), 'o: %j, a: %j'); @@ -396,7 +388,6 @@ assert.strictEqual(util.format('a% b', 'x'), 'a% b x'); assert.strictEqual(util.format('percent: %d%, fraction: %d', 10, 0.1), 'percent: 10%, fraction: 0.1'); assert.strictEqual(util.format('abc%', 1), 'abc% 1'); -*/ // Additional arguments after format specifiers assert.strictEqual(util.format('%i', 1, 'number'), '1 number'); @@ -408,7 +399,6 @@ assert.strictEqual(util.format('%cab'), '%cab'); assert.strictEqual(util.format('%cab', 'color: blue'), 'ab'); assert.strictEqual(util.format('%cab', 'color: blue', 'c'), 'ab c'); -/* TODO(kt3k): Enable this { const o = {}; o.o = o; @@ -424,7 +414,6 @@ assert.strictEqual(util.format('%cab', 'color: blue', 'c'), 'ab c'); assert.throws(() => util.format('%j', o), /^Error: Not a circular object but still not serializable$/); } -*/ // Errors const err = new Error('foo'); @@ -442,7 +431,6 @@ class CustomError extends Error { const customError = new CustomError('bar'); assert.strictEqual(util.format(customError), customError.stack); // Doesn't capture stack trace -/* TODO(kt3k): Enable this function BadCustomError(msg) { Error.call(this); Object.defineProperty(this, 'message', @@ -454,7 +442,6 @@ Object.setPrototypeOf(BadCustomError.prototype, Error.prototype); Object.setPrototypeOf(BadCustomError, Error); assert.strictEqual(util.format(new BadCustomError('foo')), '[BadCustomError: foo]'); -*/ // The format of arguments should not depend on type of the first argument assert.strictEqual(util.format('1', '1'), '1 1'); @@ -491,7 +478,6 @@ assert.strictEqual( // 'SharedArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }' // ); -/* TODO(kt3k): Enable this assert.strictEqual( util.formatWithOptions( { colors: true, compact: 3 }, @@ -499,7 +485,6 @@ assert.strictEqual( ), '[ 1, [Object] ]' ); -*/ [ undefined, diff --git a/cli/tests/node_compat/test/parallel/test-util-inspect.js b/cli/tests/node_compat/test/parallel/test-util-inspect.js index f5382844a..fd3243ec5 100644 --- a/cli/tests/node_compat/test/parallel/test-util-inspect.js +++ b/cli/tests/node_compat/test/parallel/test-util-inspect.js @@ -110,13 +110,11 @@ assert.strictEqual( ); assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString()); assert.strictEqual(util.inspect('\n\x01'), "'\\n\\x01'"); -/* TODO(kt3k): Enable this assert.strictEqual( util.inspect(`${Array(75).fill(1)}'\n\x1d\n\x03\x85\x7f\x7e\x9f\xa0`), // eslint-disable-next-line no-irregular-whitespace `"${Array(75).fill(1)}'\\n" +\n '\\x1D\\n' +\n '\\x03\\x85\\x7F~\\x9F '` ); -*/ assert.strictEqual(util.inspect([]), '[]'); assert.strictEqual(util.inspect(Object.create([])), 'Array {}'); assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]'); @@ -137,21 +135,17 @@ assert.strictEqual(util.inspect({ 'a': {} }), '{ a: {} }'); assert.strictEqual(util.inspect({ 'a': { 'b': 2 } }), '{ a: { b: 2 } }'); assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }), '{ a: { b: { c: [Object] } } }'); -/* TODO(kt3k): Enable this assert.strictEqual( util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }, false, null), '{\n a: { b: { c: { d: 2 } } }\n}'); -*/ // TODO(wafuwafu13): Fix // assert.strictEqual(util.inspect([1, 2, 3], true), '[ 1, 2, 3, [length]: 3 ]'); -/* TODO(kt3k): Enable this assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 0), '{ a: [Object] }'); assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1), '{ a: { b: [Object] } }'); assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1), '{ a: { b: [Array] } }'); -*/ // TODO(wafuwafu13): Fix // assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array(0) []'); // assert(inspect(new Uint8Array(0), { showHidden: true }).includes('[buffer]')); @@ -187,12 +181,10 @@ assert.match( util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true), /Object/ ); -/* TODO(kt3k): Enable this assert.doesNotMatch( util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true), /Object/ ); -*/ // TODO(wafuwafu13): Fix // { @@ -368,7 +360,6 @@ assert.doesNotMatch( // assert.strictEqual(inspect(brokenLength), 'Float32Array(2) [ 0n, 0n ]'); // } -/* TODO(kt3k): Enable this assert.strictEqual( util.inspect(Object.create({}, { visible: { value: 1, enumerable: true }, @@ -392,7 +383,6 @@ assert.strictEqual( })), "[Object: null prototype] { name: 'Tim' }" ); -*/ // Dynamic properties. { @@ -404,12 +394,10 @@ assert.strictEqual( util.inspect({ get readwrite() { return 1; }, set readwrite(val) {} }), '{ readwrite: [Getter/Setter] }'); - /* TODO(kt3k): Enable this assert.strictEqual( // eslint-disable-next-line accessor-pairs util.inspect({ set writeonly(val) {} }), '{ writeonly: [Setter] }'); - */ const value = {}; value.a = value; @@ -419,12 +407,10 @@ assert.strictEqual( return null; } }; - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(getterFn, { getters: true }), '{ one: [Getter: null] }' ); - */ } // TODO(wafuwafu13): Fix @@ -459,12 +445,10 @@ assert.strictEqual( CustomArray.prototype.foo = true; const arr = new CustomArray(50); arr[49] = 'I win'; - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(arr), "CustomArray(50) [ <49 empty items>, 'I win' ]" ); - */ // TODO(wafuwafu13): Fix // assert.strictEqual( // util.inspect(arr, { showHidden: true }), @@ -570,19 +554,15 @@ assert.strictEqual( { const value = /123/ig; value.aprop = 42; - /* TODO(kt3k): Enable this assert.strictEqual(util.inspect(value), '/123/gi { aprop: 42 }'); - */ } // Dates with properties. { const value = new Date('Sun, 14 Feb 2010 11:48:40 GMT'); value.aprop = 42; - /* TODO(kt3k): Enable this assert.strictEqual(util.inspect(value), '2010-02-14T11:48:40.000Z { aprop: 42 }'); - */ } // TODO(wafuwafu13): Implement 'vm' @@ -621,7 +601,6 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); "[ 'foo', <1 empty item>, 'baz', 'bar', <96 empty items>, 'qux' ]" ); delete a[3]; - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(a, { maxArrayLength: 4 }), "[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]" @@ -630,7 +609,6 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); assert.strictEqual(util.inspect(a, { maxArrayLength: 2 }), "[ 'foo', <1 empty item>, ... 99 more items ]"); - */ } // TODO(wafuwafu13): Implement `previewEntries` @@ -680,7 +658,6 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); set: function() {} } }); - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(getter, true), '[Object: null prototype] { [a]: [Getter] }' @@ -693,7 +670,6 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); util.inspect(getterAndSetter, true), '[Object: null prototype] { [c]: [Getter/Setter] }' ); - */ } // Exceptions should print the error message, not '{}'. @@ -716,10 +692,8 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); const ex = util.inspect(new Error('FAIL'), true); assert(ex.includes('Error: FAIL')); - /* TODO(kt3k): Enable this assert(ex.includes('[stack]')); assert(ex.includes('[message]')); - */ } { @@ -727,9 +701,7 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); Error.stackTraceLimit = 0; const err = new Error('foo'); const err2 = new Error('foo\nbar'); - /* TODO(kt3k): Enable this assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]'); - */ assert(err.stack); delete err.stack; assert(!err.stack); @@ -809,12 +781,10 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); } Object.setPrototypeOf(BadCustomError.prototype, Error.prototype); Object.setPrototypeOf(BadCustomError, Error); - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(new BadCustomError('foo')), '[BadCustomError: foo]' ); - */ } // TODO(wafuwafu13): Fix @@ -848,9 +818,7 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); // }); // https://github.com/nodejs/node-v0.x-archive/issues/1941 -/* TODO(kt3k): Enable this assert.strictEqual(util.inspect(Object.create(Date.prototype)), 'Date {}'); -*/ // https://github.com/nodejs/node-v0.x-archive/issues/1944 { @@ -913,7 +881,6 @@ assert.strictEqual(util.inspect(Object.create(Date.prototype)), 'Date {}'); // } // Test util.inspect.styles and util.inspect.colors. -/* TODO(kt3k): Enable this { function testColorStyle(style, input, implicit) { const colorName = util.inspect.styles[style]; @@ -939,7 +906,6 @@ assert.strictEqual(util.inspect(Object.create(Date.prototype)), 'Date {}'); testColorStyle('date', new Date()); testColorStyle('regexp', /regexp/); } -*/ // An object with "hasOwnProperty" overwritten should not throw. util.inspect({ hasOwnProperty: null }); @@ -953,12 +919,10 @@ util.inspect({ hasOwnProperty: null }); util.inspect(subject, { showHidden: false }).includes('hidden'), false ); - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(subject, { showHidden: true }).includes('hidden'), true ); - */ assert.strictEqual( util.inspect(subject, { colors: false }).includes('\u001b[32m'), false @@ -971,7 +935,6 @@ util.inspect({ hasOwnProperty: null }); util.inspect(subject, { depth: 2 }).includes('c: [Object]'), true ); - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(subject, { depth: 0 }).includes('a: [Object]'), true @@ -984,14 +947,12 @@ util.inspect({ hasOwnProperty: null }); util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'), true ); - */ } { // "customInspect" option can enable/disable calling [util.inspect.custom](). const subject = { [util.inspect.custom]: () => 123 }; - /* TODO(kt3k): Enable this assert.strictEqual( util.inspect(subject, { customInspect: true }).includes('123'), true @@ -1004,7 +965,6 @@ util.inspect({ hasOwnProperty: null }); util.inspect(subject, { customInspect: false }).includes('123'), false ); - */ // TODO(wafuwafu13): Fix // assert.strictEqual( // util.inspect(subject, { customInspect: false }).includes('inspect'), @@ -1014,7 +974,6 @@ util.inspect({ hasOwnProperty: null }); // A custom [util.inspect.custom]() should be able to return other Objects. subject[util.inspect.custom] = () => ({ foo: 'bar' }); - /* TODO(kt3k): Enable this assert.strictEqual(util.inspect(subject), "{ foo: 'bar' }"); subject[util.inspect.custom] = common.mustCall((depth, opts) => { @@ -1038,7 +997,6 @@ util.inspect({ hasOwnProperty: null }); }); util.inspect(subject); - */ // util.inspect.custom is a shared symbol which can be accessed as // Symbol.for("nodejs.util.inspect.custom"). @@ -1046,9 +1004,7 @@ util.inspect({ hasOwnProperty: null }); subject[inspect] = () => ({ baz: 'quux' }); - /* TODO(kt3k): Enable this assert.strictEqual(util.inspect(subject), '{ baz: \'quux\' }'); - */ subject[inspect] = (depth, opts) => { assert.strictEqual(opts.customInspectOptions, true); @@ -1059,7 +1015,6 @@ util.inspect({ hasOwnProperty: null }); util.inspect(subject, { customInspectOptions: true, seen: null }); } -/* TODO(kt3k): Enable this { const subject = { [util.inspect.custom]: common.mustCall((depth, opts) => { assert.strictEqual(depth, null); @@ -1067,7 +1022,6 @@ util.inspect({ hasOwnProperty: null }); }) }; util.inspect(subject, { depth: null, compact: true }); } -*/ // TODO(wafuwafu13): Fix // { @@ -1081,12 +1035,10 @@ util.inspect({ hasOwnProperty: null }); // } // Verify that it's possible to use the stylize function to manipulate input. -/* TODO(kt3k): Enable this assert.strictEqual( util.inspect([1, 2, 3], { stylize() { return 'x'; } }), '[ x, x, x ]' ); -*/ // Using `util.inspect` with "colors" option should produce as many lines as // without it. @@ -1114,13 +1066,11 @@ assert.strictEqual( } // Test boxed primitives output the correct values. -/* TODO(kt3k): Enable this assert.strictEqual(util.inspect(new String('test')), "[String: 'test']"); assert.strictEqual( util.inspect(new String('test'), { colors: true }), "\u001b[32m[String: 'test']\u001b[39m" ); -*/ assert.strictEqual( util.inspect(Object(Symbol('test'))), '[Symbol: Symbol(test)]' @@ -1146,7 +1096,6 @@ assert.strictEqual(util.inspect(new Number(-1.1)), '[Number: -1.1]'); assert.strictEqual(util.inspect(new Number(13.37)), '[Number: 13.37]'); // Test boxed primitives with own properties. -/* TODO(kt3k): Enable this { const str = new String('baz'); str.foo = 'bar'; @@ -1275,7 +1224,6 @@ if (typeof Symbol !== 'undefined') { '}' ); } -*/ // TODO(wafuwafu13): Fix // // Test Promise. @@ -1357,7 +1305,6 @@ if (typeof Symbol !== 'undefined') { // Minimal inspection should still return as much information as possible about // the constructor and Symbol.toStringTag. -/* TODO(kt3k): Enable this { class Foo { get [Symbol.toStringTag]() { @@ -1502,19 +1449,16 @@ if (typeof Symbol !== 'undefined') { }); assert.strictEqual(util.inspect(x), '{ constructor: [Getter] }'); } -*/ { const x = new function() {}; // eslint-disable-line new-parens assert.strictEqual(util.inspect(x), '{}'); } -/* TODO(kt3k): Enable this { const x = Object.create(null); assert.strictEqual(util.inspect(x), '[Object: null prototype] {}'); } -*/ // TODO(wafuwafu13): Fix // { @@ -1539,7 +1483,6 @@ if (typeof Symbol !== 'undefined') { // '[ ... 101 more items ]'); // } -/* TODO(kt3k): Enable this { const x = Array(101); assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }), @@ -1633,7 +1576,6 @@ if (typeof Symbol !== 'undefined') { } ); } -*/ util.inspect(process); @@ -1647,7 +1589,6 @@ util.inspect(process); // ); // } -/* TODO(kt3k): Enable this { // @@toStringTag const obj = { [Symbol.toStringTag]: 'a' }; @@ -1822,7 +1763,6 @@ util.inspect(process); expect = "{\n a: '12 45 78 01 34 67 90 23'\n}"; assert.strictEqual(out, expect); } -*/ // TODO(wafuwafu13): Fix // // Check compact indentation. @@ -2022,7 +1962,6 @@ util.inspect(process); // assert(obj && arr); // } -/* TODO(kt3k): Enable this { // Test argument objects. const args = (function() { return arguments; })('a'); assert.strictEqual(util.inspect(args), "[Arguments] { '0': 'a' }"); @@ -2047,15 +1986,12 @@ util.inspect(process); // assert(longList.includes('[Object: Inspection interrupted ' + // 'prematurely. Maximum call stack size exceeded.]')); } -*/ // Do not escape single quotes if no double quote or backtick is present. assert.strictEqual(util.inspect("'"), '"\'"'); assert.strictEqual(util.inspect('"\''), '`"\'`'); // eslint-disable-next-line no-template-curly-in-string -/* TODO(kt3k): Enable this assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); -*/ // TODO(wafuwafu13): Fix // // Errors should visualize as much information as possible. @@ -2179,7 +2115,6 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); // }); // "class" properties should not be detected as "class". -/* TODO(kt3k): Enable this { // eslint-disable-next-line space-before-function-paren let obj = { class () {} }; @@ -2210,7 +2145,6 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); '[Function: class Foo {}]' ); } -*/ // TODO(wafuwafu13): Fix // // Verify that throwing in valueOf and toString still produces nice results. @@ -2344,7 +2278,6 @@ assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]'); // inspect(new BigUint64Array([0n])), 'BigUint64Array(1) [ 0n ]'); // Verify non-enumerable keys get escaped. -/* TODO(kt3k): Enable this { const obj = {}; Object.defineProperty(obj, 'Non\nenumerable\tkey', { value: true }); @@ -2415,7 +2348,6 @@ assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]'); assert.strictEqual(inspect('foobar', { colors: true }), "'foobar'"); inspect.styles.string = stringStyle; } -*/ assert.strictEqual( inspect([1, 3, 2], { sorted: true }), @@ -2425,7 +2357,6 @@ assert.strictEqual( inspect({ c: 3, a: 1, b: 2 }, { sorted: true }), '{ a: 1, b: 2, c: 3 }' ); -/* TODO(kt3k): Enable this assert.strictEqual( inspect( { a200: 4, a100: 1, a102: 3, a101: 2 }, @@ -2433,7 +2364,6 @@ assert.strictEqual( ), '{ a200: 4, a102: 3, a101: 2, a100: 1 }' ); -*/ // TODO(wafuwafu13): Fix // // Non-indices array properties are sorted as well. @@ -2535,8 +2465,8 @@ assert.strictEqual( // ); // } +// TODO(wafuwafu13): Fix // Check the getter option. -/* TODO(kt3k): Enable this { let foo = 1; const get = { get foo() { return foo; } }; @@ -2568,7 +2498,6 @@ assert.strictEqual( // '{\n foo: [Getter/Setter] Set(3) { [ [Object], 2, {} ], ' + // "'foobar', { x: 1 } },\n inc: [Getter: NaN]\n}"); } -*/ // Check compact number mode. { @@ -2981,12 +2910,10 @@ assert.strictEqual( const bar = new Bar(); - /* TODO(kt3k): Enable this assert.strictEqual( inspect(bar), 'Bar(0) [Map] { prop: true, prop2: true, abc: true }' ); - */ // TODO(wafuwafu13): Fix // assert.strictEqual( // inspect(bar, { showHidden: true, getters: true, colors: false }), @@ -3050,7 +2977,6 @@ assert.strictEqual( // Check that prototypes with a null prototype are inspectable. // Regression test for https://github.com/nodejs/node/issues/35730 -/* TODO(kt3k): Enable this { function Func() {} Func.prototype = null; @@ -3059,7 +2985,6 @@ assert.strictEqual( assert.strictEqual(util.inspect(object), '{ constructor: [Function: Func] }'); } -*/ // Test changing util.inspect.colors colors and aliases. { @@ -3096,7 +3021,6 @@ assert.strictEqual( // } // Truncate output for Primitives with 1 character left -/* TODO(kt3k): Enable this { assert.strictEqual(util.inspect('bl', { maxStringLength: 1 }), "'b'... 1 more character"); @@ -3111,7 +3035,6 @@ assert.strictEqual( ); assert.match(util.inspect(x, { maxStringLength: null }), /a'$/); } -*/ // TODO(wafuwafu13): Implement 'vm' // { @@ -3197,7 +3120,6 @@ assert.strictEqual( // assert(fullObjectGraph(global).has(Function.prototype)); // } -/* TODO(kt3k): Enable this { // Confirm that own constructor value displays correctly. @@ -3217,7 +3139,6 @@ assert.strictEqual( '}' ); } -*/ // TODO(wafuwafu13): Fix TypeError: main.hasOwnProperty is not a function // { @@ -3271,11 +3192,9 @@ assert.strictEqual( ); } -/* TODO(kt3k): Enable this { assert.strictEqual( util.inspect({ ['__proto__']: { a: 1 } }), "{ ['__proto__']: { a: 1 } }" ); } -*/ diff --git a/cli/tests/unit_node/util_test.ts b/cli/tests/unit_node/util_test.ts index 44b20299e..81794c856 100644 --- a/cli/tests/unit_node/util_test.ts +++ b/cli/tests/unit_node/util_test.ts @@ -12,7 +12,7 @@ import * as util from "node:util"; Deno.test({ name: "[util] format", fn() { - assertEquals(util.format("%o", [10, 11]), "[ 10, 11 ]"); + assertEquals(util.format("%o", [10, 11]), "[ 10, 11, [length]: 2 ]"); }, }); diff --git a/ext/console/02_console.js b/ext/console/02_console.js index e93272de1..5547dd230 100644 --- a/ext/console/02_console.js +++ b/ext/console/02_console.js @@ -204,7 +204,7 @@ function isFullWidthCodePoint(code) { ); } -export function getStringWidth(str) { +function getStringWidth(str) { str = StringPrototypeNormalize(colors.stripColor(str), "NFC"); let width = 0; @@ -1339,16 +1339,6 @@ function inspectObject(value, inspectOptions, proxyDetails) { ) { return String(value[customInspect](inspect, inspectOptions)); } - if ( - ReflectHas(value, nodeCustomInspect) && - typeof value[nodeCustomInspect] === "function" - ) { - // TODO(kt3k): The last inspect needs to be util.inspect of Node.js. - // We need to move the implementation of util.inspect to this file. - return String( - value[nodeCustomInspect](inspectOptions.depth, inspectOptions, inspect), - ); - } // This non-unique symbol is used to support op_crates, ie. // in extensions/web we don't want to depend on public // Symbol.for("Deno.customInspect") symbol defined in the public API. @@ -2325,7 +2315,6 @@ class Console { } const customInspect = SymbolFor("Deno.customInspect"); -const nodeCustomInspect = SymbolFor("nodejs.util.inspect.custom"); function inspect( value, diff --git a/ext/node/polyfills/internal/event_target.mjs b/ext/node/polyfills/internal/event_target.mjs index cad1ec5a4..cb3f356d0 100644 --- a/ext/node/polyfills/internal/event_target.mjs +++ b/ext/node/polyfills/internal/event_target.mjs @@ -19,8 +19,6 @@ import { } from "ext:deno_node/internal/util.mjs"; import { inspect } from "ext:deno_node/util.ts"; -const { ObjectAssign } = globalThis.__bootstrap.primordials; - const kIsEventTarget = Symbol.for("nodejs.event_target"); const kIsNodeEventTarget = Symbol("kIsNodeEventTarget"); @@ -97,7 +95,7 @@ class Event extends WebEvent { return name; } - const opts = ObjectAssign({}, options, { + const opts = Object.assign({}, options, { depth: NumberIsInteger(options.depth) ? options.depth - 1 : options.depth, }); diff --git a/ext/node/polyfills/internal/util/inspect.mjs b/ext/node/polyfills/internal/util/inspect.mjs index c897b5d06..d8409f198 100644 --- a/ext/node/polyfills/internal/util/inspect.mjs +++ b/ext/node/polyfills/internal/util/inspect.mjs @@ -20,38 +20,195 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -import { inspect as denoInspect, getStringWidth as denoGetStringWidth, inspectArgs } from "ext:deno_console/02_console.js"; +import * as types from "ext:deno_node/internal/util/types.ts"; import { validateObject, validateString } from "ext:deno_node/internal/validators.mjs"; import { codes } from "ext:deno_node/internal/error_codes.ts"; +import { + ALL_PROPERTIES, + getOwnNonIndexProperties, + ONLY_ENUMERABLE, +} from "ext:deno_node/internal_binding/util.ts"; + +const kObjectType = 0; +const kArrayType = 1; +const kArrayExtrasType = 2; + +const kMinLineLength = 16; + +// Constants to map the iterator state. +const kWeak = 0; +const kIterator = 1; +const kMapEntries = 2; + +const kPending = 0; +const kRejected = 2; + +// Escaped control characters (plus the single quote and the backslash). Use +// empty strings to fill up unused entries. +// deno-fmt-ignore +const meta = [ + '\\x00', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\x07', // x07 + '\\b', '\\t', '\\n', '\\x0B', '\\f', '\\r', '\\x0E', '\\x0F', // x0F + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', // x17 + '\\x18', '\\x19', '\\x1A', '\\x1B', '\\x1C', '\\x1D', '\\x1E', '\\x1F', // x1F + '', '', '', '', '', '', '', "\\'", '', '', '', '', '', '', '', '', // x2F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x3F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x4F + '', '', '', '', '', '', '', '', '', '', '', '', '\\\\', '', '', '', // x5F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', // x6F + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '\\x7F', // x7F + '\\x80', '\\x81', '\\x82', '\\x83', '\\x84', '\\x85', '\\x86', '\\x87', // x87 + '\\x88', '\\x89', '\\x8A', '\\x8B', '\\x8C', '\\x8D', '\\x8E', '\\x8F', // x8F + '\\x90', '\\x91', '\\x92', '\\x93', '\\x94', '\\x95', '\\x96', '\\x97', // x97 + '\\x98', '\\x99', '\\x9A', '\\x9B', '\\x9C', '\\x9D', '\\x9E', '\\x9F', // x9F +]; + +// https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot +const isUndetectableObject = (v) => typeof v === "undefined" && v !== undefined; + +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c\x7f-\x9f]/g; +// deno-lint-ignore no-control-regex +const strEscapeSequencesRegExpSingle = /[\x00-\x1f\x5c\x7f-\x9f]/; +// deno-lint-ignore no-control-regex +const strEscapeSequencesReplacerSingle = /[\x00-\x1f\x5c\x7f-\x9f]/g; + +const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/; +const numberRegExp = /^(0|[1-9][0-9]*)$/; +const nodeModulesRegExp = /[/\\]node_modules[/\\](.+?)(?=[/\\])/g; + +const classRegExp = /^(\s+[^(]*?)\s*{/; +// eslint-disable-next-line node-core/no-unescaped-regexp-dot +const stripCommentsRegExp = /(\/\/.*?\n)|(\/\*(.|\n)*?\*\/)/g; + const inspectDefaultOptions = { showHidden: false, - depth: 3, + depth: 2, colors: false, customInspect: true, showProxy: false, maxArrayLength: 100, maxStringLength: 10000, breakLength: 80, - compact: true, + compact: 3, sorted: false, getters: false, }; +function getUserOptions(ctx, isCrossContext) { + const ret = { + stylize: ctx.stylize, + showHidden: ctx.showHidden, + depth: ctx.depth, + colors: ctx.colors, + customInspect: ctx.customInspect, + showProxy: ctx.showProxy, + maxArrayLength: ctx.maxArrayLength, + maxStringLength: ctx.maxStringLength, + breakLength: ctx.breakLength, + compact: ctx.compact, + sorted: ctx.sorted, + getters: ctx.getters, + ...ctx.userOptions, + }; + + // Typically, the target value will be an instance of `Object`. If that is + // *not* the case, the object may come from another vm.Context, and we want + // to avoid passing it objects from this Context in that case, so we remove + // the prototype from the returned object itself + the `stylize()` function, + // and remove all other non-primitives, including non-primitive user options. + if (isCrossContext) { + Object.setPrototypeOf(ret, null); + for (const key of Object.keys(ret)) { + if ( + (typeof ret[key] === "object" || typeof ret[key] === "function") && + ret[key] !== null + ) { + delete ret[key]; + } + } + ret.stylize = Object.setPrototypeOf((value, flavour) => { + let stylized; + try { + stylized = `${ctx.stylize(value, flavour)}`; + } catch { + // noop + } + + if (typeof stylized !== "string") return value; + // `stylized` is a string as it should be, which is safe to pass along. + return stylized; + }, null); + } + + return ret; +} + /** * Echos the value of any input. Tries to print the value out * in the best way possible given the different types. */ /* Legacy: value, showHidden, depth, colors */ -export function inspect(value, opts = {}) { - if (typeof opts === "boolean") { - opts = { - showHidden: opts, - depth: arguments[2], - colors: arguments[3], +export function inspect(value, opts) { + // Default options + const ctx = { + budget: {}, + indentationLvl: 0, + seen: [], + currentDepth: 0, + stylize: stylizeNoColor, + showHidden: inspectDefaultOptions.showHidden, + depth: inspectDefaultOptions.depth, + colors: inspectDefaultOptions.colors, + customInspect: inspectDefaultOptions.customInspect, + showProxy: inspectDefaultOptions.showProxy, + maxArrayLength: inspectDefaultOptions.maxArrayLength, + maxStringLength: inspectDefaultOptions.maxStringLength, + breakLength: inspectDefaultOptions.breakLength, + compact: inspectDefaultOptions.compact, + sorted: inspectDefaultOptions.sorted, + getters: inspectDefaultOptions.getters, + }; + if (arguments.length > 1) { + // Legacy... + if (arguments.length > 2) { + if (arguments[2] !== undefined) { + ctx.depth = arguments[2]; + } + if (arguments.length > 3 && arguments[3] !== undefined) { + ctx.colors = arguments[3]; + } + } + // Set user-specified options + if (typeof opts === "boolean") { + ctx.showHidden = opts; + } else if (opts) { + const optKeys = Object.keys(opts); + for (let i = 0; i < optKeys.length; ++i) { + const key = optKeys[i]; + // TODO(BridgeAR): Find a solution what to do about stylize. Either make + // this function public or add a new API with a similar or better + // functionality. + if ( + // deno-lint-ignore no-prototype-builtins + inspectDefaultOptions.hasOwnProperty(key) || + key === "stylize" + ) { + ctx[key] = opts[key]; + } else if (ctx.userOptions === undefined) { + // This is required to pass through the actual user input. + ctx.userOptions = opts; + } + } } } - return denoInspect(value, Object.assign({}, opts, { quotes: ["'", '"', "`"], depth: 3 })); + if (ctx.colors) ctx.stylize = stylizeWithColor; + if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity; + if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity; + return formatValue(ctx, value, 0); } const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom"); inspect.custom = customInspectSymbol; @@ -145,6 +302,1614 @@ defineColorAlias("inverse", "swapColors"); defineColorAlias("inverse", "swapcolors"); defineColorAlias("doubleunderline", "doubleUnderline"); +// TODO(BridgeAR): Add function style support for more complex styles. +// Don't use 'blue' not visible on cmd.exe +inspect.styles = Object.assign(Object.create(null), { + special: "cyan", + number: "yellow", + bigint: "yellow", + boolean: "yellow", + undefined: "grey", + null: "bold", + string: "green", + symbol: "green", + date: "magenta", + // "name": intentionally not styling + // TODO(BridgeAR): Highlight regular expressions properly. + regexp: "red", + module: "underline", +}); + +function addQuotes(str, quotes) { + if (quotes === -1) { + return `"${str}"`; + } + if (quotes === -2) { + return `\`${str}\``; + } + return `'${str}'`; +} + +// TODO(wafuwafu13): Figure out +const escapeFn = (str) => meta[str.charCodeAt(0)]; + +// Escape control characters, single quotes and the backslash. +// This is similar to JSON stringify escaping. +function strEscape(str) { + let escapeTest = strEscapeSequencesRegExp; + let escapeReplace = strEscapeSequencesReplacer; + let singleQuote = 39; + + // Check for double quotes. If not present, do not escape single quotes and + // instead wrap the text in double quotes. If double quotes exist, check for + // backticks. If they do not exist, use those as fallback instead of the + // double quotes. + if (str.includes("'")) { + // This invalidates the charCode and therefore can not be matched for + // anymore. + if (!str.includes('"')) { + singleQuote = -1; + } else if ( + !str.includes("`") && + !str.includes("${") + ) { + singleQuote = -2; + } + if (singleQuote !== 39) { + escapeTest = strEscapeSequencesRegExpSingle; + escapeReplace = strEscapeSequencesReplacerSingle; + } + } + + // Some magic numbers that worked out fine while benchmarking with v8 6.0 + if (str.length < 5000 && !escapeTest.test(str)) { + return addQuotes(str, singleQuote); + } + if (str.length > 100) { + str = str.replace(escapeReplace, escapeFn); + return addQuotes(str, singleQuote); + } + + let result = ""; + let last = 0; + const lastIndex = str.length; + for (let i = 0; i < lastIndex; i++) { + const point = str.charCodeAt(i); + if ( + point === singleQuote || + point === 92 || + point < 32 || + (point > 126 && point < 160) + ) { + if (last === i) { + result += meta[point]; + } else { + result += `${str.slice(last, i)}${meta[point]}`; + } + last = i + 1; + } + } + + if (last !== lastIndex) { + result += str.slice(last); + } + return addQuotes(result, singleQuote); +} + +function stylizeWithColor(str, styleType) { + const style = inspect.styles[styleType]; + if (style !== undefined) { + const color = inspect.colors[style]; + if (color !== undefined) { + return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; + } + } + return str; +} + +function stylizeNoColor(str) { + return str; +} + +// Note: using `formatValue` directly requires the indentation level to be +// corrected by setting `ctx.indentationLvL += diff` and then to decrease the +// value afterwards again. +function formatValue( + ctx, + value, + recurseTimes, + typedArray, +) { + // Primitive types cannot have properties. + if ( + typeof value !== "object" && + typeof value !== "function" && + !isUndetectableObject(value) + ) { + return formatPrimitive(ctx.stylize, value, ctx); + } + if (value === null) { + return ctx.stylize("null", "null"); + } + + // Memorize the context for custom inspection on proxies. + const context = value; + // Always check for proxies to prevent side effects and to prevent triggering + // any proxy handlers. + // TODO(wafuwafu13): Set Proxy + const proxy = undefined; + // const proxy = getProxyDetails(value, !!ctx.showProxy); + // if (proxy !== undefined) { + // if (ctx.showProxy) { + // return formatProxy(ctx, proxy, recurseTimes); + // } + // value = proxy; + // } + + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it. + if (ctx.customInspect) { + const maybeCustom = value[customInspectSymbol]; + if ( + typeof maybeCustom === "function" && + // Filter out the util module, its inspect function is special. + maybeCustom !== inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value) + ) { + // This makes sure the recurseTimes are reported as before while using + // a counter internally. + const depth = ctx.depth === null ? null : ctx.depth - recurseTimes; + const isCrossContext = proxy !== undefined || + !(context instanceof Object); + const ret = maybeCustom.call( + context, + depth, + getUserOptions(ctx, isCrossContext), + ); + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== context) { + if (typeof ret !== "string") { + return formatValue(ctx, ret, recurseTimes); + } + return ret.replace(/\n/g, `\n${" ".repeat(ctx.indentationLvl)}`); + } + } + } + + // Using an array here is actually better for the average case than using + // a Set. `seen` will only check for the depth and will never grow too large. + if (ctx.seen.includes(value)) { + let index = 1; + if (ctx.circular === undefined) { + ctx.circular = new Map(); + ctx.circular.set(value, index); + } else { + index = ctx.circular.get(value); + if (index === undefined) { + index = ctx.circular.size + 1; + ctx.circular.set(value, index); + } + } + return ctx.stylize(`[Circular *${index}]`, "special"); + } + + return formatRaw(ctx, value, recurseTimes, typedArray); +} + +function formatRaw(ctx, value, recurseTimes, typedArray) { + let keys; + let protoProps; + if (ctx.showHidden && (recurseTimes <= ctx.depth || ctx.depth === null)) { + protoProps = []; + } + + const constructor = getConstructorName(value, ctx, recurseTimes, protoProps); + // Reset the variable to check for this later on. + if (protoProps !== undefined && protoProps.length === 0) { + protoProps = undefined; + } + + let tag = value[Symbol.toStringTag]; + // Only list the tag in case it's non-enumerable / not an own property. + // Otherwise we'd print this twice. + if ( + typeof tag !== "string" + // TODO(wafuwafu13): Implement + // (tag !== "" && + // (ctx.showHidden + // ? Object.prototype.hasOwnProperty + // : Object.prototype.propertyIsEnumerable)( + // value, + // Symbol.toStringTag, + // )) + ) { + tag = ""; + } + let base = ""; + let formatter = getEmptyFormatArray; + let braces; + let noIterator = true; + let i = 0; + const filter = ctx.showHidden ? ALL_PROPERTIES : ONLY_ENUMERABLE; + + let extrasType = kObjectType; + + // Iterators and the rest are split to reduce checks. + // We have to check all values in case the constructor is set to null. + // Otherwise it would not possible to identify all types properly. + if (value[Symbol.iterator] || constructor === null) { + noIterator = false; + if (Array.isArray(value)) { + // Only set the constructor for non ordinary ("Array [...]") arrays. + const prefix = (constructor !== "Array" || tag !== "") + ? getPrefix(constructor, tag, "Array", `(${value.length})`) + : ""; + keys = getOwnNonIndexProperties(value, filter); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && protoProps === undefined) { + return `${braces[0]}]`; + } + extrasType = kArrayExtrasType; + formatter = formatArray; + } else if (types.isSet(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Set", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatSet.bind(null, value) + : formatSet.bind(null, value.values()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isMap(value)) { + const size = value.size; + const prefix = getPrefix(constructor, tag, "Map", `(${size})`); + keys = getKeys(value, ctx.showHidden); + formatter = constructor !== null + ? formatMap.bind(null, value) + : formatMap.bind(null, value.entries()); + if (size === 0 && keys.length === 0 && protoProps === undefined) { + return `${prefix}{}`; + } + braces = [`${prefix}{`, "}"]; + } else if (types.isTypedArray(value)) { + keys = getOwnNonIndexProperties(value, filter); + const bound = value; + const fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = TypedArrayPrototypeGetSymbolToStringTag(value); + // // Reconstruct the array information. + // bound = new primordials[fallback](value); + } + const size = value.length; + const prefix = getPrefix(constructor, tag, fallback, `(${size})`); + braces = [`${prefix}[`, "]"]; + if (value.length === 0 && keys.length === 0 && !ctx.showHidden) { + return `${braces[0]}]`; + } + // Special handle the value. The original value is required below. The + // bound function is required to reconstruct missing information. + (formatter) = formatTypedArray.bind(null, bound, size); + extrasType = kArrayExtrasType; + } else if (types.isMapIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Map", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else if (types.isSetIterator(value)) { + keys = getKeys(value, ctx.showHidden); + braces = getIteratorBraces("Set", tag); + // Add braces to the formatter parameters. + (formatter) = formatIterator.bind(null, braces); + } else { + noIterator = true; + } + } + if (noIterator) { + keys = getKeys(value, ctx.showHidden); + braces = ["{", "}"]; + if (constructor === "Object") { + if (types.isArgumentsObject(value)) { + braces[0] = "[Arguments] {"; + } else if (tag !== "") { + braces[0] = `${getPrefix(constructor, tag, "Object")}{`; + } + if (keys.length === 0 && protoProps === undefined) { + return `${braces[0]}}`; + } + } else if (typeof value === "function") { + base = getFunctionBase(value, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "special"); + } + } else if (types.isRegExp(value)) { + // Make RegExps say that they are RegExps + base = RegExp(constructor !== null ? value : new RegExp(value)) + .toString(); + const prefix = getPrefix(constructor, tag, "RegExp"); + if (prefix !== "RegExp ") { + base = `${prefix}${base}`; + } + if ( + (keys.length === 0 && protoProps === undefined) || + (recurseTimes > ctx.depth && ctx.depth !== null) + ) { + return ctx.stylize(base, "regexp"); + } + } else if (types.isDate(value)) { + // Make dates with properties first say the date + base = Number.isNaN(value.getTime()) + ? value.toString() + : value.toISOString(); + const prefix = getPrefix(constructor, tag, "Date"); + if (prefix !== "Date ") { + base = `${prefix}${base}`; + } + if (keys.length === 0 && protoProps === undefined) { + return ctx.stylize(base, "date"); + } + } else if (value instanceof Error) { + base = formatError(value, constructor, tag, ctx, keys); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else if (types.isAnyArrayBuffer(value)) { + // Fast path for ArrayBuffer and SharedArrayBuffer. + // Can't do the same for DataView because it has a non-primitive + // .buffer property that we need to recurse for. + const arrayType = types.isArrayBuffer(value) + ? "ArrayBuffer" + : "SharedArrayBuffer"; + const prefix = getPrefix(constructor, tag, arrayType); + if (typedArray === undefined) { + (formatter) = formatArrayBuffer; + } else if (keys.length === 0 && protoProps === undefined) { + return prefix + + `{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`; + } + braces[0] = `${prefix}{`; + Array.prototype.unshift.call(keys, "byteLength"); + } else if (types.isDataView(value)) { + braces[0] = `${getPrefix(constructor, tag, "DataView")}{`; + // .buffer goes last, it's not a primitive like the others. + Array.prototype.unshift.call(keys, "byteLength", "byteOffset", "buffer"); + } else if (types.isPromise(value)) { + braces[0] = `${getPrefix(constructor, tag, "Promise")}{`; + (formatter) = formatPromise; + } else if (types.isWeakSet(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakSet")}{`; + (formatter) = ctx.showHidden ? formatWeakSet : formatWeakCollection; + } else if (types.isWeakMap(value)) { + braces[0] = `${getPrefix(constructor, tag, "WeakMap")}{`; + (formatter) = ctx.showHidden ? formatWeakMap : formatWeakCollection; + } else if (types.isModuleNamespaceObject(value)) { + braces[0] = `${getPrefix(constructor, tag, "Module")}{`; + // Special handle keys for namespace objects. + (formatter) = formatNamespaceObject.bind(null, keys); + } else if (types.isBoxedPrimitive(value)) { + base = getBoxedBase(value, ctx, keys, constructor, tag); + if (keys.length === 0 && protoProps === undefined) { + return base; + } + } else { + if (keys.length === 0 && protoProps === undefined) { + // TODO(wafuwafu13): Implement + // if (types.isExternal(value)) { + // const address = getExternalValue(value).toString(16); + // return ctx.stylize(`[External: ${address}]`, 'special'); + // } + return `${getCtxStyle(value, constructor, tag)}{}`; + } + braces[0] = `${getCtxStyle(value, constructor, tag)}{`; + } + } + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + let constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + if (constructor !== null) { + constructorName = `[${constructorName}]`; + } + return ctx.stylize(constructorName, "special"); + } + recurseTimes += 1; + + ctx.seen.push(value); + ctx.currentDepth = recurseTimes; + let output; + const indentationLvl = ctx.indentationLvl; + try { + output = formatter(ctx, value, recurseTimes); + for (i = 0; i < keys.length; i++) { + output.push( + formatProperty(ctx, value, recurseTimes, keys[i], extrasType), + ); + } + if (protoProps !== undefined) { + output.push(...protoProps); + } + } catch (err) { + const constructorName = getCtxStyle(value, constructor, tag).slice(0, -1); + return handleMaxCallStackSize(ctx, err, constructorName, indentationLvl); + } + if (ctx.circular !== undefined) { + const index = ctx.circular.get(value); + if (index !== undefined) { + const reference = ctx.stylize(`<ref *${index}>`, "special"); + // Add reference always to the very beginning of the output. + if (ctx.compact !== true) { + base = base === "" ? reference : `${reference} ${base}`; + } else { + braces[0] = `${reference} ${braces[0]}`; + } + } + } + ctx.seen.pop(); + + if (ctx.sorted) { + const comparator = ctx.sorted === true ? undefined : ctx.sorted; + if (extrasType === kObjectType) { + output = output.sort(comparator); + } else if (keys.length > 1) { + const sorted = output.slice(output.length - keys.length).sort(comparator); + output.splice(output.length - keys.length, keys.length, ...sorted); + } + } + + const res = reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, + ); + const budget = ctx.budget[ctx.indentationLvl] || 0; + const newLength = budget + res.length; + ctx.budget[ctx.indentationLvl] = newLength; + // If any indentationLvl exceeds this limit, limit further inspecting to the + // minimum. Otherwise the recursive algorithm might continue inspecting the + // object even though the maximum string size (~2 ** 28 on 32 bit systems and + // ~2 ** 30 on 64 bit systems) exceeded. The actual output is not limited at + // exactly 2 ** 27 but a bit higher. This depends on the object shape. + // This limit also makes sure that huge objects don't block the event loop + // significantly. + if (newLength > 2 ** 27) { + ctx.depth = -1; + } + return res; +} + +const builtInObjects = new Set( + Object.getOwnPropertyNames(globalThis).filter((e) => + /^[A-Z][a-zA-Z0-9]+$/.test(e) + ), +); + +function addPrototypeProperties( + ctx, + main, + obj, + recurseTimes, + output, +) { + let depth = 0; + let keys; + let keySet; + do { + if (depth !== 0 || main === obj) { + obj = Object.getPrototypeOf(obj); + // Stop as soon as a null prototype is encountered. + if (obj === null) { + return; + } + // Stop as soon as a built-in object type is detected. + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name) + ) { + return; + } + } + + if (depth === 0) { + keySet = new Set(); + } else { + Array.prototype.forEach.call(keys, (key) => keySet.add(key)); + } + // Get all own property names and symbols. + keys = Reflect.ownKeys(obj); + Array.prototype.push.call(ctx.seen, main); + for (const key of keys) { + // Ignore the `constructor` property and keys that exist on layers above. + if ( + key === "constructor" || + // deno-lint-ignore no-prototype-builtins + main.hasOwnProperty(key) || + (depth !== 0 && keySet.has(key)) + ) { + continue; + } + const desc = Object.getOwnPropertyDescriptor(obj, key); + if (typeof desc.value === "function") { + continue; + } + const value = formatProperty( + ctx, + obj, + recurseTimes, + key, + kObjectType, + desc, + main, + ); + if (ctx.colors) { + // Faint! + Array.prototype.push.call(output, `\u001b[2m${value}\u001b[22m`); + } else { + Array.prototype.push.call(output, value); + } + } + Array.prototype.pop.call(ctx.seen); + // Limit the inspection to up to three prototype layers. Using `recurseTimes` + // is not a good choice here, because it's as if the properties are declared + // on the current object from the users perspective. + } while (++depth !== 3); +} + +function getConstructorName( + obj, + ctx, + recurseTimes, + protoProps, +) { + let firstProto; + const tmp = obj; + while (obj || isUndetectableObject(obj)) { + const descriptor = Object.getOwnPropertyDescriptor(obj, "constructor"); + if ( + descriptor !== undefined && + typeof descriptor.value === "function" && + descriptor.value.name !== "" && + isInstanceof(tmp, descriptor.value) + ) { + if ( + protoProps !== undefined && + (firstProto !== obj || + !builtInObjects.has(descriptor.value.name)) + ) { + addPrototypeProperties( + ctx, + tmp, + firstProto || tmp, + recurseTimes, + protoProps, + ); + } + return descriptor.value.name; + } + + obj = Object.getPrototypeOf(obj); + if (firstProto === undefined) { + firstProto = obj; + } + } + + if (firstProto === null) { + return null; + } + + // TODO(wafuwafu13): Implement + // const res = internalGetConstructorName(tmp); + const res = undefined; + + if (recurseTimes > ctx.depth && ctx.depth !== null) { + return `${res} <Complex prototype>`; + } + + const protoConstr = getConstructorName( + firstProto, + ctx, + recurseTimes + 1, + protoProps, + ); + + if (protoConstr === null) { + return `${res} <${ + inspect(firstProto, { + ...ctx, + customInspect: false, + depth: -1, + }) + }>`; + } + + return `${res} <${protoConstr}>`; +} + +function formatPrimitive(fn, value, ctx) { + if (typeof value === "string") { + let trailer = ""; + if (value.length > ctx.maxStringLength) { + const remaining = value.length - ctx.maxStringLength; + value = value.slice(0, ctx.maxStringLength); + trailer = `... ${remaining} more character${remaining > 1 ? "s" : ""}`; + } + if ( + ctx.compact !== true && + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. + value.length > kMinLineLength && + value.length > ctx.breakLength - ctx.indentationLvl - 4 + ) { + return value + .split(/(?<=\n)/) + .map((line) => fn(strEscape(line), "string")) + .join(` +\n${" ".repeat(ctx.indentationLvl + 2)}`) + trailer; + } + return fn(strEscape(value), "string") + trailer; + } + if (typeof value === "number") { + return formatNumber(fn, value); + } + if (typeof value === "bigint") { + return formatBigInt(fn, value); + } + if (typeof value === "boolean") { + return fn(`${value}`, "boolean"); + } + if (typeof value === "undefined") { + return fn("undefined", "undefined"); + } + // es6 symbol primitive + return fn(value.toString(), "symbol"); +} + +// Return a new empty array to push in the results of the default formatter. +function getEmptyFormatArray() { + return []; +} + +function isInstanceof(object, proto) { + try { + return object instanceof proto; + } catch { + return false; + } +} + +function getPrefix(constructor, tag, fallback, size = "") { + if (constructor === null) { + if (tag !== "" && fallback !== tag) { + return `[${fallback}${size}: null prototype] [${tag}] `; + } + return `[${fallback}${size}: null prototype] `; + } + + if (tag !== "" && constructor !== tag) { + return `${constructor}${size} [${tag}] `; + } + return `${constructor}${size} `; +} + +function formatArray(ctx, value, recurseTimes) { + const valLen = value.length; + const len = Math.min(Math.max(0, ctx.maxArrayLength), valLen); + + const remaining = valLen - len; + const output = []; + for (let i = 0; i < len; i++) { + // Special handle sparse arrays. + // deno-lint-ignore no-prototype-builtins + if (!value.hasOwnProperty(i)) { + return formatSpecialArray(ctx, value, recurseTimes, len, output, i); + } + output.push(formatProperty(ctx, value, recurseTimes, i, kArrayType)); + } + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getCtxStyle(_value, constructor, tag) { + let fallback = ""; + if (constructor === null) { + // TODO(wafuwafu13): Implement + // fallback = internalGetConstructorName(value); + if (fallback === tag) { + fallback = "Object"; + } + } + return getPrefix(constructor, tag, fallback); +} + +// Look up the keys of the object. +function getKeys(value, showHidden) { + let keys; + const symbols = Object.getOwnPropertySymbols(value); + if (showHidden) { + keys = Object.getOwnPropertyNames(value); + if (symbols.length !== 0) { + Array.prototype.push.apply(keys, symbols); + } + } else { + // This might throw if `value` is a Module Namespace Object from an + // unevaluated module, but we don't want to perform the actual type + // check because it's expensive. + // TODO(devsnek): track https://github.com/tc39/ecma262/issues/1209 + // and modify this logic as needed. + try { + keys = Object.keys(value); + } catch (_err) { + // TODO(wafuwafu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError' && + // isModuleNamespaceObject(value)); + keys = Object.getOwnPropertyNames(value); + } + if (symbols.length !== 0) { + // TODO(wafuwafu13): Implement + // const filter = (key: any) => + // + // Object.prototype.propertyIsEnumerable(value, key); + // Array.prototype.push.apply( + // keys, + // symbols.filter(filter), + // ); + } + } + return keys; +} + +function formatSet(value, ctx, _ignored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const v of value) { + Array.prototype.push.call(output, formatValue(ctx, v, recurseTimes)); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatMap(value, ctx, _gnored, recurseTimes) { + const output = []; + ctx.indentationLvl += 2; + for (const { 0: k, 1: v } of value) { + output.push( + `${formatValue(ctx, k, recurseTimes)} => ${ + formatValue(ctx, v, recurseTimes) + }`, + ); + } + ctx.indentationLvl -= 2; + return output; +} + +function formatTypedArray( + value, + length, + ctx, + _ignored, + recurseTimes, +) { + const maxLength = Math.min(Math.max(0, ctx.maxArrayLength), length); + const remaining = value.length - maxLength; + const output = new Array(maxLength); + const elementFormatter = value.length > 0 && typeof value[0] === "number" + ? formatNumber + : formatBigInt; + for (let i = 0; i < maxLength; ++i) { + output[i] = elementFormatter(ctx.stylize, value[i]); + } + if (remaining > 0) { + output[maxLength] = `... ${remaining} more item${remaining > 1 ? "s" : ""}`; + } + if (ctx.showHidden) { + // .buffer goes last, it's not a primitive like the others. + // All besides `BYTES_PER_ELEMENT` are actually getters. + ctx.indentationLvl += 2; + for ( + const key of [ + "BYTES_PER_ELEMENT", + "length", + "byteLength", + "byteOffset", + "buffer", + ] + ) { + const str = formatValue(ctx, value[key], recurseTimes, true); + Array.prototype.push.call(output, `[${key}]: ${str}`); + } + ctx.indentationLvl -= 2; + } + return output; +} + +function getIteratorBraces(type, tag) { + if (tag !== `${type} Iterator`) { + if (tag !== "") { + tag += "] ["; + } + tag += `${type} Iterator`; + } + return [`[${tag}] {`, "}"]; +} + +function formatIterator(braces, ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const { 0: entries, 1: isKeyValue } = previewEntries(value, true); + const { 0: entries, 1: isKeyValue } = value; + if (isKeyValue) { + // Mark entry iterators as such. + braces[0] = braces[0].replace(/ Iterator] {$/, " Entries] {"); + return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries); + } + + return formatSetIterInner(ctx, recurseTimes, entries, kIterator); +} + +function getFunctionBase(value, constructor, tag) { + const stringified = Function.prototype.toString.call(value); + if (stringified.slice(0, 5) === "class" && stringified.endsWith("}")) { + const slice = stringified.slice(5, -1); + const bracketIndex = slice.indexOf("{"); + if ( + bracketIndex !== -1 && + (!slice.slice(0, bracketIndex).includes("(") || + // Slow path to guarantee that it's indeed a class. + classRegExp.test(slice.replace(stripCommentsRegExp))) + ) { + return getClassBase(value, constructor, tag); + } + } + let type = "Function"; + if (types.isGeneratorFunction(value)) { + type = `Generator${type}`; + } + if (types.isAsyncFunction(value)) { + type = `Async${type}`; + } + let base = `[${type}`; + if (constructor === null) { + base += " (null prototype)"; + } + if (value.name === "") { + base += " (anonymous)"; + } else { + base += `: ${value.name}`; + } + base += "]"; + if (constructor !== type && constructor !== null) { + base += ` ${constructor}`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + return base; +} + +function formatError( + err, + constructor, + tag, + ctx, + keys, +) { + const name = err.name != null ? String(err.name) : "Error"; + let len = name.length; + let stack = err.stack ? String(err.stack) : err.toString(); + + // Do not "duplicate" error properties that are already included in the output + // otherwise. + if (!ctx.showHidden && keys.length !== 0) { + for (const name of ["name", "message", "stack"]) { + const index = keys.indexOf(name); + // Only hide the property in case it's part of the original stack + if (index !== -1 && stack.includes(err[name])) { + keys.splice(index, 1); + } + } + } + + // A stack trace may contain arbitrary data. Only manipulate the output + // for "regular errors" (errors that "look normal") for now. + if ( + constructor === null || + (name.endsWith("Error") && + stack.startsWith(name) && + (stack.length === len || stack[len] === ":" || stack[len] === "\n")) + ) { + let fallback = "Error"; + if (constructor === null) { + const start = stack.match(/^([A-Z][a-z_ A-Z0-9[\]()-]+)(?::|\n {4}at)/) || + stack.match(/^([a-z_A-Z0-9-]*Error)$/); + fallback = (start && start[1]) || ""; + len = fallback.length; + fallback = fallback || "Error"; + } + const prefix = getPrefix(constructor, tag, fallback).slice(0, -1); + if (name !== prefix) { + if (prefix.includes(name)) { + if (len === 0) { + stack = `${prefix}: ${stack}`; + } else { + stack = `${prefix}${stack.slice(len)}`; + } + } else { + stack = `${prefix} [${name}]${stack.slice(len)}`; + } + } + } + // Ignore the error message if it's contained in the stack. + let pos = (err.message && stack.indexOf(err.message)) || -1; + if (pos !== -1) { + pos += err.message.length; + } + // Wrap the error in brackets in case it has no stack trace. + const stackStart = stack.indexOf("\n at", pos); + if (stackStart === -1) { + stack = `[${stack}]`; + } else if (ctx.colors) { + // Highlight userland code and node modules. + let newStack = stack.slice(0, stackStart); + const lines = stack.slice(stackStart + 1).split("\n"); + for (const line of lines) { + // const core = line.match(coreModuleRegExp); + // TODO(wafuwafu13): Implement + // if (core !== null && NativeModule.exists(core[1])) { + // newStack += `\n${ctx.stylize(line, 'undefined')}`; + // } else { + // This adds underscores to all node_modules to quickly identify them. + let nodeModule; + newStack += "\n"; + let pos = 0; + // deno-lint-ignore no-cond-assign + while (nodeModule = nodeModulesRegExp.exec(line)) { + // '/node_modules/'.length === 14 + newStack += line.slice(pos, nodeModule.index + 14); + newStack += ctx.stylize(nodeModule[1], "module"); + pos = nodeModule.index + nodeModule[0].length; + } + newStack += pos === 0 ? line : line.slice(pos); + // } + } + stack = newStack; + } + // The message and the stack have to be indented as well! + if (ctx.indentationLvl !== 0) { + const indentation = " ".repeat(ctx.indentationLvl); + stack = stack.replace(/\n/g, `\n${indentation}`); + } + return stack; +} + +let hexSlice; + +function formatArrayBuffer(ctx, value) { + let buffer; + try { + buffer = new Uint8Array(value); + } catch { + return [ctx.stylize("(detached)", "special")]; + } + // TODO(wafuwafu13): Implement + // if (hexSlice === undefined) + // hexSlice = uncurryThis(require('buffer').Buffer.prototype.hexSlice); + let str = hexSlice(buffer, 0, Math.min(ctx.maxArrayLength, buffer.length)) + .replace(/(.{2})/g, "$1 ").trim(); + + const remaining = buffer.length - ctx.maxArrayLength; + if (remaining > 0) { + str += ` ... ${remaining} more byte${remaining > 1 ? "s" : ""}`; + } + return [`${ctx.stylize("[Uint8Contents]", "special")}: <${str}>`]; +} + +function formatNumber(fn, value) { + // Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0. + return fn(Object.is(value, -0) ? "-0" : `${value}`, "number"); +} + +function formatPromise(ctx, value, recurseTimes) { + let output; + // TODO(wafuwafu13): Implement + // const { 0: state, 1: result } = getPromiseDetails(value); + const { 0: state, 1: result } = value; + if (state === kPending) { + output = [ctx.stylize("<pending>", "special")]; + } else { + ctx.indentationLvl += 2; + const str = formatValue(ctx, result, recurseTimes); + ctx.indentationLvl -= 2; + output = [ + state === kRejected + ? `${ctx.stylize("<rejected>", "special")} ${str}` + : str, + ]; + } + return output; +} + +function formatWeakCollection(ctx) { + return [ctx.stylize("<items unknown>", "special")]; +} + +function formatWeakSet(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatSetIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatWeakMap(ctx, value, recurseTimes) { + // TODO(wafuwafu13): Implement + // const entries = previewEntries(value); + const entries = value; + return formatMapIterInner(ctx, recurseTimes, entries, kWeak); +} + +function formatProperty( + ctx, + value, + recurseTimes, + key, + type, + desc, + original = value, +) { + let name, str; + let extra = " "; + desc = desc || Object.getOwnPropertyDescriptor(value, key) || + { value: value[key], enumerable: true }; + if (desc.value !== undefined) { + const diff = (ctx.compact !== true || type !== kObjectType) ? 2 : 3; + ctx.indentationLvl += diff; + str = formatValue(ctx, desc.value, recurseTimes); + if (diff === 3 && ctx.breakLength < getStringWidth(str, ctx.colors)) { + extra = `\n${" ".repeat(ctx.indentationLvl)}`; + } + ctx.indentationLvl -= diff; + } else if (desc.get !== undefined) { + const label = desc.set !== undefined ? "Getter/Setter" : "Getter"; + const s = ctx.stylize; + const sp = "special"; + if ( + ctx.getters && (ctx.getters === true || + (ctx.getters === "get" && desc.set === undefined) || + (ctx.getters === "set" && desc.set !== undefined)) + ) { + try { + const tmp = desc.get.call(original); + ctx.indentationLvl += 2; + if (tmp === null) { + str = `${s(`[${label}:`, sp)} ${s("null", "null")}${s("]", sp)}`; + } else if (typeof tmp === "object") { + str = `${s(`[${label}]`, sp)} ${formatValue(ctx, tmp, recurseTimes)}`; + } else { + const primitive = formatPrimitive(s, tmp, ctx); + str = `${s(`[${label}:`, sp)} ${primitive}${s("]", sp)}`; + } + ctx.indentationLvl -= 2; + } catch (err) { + const message = `<Inspection threw (${err.message})>`; + str = `${s(`[${label}:`, sp)} ${message}${s("]", sp)}`; + } + } else { + str = ctx.stylize(`[${label}]`, sp); + } + } else if (desc.set !== undefined) { + str = ctx.stylize("[Setter]", "special"); + } else { + str = ctx.stylize("undefined", "undefined"); + } + if (type === kArrayType) { + return str; + } + if (typeof key === "symbol") { + const tmp = key.toString().replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${ctx.stylize(tmp, "symbol")}]`; + } else if (key === "__proto__") { + name = "['__proto__']"; + } else if (desc.enumerable === false) { + const tmp = key.replace(strEscapeSequencesReplacer, escapeFn); + + name = `[${tmp}]`; + } else if (keyStrRegExp.test(key)) { + name = ctx.stylize(key, "name"); + } else { + name = ctx.stylize(strEscape(key), "string"); + } + return `${name}:${extra}${str}`; +} + +function handleMaxCallStackSize( + _ctx, + _err, + _constructorName, + _indentationLvl, +) { + // TODO(wafuwafu13): Implement + // if (types.isStackOverflowError(err)) { + // ctx.seen.pop(); + // ctx.indentationLvl = indentationLvl; + // return ctx.stylize( + // `[${constructorName}: Inspection interrupted ` + + // 'prematurely. Maximum call stack size exceeded.]', + // 'special' + // ); + // } + // /* c8 ignore next */ + // assert.fail(err.stack); +} + +// deno-lint-ignore no-control-regex +const colorRegExp = /\u001b\[\d\d?m/g; +function removeColors(str) { + return str.replace(colorRegExp, ""); +} + +function isBelowBreakLength(ctx, output, start, base) { + // Each entry is separated by at least a comma. Thus, we start with a total + // length of at least `output.length`. In addition, some cases have a + // whitespace in-between each other that is added to the total as well. + // TODO(BridgeAR): Add unicode support. Use the readline getStringWidth + // function. Check the performance overhead and make it an opt-in in case it's + // significant. + let totalLength = output.length + start; + if (totalLength + output.length > ctx.breakLength) { + return false; + } + for (let i = 0; i < output.length; i++) { + if (ctx.colors) { + totalLength += removeColors(output[i]).length; + } else { + totalLength += output[i].length; + } + if (totalLength > ctx.breakLength) { + return false; + } + } + // Do not line up properties on the same line if `base` contains line breaks. + return base === "" || !base.includes("\n"); +} + +function formatBigInt(fn, value) { + return fn(`${value}n`, "bigint"); +} + +function formatNamespaceObject( + keys, + ctx, + value, + recurseTimes, +) { + const output = new Array(keys.length); + for (let i = 0; i < keys.length; i++) { + try { + output[i] = formatProperty( + ctx, + value, + recurseTimes, + keys[i], + kObjectType, + ); + } catch (_err) { + // TODO(wafuwfu13): Implement + // assert(isNativeError(err) && err.name === 'ReferenceError'); + // Use the existing functionality. This makes sure the indentation and + // line breaks are always correct. Otherwise it is very difficult to keep + // this aligned, even though this is a hacky way of dealing with this. + const tmp = { [keys[i]]: "" }; + output[i] = formatProperty(ctx, tmp, recurseTimes, keys[i], kObjectType); + const pos = output[i].lastIndexOf(" "); + // We have to find the last whitespace and have to replace that value as + // it will be visualized as a regular string. + output[i] = output[i].slice(0, pos + 1) + + ctx.stylize("<uninitialized>", "special"); + } + } + // Reset the keys to an empty array. This prevents duplicated inspection. + keys.length = 0; + return output; +} + +// The array is sparse and/or has extra keys +function formatSpecialArray( + ctx, + value, + recurseTimes, + maxLength, + output, + i, +) { + const keys = Object.keys(value); + let index = i; + for (; i < keys.length && output.length < maxLength; i++) { + const key = keys[i]; + const tmp = +key; + // Arrays can only have up to 2^32 - 1 entries + if (tmp > 2 ** 32 - 2) { + break; + } + if (`${index}` !== key) { + if (!numberRegExp.test(key)) { + break; + } + const emptyItems = tmp - index; + const ending = emptyItems > 1 ? "s" : ""; + const message = `<${emptyItems} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + index = tmp; + if (output.length === maxLength) { + break; + } + } + output.push(formatProperty(ctx, value, recurseTimes, key, kArrayType)); + index++; + } + const remaining = value.length - index; + if (output.length !== maxLength) { + if (remaining > 0) { + const ending = remaining > 1 ? "s" : ""; + const message = `<${remaining} empty item${ending}>`; + output.push(ctx.stylize(message, "undefined")); + } + } else if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function getBoxedBase( + value, + ctx, + keys, + constructor, + tag, +) { + let type; + if (types.isNumberObject(value)) { + type = "Number"; + } else if (types.isStringObject(value)) { + type = "String"; + // For boxed Strings, we have to remove the 0-n indexed entries, + // since they just noisy up the output and are redundant + // Make boxed primitive Strings look like such + keys.splice(0, value.length); + } else if (types.isBooleanObject(value)) { + type = "Boolean"; + } else if (types.isBigIntObject(value)) { + type = "BigInt"; + } else { + type = "Symbol"; + } + let base = `[${type}`; + if (type !== constructor) { + if (constructor === null) { + base += " (null prototype)"; + } else { + base += ` (${constructor})`; + } + } + + base += `: ${formatPrimitive(stylizeNoColor, value.valueOf(), ctx)}]`; + if (tag !== "" && tag !== constructor) { + base += ` [${tag}]`; + } + if (keys.length !== 0 || ctx.stylize === stylizeNoColor) { + return base; + } + return ctx.stylize(base, type.toLowerCase()); +} + +function getClassBase(value, constructor, tag) { + // deno-lint-ignore no-prototype-builtins + const hasName = value.hasOwnProperty("name"); + const name = (hasName && value.name) || "(anonymous)"; + let base = `class ${name}`; + if (constructor !== "Function" && constructor !== null) { + base += ` [${constructor}]`; + } + if (tag !== "" && constructor !== tag) { + base += ` [${tag}]`; + } + if (constructor !== null) { + const superName = Object.getPrototypeOf(value).name; + if (superName) { + base += ` extends ${superName}`; + } + } else { + base += " extends [null prototype]"; + } + return `[${base}]`; +} + +function reduceToSingleString( + ctx, + output, + base, + braces, + extrasType, + recurseTimes, + value, +) { + if (ctx.compact !== true) { + if (typeof ctx.compact === "number" && ctx.compact >= 1) { + // Memorize the original output length. In case the output is grouped, + // prevent lining up the entries on a single line. + const entries = output.length; + // Group array elements together if the array contains at least six + // separate entries. + if (extrasType === kArrayExtrasType && entries > 6) { + output = groupArrayElements(ctx, output, value); + } + // `ctx.currentDepth` is set to the most inner depth of the currently + // inspected object part while `recurseTimes` is the actual current depth + // that is inspected. + // + // Example: + // + // const a = { first: [ 1, 2, 3 ], second: { inner: [ 1, 2, 3 ] } } + // + // The deepest depth of `a` is 2 (a.second.inner) and `a.first` has a max + // depth of 1. + // + // Consolidate all entries of the local most inner depth up to + // `ctx.compact`, as long as the properties are smaller than + // `ctx.breakLength`. + if ( + ctx.currentDepth - recurseTimes < ctx.compact && + entries === output.length + ) { + // Line up all entries on a single line in case the entries do not + // exceed `breakLength`. Add 10 as constant to start next to all other + // factors that may reduce `breakLength`. + const start = output.length + ctx.indentationLvl + + braces[0].length + base.length + 10; + if (isBelowBreakLength(ctx, output, start, base)) { + return `${base ? `${base} ` : ""}${braces[0]} ${join(output, ", ")}` + + ` ${braces[1]}`; + } + } + } + // Line up each entry on an individual line. + const indentation = `\n${" ".repeat(ctx.indentationLvl)}`; + return `${base ? `${base} ` : ""}${braces[0]}${indentation} ` + + `${join(output, `,${indentation} `)}${indentation}${braces[1]}`; + } + // Line up all entries on a single line in case the entries do not exceed + // `breakLength`. + if (isBelowBreakLength(ctx, output, 0, base)) { + return `${braces[0]}${base ? ` ${base}` : ""} ${join(output, ", ")} ` + + braces[1]; + } + const indentation = " ".repeat(ctx.indentationLvl); + // If the opening "brace" is too large, like in the case of "Set {", + // we need to force the first item to be on the next line or the + // items will not line up correctly. + const ln = base === "" && braces[0].length === 1 + ? " " + : `${base ? ` ${base}` : ""}\n${indentation} `; + // Line up each entry on an individual line. + return `${braces[0]}${ln}${join(output, `,\n${indentation} `)} ${braces[1]}`; +} + +// The built-in Array#join is slower in v8 6.0 +function join(output, separator) { + let str = ""; + if (output.length !== 0) { + const lastIndex = output.length - 1; + for (let i = 0; i < lastIndex; i++) { + // It is faster not to use a template string here + str += output[i]; + str += separator; + } + str += output[lastIndex]; + } + return str; +} + +function groupArrayElements(ctx, output, value) { + let totalLength = 0; + let maxLength = 0; + let i = 0; + let outputLength = output.length; + if (ctx.maxArrayLength < output.length) { + // This makes sure the "... n more items" part is not taken into account. + outputLength--; + } + const separatorSpace = 2; // Add 1 for the space and 1 for the separator. + const dataLen = new Array(outputLength); + // Calculate the total length of all output entries and the individual max + // entries length of all output entries. We have to remove colors first, + // otherwise the length would not be calculated properly. + for (; i < outputLength; i++) { + const len = getStringWidth(output[i], ctx.colors); + dataLen[i] = len; + totalLength += len + separatorSpace; + if (maxLength < len) { + maxLength = len; + } + } + // Add two to `maxLength` as we add a single whitespace character plus a comma + // in-between two entries. + const actualMax = maxLength + separatorSpace; + // Check if at least three entries fit next to each other and prevent grouping + // of arrays that contains entries of very different length (i.e., if a single + // entry is longer than 1/5 of all other entries combined). Otherwise the + // space in-between small entries would be enormous. + if ( + actualMax * 3 + ctx.indentationLvl < ctx.breakLength && + (totalLength / actualMax > 5 || maxLength <= 6) + ) { + const approxCharHeights = 2.5; + const averageBias = Math.sqrt(actualMax - totalLength / output.length); + const biasedMax = Math.max(actualMax - 3 - averageBias, 1); + // Dynamically check how many columns seem possible. + const columns = Math.min( + // Ideally a square should be drawn. We expect a character to be about 2.5 + // times as high as wide. This is the area formula to calculate a square + // which contains n rectangles of size `actualMax * approxCharHeights`. + // Divide that by `actualMax` to receive the correct number of columns. + // The added bias increases the columns for short entries. + Math.round( + Math.sqrt( + approxCharHeights * biasedMax * outputLength, + ) / biasedMax, + ), + // Do not exceed the breakLength. + Math.floor((ctx.breakLength - ctx.indentationLvl) / actualMax), + // Limit array grouping for small `compact` modes as the user requested + // minimal grouping. + ctx.compact * 4, + // Limit the columns to a maximum of fifteen. + 15, + ); + // Return with the original output if no grouping should happen. + if (columns <= 1) { + return output; + } + const tmp = []; + const maxLineLength = []; + for (let i = 0; i < columns; i++) { + let lineMaxLength = 0; + for (let j = i; j < output.length; j += columns) { + if (dataLen[j] > lineMaxLength) { + lineMaxLength = dataLen[j]; + } + } + lineMaxLength += separatorSpace; + maxLineLength[i] = lineMaxLength; + } + let order = String.prototype.padStart; + if (value !== undefined) { + for (let i = 0; i < output.length; i++) { + if (typeof value[i] !== "number" && typeof value[i] !== "bigint") { + order = String.prototype.padEnd; + break; + } + } + } + // Each iteration creates a single line of grouped entries. + for (let i = 0; i < outputLength; i += columns) { + // The last lines may contain less entries than columns. + const max = Math.min(i + columns, outputLength); + let str = ""; + let j = i; + for (; j < max - 1; j++) { + // Calculate extra color padding in case it's active. This has to be + // done line by line as some lines might contain more colors than + // others. + const padding = maxLineLength[j - i] + output[j].length - dataLen[j]; + str += `${output[j]}, `.padStart(padding, " "); + } + if (order === String.prototype.padStart) { + const padding = maxLineLength[j - i] + + output[j].length - + dataLen[j] - + separatorSpace; + str += output[j].padStart(padding, " "); + } else { + str += output[j]; + } + Array.prototype.push.call(tmp, str); + } + if (ctx.maxArrayLength < output.length) { + Array.prototype.push.call(tmp, output[outputLength]); + } + output = tmp; + } + return output; +} + +function formatMapIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + // Entries exist as [key1, val1, key2, val2, ...] + const len = entries.length / 2; + const remaining = len - maxArrayLength; + const maxLength = Math.min(maxArrayLength, len); + let output = new Array(maxLength); + let i = 0; + ctx.indentationLvl += 2; + if (state === kWeak) { + for (; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${formatValue(ctx, entries[pos], recurseTimes)} => ${ + formatValue(ctx, entries[pos + 1], recurseTimes) + }`; + } + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + if (!ctx.sorted) { + output = output.sort(); + } + } else { + for (; i < maxLength; i++) { + const pos = i * 2; + const res = [ + formatValue(ctx, entries[pos], recurseTimes), + formatValue(ctx, entries[pos + 1], recurseTimes), + ]; + output[i] = reduceToSingleString( + ctx, + res, + "", + ["[", "]"], + kArrayExtrasType, + recurseTimes, + ); + } + } + ctx.indentationLvl -= 2; + if (remaining > 0) { + output.push(`... ${remaining} more item${remaining > 1 ? "s" : ""}`); + } + return output; +} + +function formatSetIterInner( + ctx, + recurseTimes, + entries, + state, +) { + const maxArrayLength = Math.max(ctx.maxArrayLength, 0); + const maxLength = Math.min(maxArrayLength, entries.length); + const output = new Array(maxLength); + ctx.indentationLvl += 2; + for (let i = 0; i < maxLength; i++) { + output[i] = formatValue(ctx, entries[i], recurseTimes); + } + ctx.indentationLvl -= 2; + if (state === kWeak && !ctx.sorted) { + // Sort all entries to have a halfway reliable output (if more entries than + // retrieved ones exist, we can not reliably return the same output) if the + // output is not sorted anyway. + output.sort(); + } + const remaining = entries.length - maxLength; + if (remaining > 0) { + Array.prototype.push.call( + output, + `... ${remaining} more item${remaining > 1 ? "s" : ""}`, + ); + } + return output; +} + // 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 @@ -164,11 +1929,135 @@ export function getStringWidth(str, removeControlChars = true) { if (removeControlChars) { str = stripVTControlCharacters(str); } - return denoGetStringWidth(str); + str = str.normalize("NFC"); + for (const char of str[Symbol.iterator]()) { + const code = char.codePointAt(0); + if (isFullWidthCodePoint(code)) { + width += 2; + } else if (!isZeroWidthCodePoint(code)) { + width++; + } + } + + return width; +} + +/** + * Returns true if the character represented by a given + * Unicode code point is full-width. Otherwise returns false. + */ +const 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) + ); +}; + +const 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 hasBuiltInToString(value) { + // TODO(wafuwafu13): Implement + // // Prevent triggering proxy traps. + // const getFullProxy = false; + // const proxyTarget = getProxyDetails(value, getFullProxy); + const proxyTarget = undefined; + if (proxyTarget !== undefined) { + value = proxyTarget; + } + + // Count objects that have no `toString` function as built-in. + if (typeof value.toString !== "function") { + return true; + } + + // The object has a own `toString` property. Thus it's not not a built-in one. + if (Object.prototype.hasOwnProperty.call(value, "toString")) { + return false; + } + + // Find the object that has the `toString` property as own property in the + // prototype chain. + let pointer = value; + do { + pointer = Object.getPrototypeOf(pointer); + } while (!Object.prototype.hasOwnProperty.call(pointer, "toString")); + + // Check closer if the object is a built-in. + const descriptor = Object.getOwnPropertyDescriptor(pointer, "constructor"); + return descriptor !== undefined && + typeof descriptor.value === "function" && + builtInObjects.has(descriptor.value.name); +} + +const firstErrorLine = (error) => error.message.split("\n", 1)[0]; +let CIRCULAR_ERROR_MESSAGE; +function tryStringify(arg) { + try { + return JSON.stringify(arg); + } catch (err) { + // Populate the circular error message lazily + if (!CIRCULAR_ERROR_MESSAGE) { + try { + const a = {}; + a.a = a; + JSON.stringify(a); + } catch (circularError) { + CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError); + } + } + if ( + err.name === "TypeError" && + firstErrorLine(err) === CIRCULAR_ERROR_MESSAGE + ) { + return "[Circular]"; + } + throw err; + } } export function format(...args) { - return inspectArgs(args); + return formatWithOptionsInternal(undefined, args); } export function formatWithOptions(inspectOptions, ...args) { @@ -179,7 +2068,155 @@ export function formatWithOptions(inspectOptions, ...args) { inspectOptions, ); } - return inspectArgs(args, inspectOptions); + return formatWithOptionsInternal(inspectOptions, args); +} + +function formatNumberNoColor(number, options) { + return formatNumber( + stylizeNoColor, + number, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatBigIntNoColor(bigint, options) { + return formatBigInt( + stylizeNoColor, + bigint, + options?.numericSeparator ?? inspectDefaultOptions.numericSeparator, + ); +} + +function formatWithOptionsInternal(inspectOptions, args) { + const first = args[0]; + let a = 0; + let str = ""; + let join = ""; + + if (typeof first === "string") { + if (args.length === 1) { + return first; + } + let tempStr; + let lastPos = 0; + + for (let i = 0; i < first.length - 1; i++) { + if (first.charCodeAt(i) === 37) { // '%' + const nextChar = first.charCodeAt(++i); + if (a + 1 !== args.length) { + switch (nextChar) { + // deno-lint-ignore no-case-declarations + case 115: // 's' + const tempArg = args[++a]; + if (typeof tempArg === "number") { + tempStr = formatNumberNoColor(tempArg, inspectOptions); + } else if (typeof tempArg === "bigint") { + tempStr = formatBigIntNoColor(tempArg, inspectOptions); + } else if ( + typeof tempArg !== "object" || + tempArg === null || + !hasBuiltInToString(tempArg) + ) { + tempStr = String(tempArg); + } else { + tempStr = inspect(tempArg, { + ...inspectOptions, + compact: 3, + colors: false, + depth: 0, + }); + } + break; + case 106: // 'j' + tempStr = tryStringify(args[++a]); + break; + // deno-lint-ignore no-case-declarations + case 100: // 'd' + const tempNum = args[++a]; + if (typeof tempNum === "bigint") { + tempStr = formatBigIntNoColor(tempNum, inspectOptions); + } else if (typeof tempNum === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor(Number(tempNum), inspectOptions); + } + break; + case 79: // 'O' + tempStr = inspect(args[++a], inspectOptions); + break; + case 111: // 'o' + tempStr = inspect(args[++a], { + ...inspectOptions, + showHidden: true, + showProxy: true, + depth: 4, + }); + break; + // deno-lint-ignore no-case-declarations + case 105: // 'i' + const tempInteger = args[++a]; + if (typeof tempInteger === "bigint") { + tempStr = formatBigIntNoColor(tempInteger, inspectOptions); + } else if (typeof tempInteger === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseInt(tempInteger), + inspectOptions, + ); + } + break; + // deno-lint-ignore no-case-declarations + case 102: // 'f' + const tempFloat = args[++a]; + if (typeof tempFloat === "symbol") { + tempStr = "NaN"; + } else { + tempStr = formatNumberNoColor( + Number.parseFloat(tempFloat), + inspectOptions, + ); + } + break; + case 99: // 'c' + a += 1; + tempStr = ""; + break; + case 37: // '%' + str += first.slice(lastPos, i); + lastPos = i + 1; + continue; + default: // Any other character is not a correct placeholder + continue; + } + if (lastPos !== i - 1) { + str += first.slice(lastPos, i - 1); + } + str += tempStr; + lastPos = i + 1; + } else if (nextChar === 37) { + str += first.slice(lastPos, i); + lastPos = i + 1; + } + } + } + if (lastPos !== 0) { + a++; + join = " "; + if (lastPos < first.length) { + str += first.slice(lastPos); + } + } + } + + while (a < args.length) { + const value = args[a]; + str += join; + str += typeof value !== "string" ? inspect(value, inspectOptions) : value; + join = " "; + a++; + } + return str; } /** diff --git a/tools/node_compat/TODO.md b/tools/node_compat/TODO.md index 0463e2657..42e36cc9b 100644 --- a/tools/node_compat/TODO.md +++ b/tools/node_compat/TODO.md @@ -3,7 +3,7 @@ NOTE: This file should not be manually edited. Please edit 'cli/tests/node_compat/config.json' and run 'tools/node_compat/setup.ts' instead. -Total: 2927 +Total: 2924 - [abort/test-abort-backtrace.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-backtrace.js) - [abort/test-abort-fatal-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-fatal-error.js) @@ -449,7 +449,6 @@ Total: 2927 - [parallel/test-console-not-call-toString.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-console-not-call-toString.js) - [parallel/test-console-self-assign.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-console-self-assign.js) - [parallel/test-console-stdio-setters.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-console-stdio-setters.js) -- [parallel/test-console-tty-colors.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-console-tty-colors.js) - [parallel/test-console.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-console.js) - [parallel/test-constants.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-constants.js) - [parallel/test-corepack-version.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-corepack-version.js) @@ -1889,7 +1888,6 @@ Total: 2927 - [parallel/test-readline-csi.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-csi.js) - [parallel/test-readline-input-onerror.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-input-onerror.js) - [parallel/test-readline-interface.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-interface.js) -- [parallel/test-readline-position.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-position.js) - [parallel/test-readline-promises-interface.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-promises-interface.js) - [parallel/test-readline-promises-tab-complete.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-promises-tab-complete.js) - [parallel/test-readline-tab-complete.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-readline-tab-complete.js) @@ -2100,7 +2098,6 @@ Total: 2927 - [parallel/test-stream-writable-samecb-singletick.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream-writable-samecb-singletick.js) - [parallel/test-stream2-finish-pipe-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream2-finish-pipe-error.js) - [parallel/test-stream2-httpclient-response-end.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream2-httpclient-response-end.js) -- [parallel/test-stream2-readable-from-list.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-stream2-readable-from-list.js) - [parallel/test-string-decoder-end.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-string-decoder-end.js) - [parallel/test-string-decoder-fuzz.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-string-decoder-fuzz.js) - [parallel/test-string-decoder.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-string-decoder.js) |