diff --git a/lib/util.js b/lib/util.js index 7b2bfd2b3c0d56..41c5b445cbea8d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -3,6 +3,7 @@ const uv = process.binding('uv'); const Buffer = require('buffer').Buffer; const internalUtil = require('internal/util'); +const binding = process.binding('util'); var Debug; var ObjectIsPromise; @@ -292,7 +293,10 @@ function formatValue(ctx, value, recurseTimes) { var base = '', empty = false, braces, formatter; if (Array.isArray(value)) { - if (constructor === Array) + // We can't use `constructor === Array` because this could + // have come from a Debug context. + // Otherwise, an Array will print "Array [...]". + if (constructor && constructor.name === 'Array') constructor = null; braces = ['[', ']']; empty = value.length === 0; @@ -320,11 +324,23 @@ function formatValue(ctx, value, recurseTimes) { braces = ['{', '}']; formatter = formatPromise; } else { - if (constructor === Object) - constructor = null; - braces = ['{', '}']; - empty = true; // No other data than keys. - formatter = formatObject; + if (binding.isMapIterator(value)) { + constructor = { name: 'MapIterator' }; + braces = ['{', '}']; + empty = false; + formatter = formatCollectionIterator; + } else if (binding.isSetIterator(value)) { + constructor = { name: 'SetIterator' }; + braces = ['{', '}']; + empty = false; + formatter = formatCollectionIterator; + } else { + if (constructor === Object) + constructor = null; + braces = ['{', '}']; + empty = true; // No other data than keys. + formatter = formatObject; + } } } @@ -498,6 +514,18 @@ function formatMap(ctx, value, recurseTimes, visibleKeys, keys) { return output; } +function formatCollectionIterator(ctx, value, recurseTimes, visibleKeys, keys) { + ensureDebugIsInitialized(); + const mirror = Debug.MakeMirror(value, true); + var nextRecurseTimes = recurseTimes === null ? null : recurseTimes - 1; + var vals = mirror.preview(); + var output = []; + for (let o of vals) { + output.push(formatValue(ctx, o, nextRecurseTimes)); + } + return output; +} + function formatPromise(ctx, value, recurseTimes, visibleKeys, keys) { var output = []; var internals = inspectPromise(value); diff --git a/node.gyp b/node.gyp index 9575f4cbcd4a40..ece9eeec2c2f73 100644 --- a/node.gyp +++ b/node.gyp @@ -115,6 +115,7 @@ 'src/node_javascript.cc', 'src/node_main.cc', 'src/node_os.cc', + 'src/node_util.cc', 'src/node_v8.cc', 'src/node_stat_watcher.cc', 'src/node_watchdog.cc', diff --git a/src/node_util.cc b/src/node_util.cc new file mode 100644 index 00000000000000..cc86478e096b2f --- /dev/null +++ b/src/node_util.cc @@ -0,0 +1,38 @@ +#include "node.h" +#include "v8.h" +#include "env.h" +#include "env-inl.h" + +namespace node { +namespace util { + +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::Local; +using v8::Object; +using v8::Value; + +static void IsMapIterator(const FunctionCallbackInfo& args) { + CHECK_EQ(1, args.Length()); + args.GetReturnValue().Set(args[0]->IsMapIterator()); +} + + +static void IsSetIterator(const FunctionCallbackInfo& args) { + CHECK_EQ(1, args.Length()); + args.GetReturnValue().Set(args[0]->IsSetIterator()); +} + + +void Initialize(Local target, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + env->SetMethod(target, "isMapIterator", IsMapIterator); + env->SetMethod(target, "isSetIterator", IsSetIterator); +} + +} // namespace util +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(util, node::util::Initialize) diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 58a4c8a2f2b142..a706f495992c03 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -23,6 +23,19 @@ assert.equal(util.inspect(a), '[ \'foo\', , \'baz\' ]'); assert.equal(util.inspect(a, true), '[ \'foo\', , \'baz\', [length]: 3 ]'); assert.equal(util.inspect(new Array(5)), '[ , , , , ]'); +// test for Array constructor in different context +const Debug = require('vm').runInDebugContext('Debug'); +var map = new Map(); +map.set(1, 2); +var mirror = Debug.MakeMirror(map.entries(), true); +var vals = mirror.preview(); +var valsOutput = []; +for (let o of vals) { + valsOutput.push(o); +} + +assert.strictEqual(util.inspect(valsOutput), '[ [ 1, 2 ] ]'); + // test for property descriptors var getter = Object.create(null, { a: { @@ -274,6 +287,26 @@ global.Promise = function() { this.bar = 42; }; assert.equal(util.inspect(new Promise()), '{ bar: 42 }'); global.Promise = oldPromise; +// Map/Set Iterators +var m = new Map([['foo', 'bar']]); +assert.strictEqual(util.inspect(m.keys()), 'MapIterator { \'foo\' }'); +assert.strictEqual(util.inspect(m.values()), 'MapIterator { \'bar\' }'); +assert.strictEqual(util.inspect(m.entries()), + 'MapIterator { [ \'foo\', \'bar\' ] }'); +// make sure the iterator doesn't get consumed +var keys = m.keys(); +assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }'); +assert.strictEqual(util.inspect(keys), 'MapIterator { \'foo\' }'); + +var s = new Set([1, 3]); +assert.strictEqual(util.inspect(s.keys()), 'SetIterator { 1, 3 }'); +assert.strictEqual(util.inspect(s.values()), 'SetIterator { 1, 3 }'); +assert.strictEqual(util.inspect(s.entries()), + 'SetIterator { [ 1, 1 ], [ 3, 3 ] }'); +// make sure the iterator doesn't get consumed +keys = s.keys(); +assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }'); +assert.strictEqual(util.inspect(keys), 'SetIterator { 1, 3 }'); // Test alignment of items in container // Assumes that the first numeric character is the start of an item.