From 7203924fea26c1bcba9774a23a9f54c642efccd6 Mon Sep 17 00:00:00 2001 From: Greg Alexander Date: Mon, 31 Jul 2017 17:08:44 -0500 Subject: [PATCH] util: implement %o and %O as formatting specifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementing the %o and %O formatting specifiers for util.format. Based on discussion in issue, this specifier should just call util.inspect to format the value. PR-URL: https://github.com/nodejs/node/pull/14558 Fixes: https://github.com/nodejs/node/issues/14545 Reviewed-By: Refael Ackermann Reviewed-By: Roman Reiss Reviewed-By: Vse Mozhet Byt Reviewed-By: James M Snell Reviewed-By: Evan Lucas Reviewed-By: Timothy Gu Reviewed-By: Colin Ihrig Reviewed-By: Tobias Nießen Reviewed-By: Yuta Hiroto --- doc/api/util.md | 8 +++ lib/util.js | 11 +++++ test/parallel/test-util-format.js | 82 +++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) diff --git a/doc/api/util.md b/doc/api/util.md index 59f17ec6fea9ad..c03b3f769d78da 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -167,6 +167,14 @@ corresponding argument. Supported placeholders are: * `%f` - Floating point value. * `%j` - JSON. Replaced with the string `'[Circular]'` if the argument contains circular references. +* `%o` - Object. A string representation of an object + with generic JavaScript object formatting. + Similar to `util.inspect()` with options `{ showHidden: true, depth: 4, showProxy: true }`. + This will show the full object including non-enumerable symbols and properties. +* `%O` - Object. A string representation of an object + with generic JavaScript object formatting. + Similar to `util.inspect()` without options. + This will show the full object not including non-enumerable symbols and properties. * `%%` - single percent sign (`'%'`). This does not consume an argument. If the placeholder does not have a corresponding argument, the placeholder is diff --git a/lib/util.js b/lib/util.js index 9db58e5458d8db..44d708923a87ff 100644 --- a/lib/util.js +++ b/lib/util.js @@ -137,6 +137,17 @@ function format(f) { str += f.slice(lastPos, i); str += String(arguments[a++]); break; + case 79: // 'O' + if (lastPos < i) + str += f.slice(lastPos, i); + str += inspect(arguments[a++]); + break; + case 111: // 'o' + if (lastPos < i) + str += f.slice(lastPos, i); + str += inspect(arguments[a++], + { showHidden: true, depth: 4, showProxy: true }); + break; case 37: // '%' if (lastPos < i) str += f.slice(lastPos, i); diff --git a/test/parallel/test-util-format.js b/test/parallel/test-util-format.js index 4527bf40e441fa..649bb63942909f 100644 --- a/test/parallel/test-util-format.js +++ b/test/parallel/test-util-format.js @@ -101,6 +101,84 @@ 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 = { + foo: 'bar', + foobar: 1, + func: function() {} +}; +const nestedObj = { + foo: 'bar', + foobar: { + foo: 'bar', + func: function() {} + } +}; +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', obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }'); +assert.strictEqual( + util.format('%o', nestedObj), + '{ foo: \'bar\',\n' + + ' foobar: \n' + + ' { foo: \'bar\',\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } } }'); +assert.strictEqual( + util.format('%o %o', obj, obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }' + + ' { foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } }'); +assert.strictEqual( + util.format('%o %o', obj), + '{ foo: \'bar\',\n' + + ' foobar: 1,\n' + + ' func: \n' + + ' { [Function: func]\n' + + ' [length]: 0,\n' + + ' [name]: \'func\',\n' + + ' [prototype]: func { [constructor]: [Circular] } } } %o'); + +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', obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +assert.strictEqual( + util.format('%O', nestedObj), + '{ foo: \'bar\', foobar: { foo: \'bar\', func: [Function: func] } }'); +assert.strictEqual( + util.format('%O %O', obj, obj), + '{ foo: \'bar\', foobar: 1, func: [Function: func] } ' + + '{ foo: \'bar\', foobar: 1, func: [Function: func] }'); +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'); assert.strictEqual(util.format('%s:%s'), '%s:%s'); @@ -125,6 +203,10 @@ assert.strictEqual(util.format('%f:%f'), '%f:%f'); 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'); +assert.strictEqual(util.format('o: %o, a: %O', {}, []), 'o: {}, a: []'); +assert.strictEqual(util.format('o: %o, a: %o', {}), 'o: {}, a: %o'); +assert.strictEqual(util.format('o: %O, a: %O'), 'o: %O, a: %O'); + // Invalid format specifiers assert.strictEqual(util.format('a% b', 'x'), 'a% b x');