From 1247809c576b99695958ba97ca97c4b4c986ee13 Mon Sep 17 00:00:00 2001 From: Kohei Ueno Date: Mon, 9 May 2022 04:51:38 +0900 Subject: [PATCH] debugger: fix inconsistent inspector output of exec new Map() PR-URL: https://github.com/nodejs/node/pull/42423 Reviewed-By: Jan Krems Reviewed-By: Rich Trott --- lib/internal/debugger/inspect_repl.js | 144 ++++++++++++------ ...test-debugger-object-type-remote-object.js | 42 +++++ 2 files changed, 137 insertions(+), 49 deletions(-) create mode 100644 test/sequential/test-debugger-object-type-remote-object.js diff --git a/lib/internal/debugger/inspect_repl.js b/lib/internal/debugger/inspect_repl.js index 4b11023554e268..a2d83da4388bcf 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/sequential/test-debugger-object-type-remote-object.js b/test/sequential/test-debugger-object-type-remote-object.js new file mode 100644 index 00000000000000..7404eae3963447 --- /dev/null +++ b/test/sequential/test-debugger-object-type-remote-object.js @@ -0,0 +1,42 @@ +'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')]); + +(async () => { + await cli.waitForInitialBreak(); + await cli.waitForPrompt(); + await cli.command('exec new Date(0)'); + assert.match(cli.output, /1970-01-01T00:00:00\.000Z/); + await cli.command('exec null'); + assert.match(cli.output, /null/); + await cli.command('exec /regex/g'); + assert.match(cli.output, /\/regex\/g/); + await cli.command('exec new Map()'); + assert.match(cli.output, /Map\(0\) {}/); + await cli.command('exec new Map([["a",1],["b",2]])'); + assert.match(cli.output, /Map\(2\) { a => 1, b => 2 }/); + await cli.command('exec new Set()'); + assert.match(cli.output, /Set\(0\) {}/); + await cli.command('exec new Set([1,2])'); + assert.match(cli.output, /Set\(2\) { 1, 2 }/); + await cli.command('exec new Set([{a:1},new Set([1])])'); + assert.match(cli.output, /Set\(2\) { { a: 1 }, Set\(1\) { \.\.\. } }/); + await cli.command('exec a={}; a'); + assert.match(cli.output, /{}/); + await cli.command('exec a={a:1,b:{c:1}}; a'); + assert.match(cli.output, /{ a: 1, b: Object }/); + await cli.command('exec a=[]; a'); + assert.match(cli.output, /\[\]/); + await cli.command('exec a=[1,2]; a'); + assert.match(cli.output, /\[ 1, 2 \]/); +})() +.finally(() => cli.quit()) +.then(common.mustCall());