diff --git a/.eslintrc b/.eslintrc index 6c0a4de..ab3696c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,9 +5,10 @@ "complexity": 0, "func-style": [2, "declaration"], "indent": [2, 4], + "max-lines": 1, "max-lines-per-function": 1, "max-params": [2, 4], - "max-statements": [2, 90], + "max-statements": [2, 100], "max-statements-per-line": [2, { "max": 2 }], "no-magic-numbers": 0, "no-param-reassign": 1, diff --git a/index.js b/index.js index 7a46d14..003daff 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,21 @@ module.exports = function inspect_(obj, options, depth, seen) { throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`'); } + if ( + has(opts, 'indent') + && ( + opts.indent !== '\t' + && !( + typeof opts.indent === 'number' + && isFinite(opts.indent) + && parseInt(opts.indent, 10) === opts.indent + && opts.indent >= 0 + ) + ) + ) { + throw new TypeError('options "indent" must be "\\t" or an integer 0 or greater'); + } + if (typeof obj === 'undefined') { return 'undefined'; } @@ -63,17 +78,28 @@ module.exports = function inspect_(obj, options, depth, seen) { return isArray(obj) ? '[Array]' : '[Object]'; } + var indent = getIndent(opts, depth); + if (typeof seen === 'undefined') { seen = []; } else if (indexOf(seen, obj) >= 0) { return '[Circular]'; } - function inspect(value, from) { + function inspect(value, from, noIndent) { if (from) { seen = seen.slice(); seen.push(from); } + if (noIndent) { + var newOpts = { + depth: opts.depth + }; + if (has(opts, 'quoteStyle')) { + newOpts.quoteStyle = opts.quoteStyle; + } + return inspect_(value, newOpts, depth + 1, seen); + } return inspect_(value, opts, depth + 1, seen); } @@ -98,7 +124,11 @@ module.exports = function inspect_(obj, options, depth, seen) { } if (isArray(obj)) { if (obj.length === 0) { return '[]'; } - return '[ ' + arrObjKeys(obj, inspect).join(', ') + ' ]'; + var xs = arrObjKeys(obj, inspect); + if (indent && !singleLineValues(xs)) { + return '[' + indentedJoin(xs, indent) + ']'; + } + return '[ ' + xs.join(', ') + ' ]'; } if (isError(obj)) { var parts = arrObjKeys(obj, inspect); @@ -115,16 +145,16 @@ module.exports = function inspect_(obj, options, depth, seen) { if (isMap(obj)) { var mapParts = []; mapForEach.call(obj, function (value, key) { - mapParts.push(inspect(key, obj) + ' => ' + inspect(value, obj)); + mapParts.push(inspect(key, obj, true) + ' => ' + inspect(value, obj)); }); - return collectionOf('Map', mapSize.call(obj), mapParts); + return collectionOf('Map', mapSize.call(obj), mapParts, indent); } if (isSet(obj)) { var setParts = []; setForEach.call(obj, function (value) { setParts.push(inspect(value, obj)); }); - return collectionOf('Set', setSize.call(obj), setParts); + return collectionOf('Set', setSize.call(obj), setParts, indent); } if (isWeakMap(obj)) { return weakCollectionOf('WeakMap'); @@ -145,9 +175,12 @@ module.exports = function inspect_(obj, options, depth, seen) { return markBoxed(inspect(String(obj))); } if (!isDate(obj) && !isRegExp(obj)) { - var xs = arrObjKeys(obj, inspect); - if (xs.length === 0) { return '{}'; } - return '{ ' + xs.join(', ') + ' }'; + var ys = arrObjKeys(obj, inspect); + if (ys.length === 0) { return '{}'; } + if (indent) { + return '{' + indentedJoin(ys, indent) + '}'; + } + return '{ ' + ys.join(', ') + ' }'; } return String(obj); }; @@ -295,8 +328,39 @@ function weakCollectionOf(type) { return type + ' { ? }'; } -function collectionOf(type, size, entries) { - return type + ' (' + size + ') {' + entries.join(', ') + '}'; +function collectionOf(type, size, entries, indent) { + var joinedEntries = indent ? indentedJoin(entries, indent) : entries.join(', '); + return type + ' (' + size + ') {' + joinedEntries + '}'; +} + +function singleLineValues(xs) { + for (var i = 0; i < xs.length; i++) { + if (indexOf(xs[i], '\n') >= 0) { + return false; + } + } + return true; +} + +function getIndent(opts, depth) { + var baseIndent; + if (opts.indent === '\t') { + baseIndent = '\t'; + } else if (typeof opts.indent === 'number' && opts.indent > 0) { + baseIndent = Array(opts.indent + 1).join(' '); + } else { + return null; + } + return { + base: baseIndent, + prev: Array(depth + 1).join(baseIndent) + }; +} + +function indentedJoin(xs, indent) { + if (xs.length === 0) { return ''; } + var lineJoiner = '\n' + indent.prev + indent.base; + return lineJoiner + xs.join(',' + lineJoiner) + '\n' + indent.prev; } function arrObjKeys(obj, inspect) { diff --git a/test/indent-option.js b/test/indent-option.js new file mode 100644 index 0000000..151206e --- /dev/null +++ b/test/indent-option.js @@ -0,0 +1,154 @@ +var test = require('tape'); + +var inspect = require('../'); + +test('simple object with indent', function (t) { + t.plan(1); + + var obj = { a: 1, b: 2 }; + + var expected = [ + '{', + ' a: 1,', + ' b: 2', + '}' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expected); +}); + +test('two deep object with indent', function (t) { + t.plan(1); + + var obj = { a: 1, b: { c: 3, d: 4 } }; + + var expected = [ + '{', + ' a: 1,', + ' b: {', + ' c: 3,', + ' d: 4', + ' }', + '}' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expected); +}); + +test('simple array with all single line elements', function (t) { + t.plan(1); + + var obj = [1, 2, 3, 'asdf\nsdf']; + + var expected = '[ 1, 2, 3, \'asdf\\nsdf\' ]'; + + t.equal(inspect(obj, { indent: 2 }), expected); +}); + +test('array with complex elements', function (t) { + t.plan(1); + + var obj = [1, { a: 1, b: { c: 1 } }, 'asdf\nsdf']; + + var expected = [ + '[', + ' 1,', + ' {', + ' a: 1,', + ' b: {', + ' c: 1', + ' }', + ' },', + ' \'asdf\\nsdf\'', + ']' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expected); +}); + +test('values', function (t) { + t.plan(1); + var obj = [{}, [], { 'a-b': 5 }]; + + var expected = [ + '[', + ' {},', + ' [],', + ' {', + ' \'a-b\': 5', + ' }', + ']' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expected); +}); + +test('Map', { skip: typeof Map !== 'function' }, function (t) { + var map = new Map(); + map.set({ a: 1 }, ['b']); + map.set(3, NaN); + + var expectedString = [ + 'Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + '}' + ].join('\n'); + + t.equal( + inspect(map, { indent: 2 }), + expectedString, + 'Map keys are not indented' + ); + + t.equal(inspect(new Map(), { indent: 2 }), 'Map (0) {}', 'empty Map should show as empty'); + + var nestedMap = new Map(); + nestedMap.set(nestedMap, map); + var expectedNested = [ + 'Map (1) {', + ' [Circular] => Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + ' }', + '}' + ].join('\n'); + t.equal(inspect(nestedMap, { indent: 2 }), expectedNested, 'Map containing a Map should work'); + + t.end(); +}); + +test('Set', { skip: typeof Set !== 'function' }, function (t) { + var set = new Set(); + set.add({ a: 1 }); + set.add(['b']); + var expectedString = [ + 'Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + '}' + ].join('\n'); + t.equal(inspect(set, { indent: 2 }), expectedString, 'new Set([{ a: 1 }, ["b"]]) should show size and contents'); + + t.equal(inspect(new Set(), { indent: 2 }), 'Set (0) {}', 'empty Set should show as empty'); + + var nestedSet = new Set(); + nestedSet.add(set); + nestedSet.add(nestedSet); + var expectedNested = [ + 'Set (2) {', + ' Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + ' },', + ' [Circular]', + '}' + ].join('\n'); + t.equal(inspect(nestedSet, { indent: 2 }), expectedNested, 'Set containing a Set should work'); + + t.end(); +});