diff --git a/lib/utils.js b/lib/utils.js index 1cfbbae781..3792cbafb2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -89,7 +89,7 @@ exports.map = function(arr, fn, scope) { * @param {number} start * @return {number} */ -exports.indexOf = function(arr, obj, start) { +var indexOf = exports.indexOf = function(arr, obj, start) { for (var i = start || 0, l = arr.length; i < l; i++) { if (arr[i] === obj) { return i; @@ -366,13 +366,11 @@ exports.highlightTags = function(name) { * * @api private * @param {*} value The value to inspect. - * @param {string} [type] The type of the value, if known. + * @param {string} typeHint The type of the value * @returns {string} */ -function emptyRepresentation(value, type) { - type = type || exports.type(value); - - switch (type) { +function emptyRepresentation(value, typeHint) { + switch (typeHint) { case 'function': return '[Function]'; case 'object': @@ -391,7 +389,7 @@ function emptyRepresentation(value, type) { * @api private * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString * @param {*} value The value to test. - * @returns {string} + * @returns {string} Computed type * @example * type({}) // 'object' * type([]) // 'array' @@ -403,8 +401,9 @@ function emptyRepresentation(value, type) { * type(/foo/) // 'regexp' * type('type') // 'string' * type(global) // 'global' + * type(new String('foo') // 'object' */ -exports.type = function type(value) { +var type = exports.type = function type(value) { if (value === undefined) { return 'undefined'; } else if (value === null) { @@ -412,9 +411,14 @@ exports.type = function type(value) { } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { return 'buffer'; } - return Object.prototype.toString.call(value) + var toStringType = Object.prototype.toString.call(value) .replace(/^\[.+\s(.+?)\]$/, '$1') .toLowerCase(); + // special case for string created with String constructor; + // it's an Object on purpose and should be displayed as such + return toStringType === 'string' && typeof value === 'object' + ? 'object' + : toStringType; }; /** @@ -433,10 +437,10 @@ exports.type = function type(value) { * @return {string} */ exports.stringify = function(value) { - var type = exports.type(value); + var typeHint = type(value); - if (!~exports.indexOf(['object', 'array', 'function'], type)) { - if (type !== 'buffer') { + if (!~indexOf(['object', 'array', 'function'], typeHint)) { + if (typeHint !== 'buffer') { return jsonStringify(value); } var json = value.toJSON(); @@ -447,11 +451,11 @@ exports.stringify = function(value) { for (var prop in value) { if (Object.prototype.hasOwnProperty.call(value, prop)) { - return jsonStringify(exports.canonicalize(value), 2).replace(/,(\n|$)/g, '$1'); + return jsonStringify(exports.canonicalize(value, null, typeHint), 2).replace(/,(\n|$)/g, '$1'); } } - return emptyRepresentation(value, type); + return emptyRepresentation(value, typeHint); }; /** @@ -480,7 +484,7 @@ function jsonStringify(object, spaces, depth) { } function _stringify(val) { - switch (exports.type(val)) { + switch (type(val)) { case 'null': case 'undefined': val = '[' + val + ']'; @@ -563,14 +567,15 @@ exports.isBuffer = function(value) { * @see {@link exports.stringify} * @param {*} value Thing to inspect. May or may not have properties. * @param {Array} [stack=[]] Stack of seen values + * @param {string} [typeHint] Type hint * @return {(Object|Array|Function|string|undefined)} */ -exports.canonicalize = function(value, stack) { +exports.canonicalize = function canonicalize(value, stack, typeHint) { var canonicalizedObj; /* eslint-disable no-unused-vars */ var prop; /* eslint-enable no-unused-vars */ - var type = exports.type(value); + typeHint = typeHint || type(value); function withStack(value, fn) { stack.push(value); fn(); @@ -579,11 +584,11 @@ exports.canonicalize = function(value, stack) { stack = stack || []; - if (exports.indexOf(stack, value) !== -1) { + if (indexOf(stack, value) !== -1) { return '[Circular]'; } - switch (type) { + switch (typeHint) { case 'undefined': case 'buffer': case 'null': @@ -604,7 +609,7 @@ exports.canonicalize = function(value, stack) { } /* eslint-enable guard-for-in */ if (!canonicalizedObj) { - canonicalizedObj = emptyRepresentation(value, type); + canonicalizedObj = emptyRepresentation(value, typeHint); break; } /* falls through */ diff --git a/test/acceptance/utils.spec.js b/test/acceptance/utils.spec.js index a2383a739f..cab0f42b01 100644 --- a/test/acceptance/utils.spec.js +++ b/test/acceptance/utils.spec.js @@ -77,6 +77,10 @@ describe('lib/utils', function () { var stringify = utils.stringify; + it('should return an object representation of a string created with a String constructor', function() { + expect(stringify(new String('foo'))).to.equal('{\n "0": "f"\n "1": "o"\n "2": "o"\n}'); + }); + it('should return Buffer with .toJSON representation', function() { expect(stringify(new Buffer([0x01]))).to.equal('[\n 1\n]'); expect(stringify(new Buffer([0x01, 0x02]))).to.equal('[\n 1\n 2\n]');