From a52f32fa7827e7a536ab9de389bc2d1c06d780f2 Mon Sep 17 00:00:00 2001 From: cola119 Date: Mon, 21 Mar 2022 11:14:58 +0900 Subject: [PATCH] debugger: Fix inconsistent inspector output of exec new Map() --- lib/internal/debugger/inspect_repl.js | 144 ++++++++++++------ ...test-debugger-object-type-remote-object.js | 45 ++++++ 2 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 test/parallel/test-debugger-object-type-remote-object.js diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index a32099a1dbf88e..a333d16faf8506 100644 --- a/lib/internal/debugger/inspect_repl.js +++ b/lib/internal/debugger/inspect_repl.js @@ -183,92 +183,138 @@ function convertResultToError(result) { return err; } -class RemoteObject { +class PropertyPreview { constructor(attributes) { ObjectAssign(this, attributes); - if (this.type === 'number') { - this.value = - this.unserializableValue ? +this.unserializableValue : +this.value; + } + + [customInspectSymbol](depth, opts) { + switch (this.type) { + case 'string': + case 'undefined': + return utilInspect(this.value, opts); + case 'number': + case 'boolean': + return opts.stylize(this.value, this.type); + case 'object': + case 'symbol': + if (this.subtype === 'date') { + return utilInspect(new Date(this.value), opts); + } + if (this.subtype === 'array') { + return opts.stylize(this.value, 'special'); + } + return opts.stylize(this.value, this.subtype || 'special'); + default: + return this.value; } } +} + +class ObjectPreview { + constructor(attributes) { + ObjectAssign(this, attributes); + } [customInspectSymbol](depth, opts) { - function formatProperty(prop) { - switch (prop.type) { - case 'string': - case 'undefined': - return utilInspect(prop.value, opts); - - case 'number': - case 'boolean': - return opts.stylize(prop.value, prop.type); - - case 'object': - case 'symbol': - if (prop.subtype === 'date') { - return utilInspect(new Date(prop.value), opts); + switch (this.type) { + case 'object': { + switch (this.subtype) { + case 'date': + return utilInspect(new Date(this.description), opts); + case 'null': + return utilInspect(null, opts); + case 'regexp': + return opts.stylize(this.description, 'regexp'); + case 'set': { + if (!this.entries) { + return `${this.description} ${this.overflow ? '{ ... }' : '{}'}`; + } + const values = ArrayPrototypeMap(this.entries, (entry) => + utilInspect(new ObjectPreview(entry.value), opts)); + return `${this.description} { ${ArrayPrototypeJoin(values, ', ')} }`; } - if (prop.subtype === 'array') { - return opts.stylize(prop.value, 'special'); + case 'map': { + if (!this.entries) { + return `${this.description} ${this.overflow ? '{ ... }' : '{}'}`; + } + const mappings = ArrayPrototypeMap(this.entries, (entry) => { + const key = utilInspect(new ObjectPreview(entry.key), opts); + const value = utilInspect(new ObjectPreview(entry.value), opts); + return `${key} => ${value}`; + }); + return `${this.description} { ${ArrayPrototypeJoin(mappings, ', ')} }`; } - return opts.stylize(prop.value, prop.subtype || 'special'); - - default: - return prop.value; + case 'array': + case undefined: { + if (this.properties.length === 0) { + return this.subtype === 'array' ? '[]' : '{}'; + } + const props = ArrayPrototypeMap(this.properties, (prop, idx) => { + const value = utilInspect(new PropertyPreview(prop)); + if (prop.name === `${idx}`) return value; + return `${prop.name}: ${value}`; + }); + if (this.overflow) { + ArrayPrototypePush(props, '...'); + } + const singleLine = ArrayPrototypeJoin(props, ', '); + const propString = singleLine.length > 60 ? ArrayPrototypeJoin(props, ',\n ') : singleLine; + return this.subtype === 'array' ? `[ ${propString} ]` : `{ ${propString} }`; + } + default: + return this.description; + } } + default: + return this.description; } + } +} + +class RemoteObject { + constructor(attributes) { + ObjectAssign(this, attributes); + if (this.type === 'number') { + this.value = + this.unserializableValue ? +this.unserializableValue : +this.value; + } + } + + [customInspectSymbol](depth, opts) { switch (this.type) { case 'boolean': case 'number': case 'string': case 'undefined': return utilInspect(this.value, opts); - case 'symbol': return opts.stylize(this.description, 'special'); - case 'function': { const fnName = extractFunctionName(this.description); const formatted = `[${this.className}${fnName}]`; return opts.stylize(formatted, 'special'); } - case 'object': switch (this.subtype) { case 'date': return utilInspect(new Date(this.description), opts); - case 'null': return utilInspect(null, opts); - case 'regexp': return opts.stylize(this.description, 'regexp'); - + case 'map': + case 'set': { + const preview = utilInspect(new ObjectPreview(this.preview), opts); + return `${this.description} ${preview}`; + } default: break; } if (this.preview) { - const props = ArrayPrototypeMap( - this.preview.properties, - (prop, idx) => { - const value = formatProperty(prop); - if (prop.name === `${idx}`) return value; - return `${prop.name}: ${value}`; - }); - if (this.preview.overflow) { - ArrayPrototypePush(props, '...'); - } - const singleLine = ArrayPrototypeJoin(props, ', '); - const propString = - singleLine.length > 60 ? - ArrayPrototypeJoin(props, ',\n ') : - singleLine; - - return this.subtype === 'array' ? - `[ ${propString} ]` : `{ ${propString} }`; + return utilInspect(new ObjectPreview(this.preview), opts); } return this.description; - default: return this.description; } diff --git a/test/parallel/test-debugger-object-type-remote-object.js b/test/parallel/test-debugger-object-type-remote-object.js new file mode 100644 index 00000000000000..32b0b89d33b2ac --- /dev/null +++ b/test/parallel/test-debugger-object-type-remote-object.js @@ -0,0 +1,45 @@ +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const startCLI = require('../common/debugger'); + +const assert = require('assert'); + +const cli = startCLI([fixtures.path('debugger/empty.js')]); + +function onFatal(error) { + cli.quit(); + throw error; +} + +cli.waitForInitialBreak() + .then(() => cli.waitForPrompt()) + .then(() => cli.command('exec new Date(0)')) + .then(() => assert.match(cli.output, /1970-01-01T00:00:00\.000Z/)) + .then(() => cli.command('exec null')) + .then(() => assert.match(cli.output, /null/)) + .then(() => cli.command('exec /regex/g')) + .then(() => assert.match(cli.output, /\/regex\/g/)) + .then(() => cli.command('exec new Map()')) + .then(() => assert.match(cli.output, /Map\(0\) {}/)) + .then(() => cli.command('exec new Map([["a",1],["b",2]])')) + .then(() => assert.match(cli.output, /Map\(2\) { a => 1, b => 2 }/)) + .then(() => cli.command('exec new Set()')) + .then(() => assert.match(cli.output, /Set\(0\) {}/)) + .then(() => cli.command('exec new Set([1,2])')) + .then(() => assert.match(cli.output, /Set\(2\) { 1, 2 }/)) + .then(() => cli.command('exec new Set([{a:1},new Set([1])])')) + .then(() => assert.match(cli.output, /Set\(2\) { { a: 1 }, Set\(1\) { \.\.\. } }/)) + .then(() => cli.command('exec a={}; a')) + .then(() => assert.match(cli.output, /{}/)) + .then(() => cli.command('exec a={a:1,b:{c:1}}; a')) + .then(() => assert.match(cli.output, /{ a: 1, b: Object }/)) + .then(() => cli.command('exec a=[]; a')) + .then(() => assert.match(cli.output, /\[\]/)) + .then(() => cli.command('exec a=[1,2]; a')) + .then(() => assert.match(cli.output, /\[ 1, 2 \]/)) + .then(() => cli.quit()) + .then(null, onFatal);