From 900d3d877d54cac76ac19d1e43d9dd00cd23efe9 Mon Sep 17 00:00:00 2001 From: mkls Date: Mon, 20 Feb 2017 22:50:19 +0100 Subject: [PATCH] [New] add `indent` option Fixes #27. --- .eslintrc | 8 +- index.js | 78 ++++++++++-- package.json | 1 + readme.markdown | 1 + test/indent-option.js | 271 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 346 insertions(+), 13 deletions(-) create mode 100644 test/indent-option.js diff --git a/.eslintrc b/.eslintrc index 6c0a4de..6ba656a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,12 +2,13 @@ "root": true, "extends": "@ljharb", "rules": { - "complexity": 0, - "func-style": [2, "declaration"], + "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, @@ -24,6 +25,7 @@ "files": ["test/**", "test-*", "example/**"], "rules": { "array-bracket-newline": 0, + "id-length": 0, "max-params": 0, "max-statements": 0, "max-statements-per-line": 0, diff --git a/index.js b/index.js index 53468fb..61176bb 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,15 @@ module.exports = function inspect_(obj, options, depth, seen) { throw new TypeError('option "customInspect", if provided, must be `true` or `false`'); } + if ( + has(opts, 'indent') + && opts.indent !== null + && opts.indent !== '\t' + && !(parseInt(opts.indent, 10) === opts.indent && opts.indent > 0) + ) { + throw new TypeError('options "indent" must be "\\t", an integer > 0, or `null`'); + } + if (typeof obj === 'undefined') { return 'undefined'; } @@ -67,17 +76,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); } @@ -102,7 +122,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); @@ -119,16 +143,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'); @@ -149,9 +173,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); }; @@ -299,8 +326,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/package.json b/package.json index a663bb1..3601993 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "aud": "^1.1.2", "core-js": "^2.6.11", "eslint": "^7.1.0", + "for-each": "^0.3.3", "nyc": "^10.3.2", "safe-publish-latest": "^1.1.4", "string.prototype.repeat": "^1.0.0", diff --git a/readme.markdown b/readme.markdown index b048625..61ec8e0 100644 --- a/readme.markdown +++ b/readme.markdown @@ -47,6 +47,7 @@ Additional options: - `quoteStyle`: must be "single" or "double", if present. Default `'single'` for strings, `'double'` for HTML elements. - `maxStringLength`: must be `0`, a positive integer, `Infinity`, or `null`, if present. Default `Infinity`. - `customInspect`: When `true`, a custom inspect method function will be invoked. Default `true`. + - `indent`: must be "\t", `null`, or a positive integer. Default `null`. # install diff --git a/test/indent-option.js b/test/indent-option.js new file mode 100644 index 0000000..89d8fce --- /dev/null +++ b/test/indent-option.js @@ -0,0 +1,271 @@ +var test = require('tape'); +var forEach = require('for-each'); + +var inspect = require('../'); + +test('bad indent options', function (t) { + forEach([ + undefined, + true, + false, + -1, + 1.2, + Infinity, + -Infinity, + NaN + ], function (indent) { + t['throws']( + function () { inspect('', { indent: indent }); }, + TypeError, + inspect(indent) + ' is invalid' + ); + }); + + t.end(); +}); + +test('simple object with indent', function (t) { + t.plan(2); + + var obj = { a: 1, b: 2 }; + + var expectedSpaces = [ + '{', + ' a: 1,', + ' b: 2', + '}' + ].join('\n'); + var expectedTabs = [ + '{', + ' a: 1,', + ' b: 2', + '}' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expectedSpaces, 'two'); + t.equal(inspect(obj, { indent: '\t' }), expectedTabs, 'tabs'); +}); + +test('two deep object with indent', function (t) { + t.plan(2); + + var obj = { a: 1, b: { c: 3, d: 4 } }; + + var expectedSpaces = [ + '{', + ' a: 1,', + ' b: {', + ' c: 3,', + ' d: 4', + ' }', + '}' + ].join('\n'); + var expectedTabs = [ + '{', + ' a: 1,', + ' b: {', + ' c: 3,', + ' d: 4', + ' }', + '}' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expectedSpaces, 'two'); + t.equal(inspect(obj, { indent: '\t' }), expectedTabs, 'tabs'); +}); + +test('simple array with all single line elements', function (t) { + t.plan(2); + + var obj = [1, 2, 3, 'asdf\nsdf']; + + var expected = '[ 1, 2, 3, \'asdf\\nsdf\' ]'; + + t.equal(inspect(obj, { indent: 2 }), expected, 'two'); + t.equal(inspect(obj, { indent: '\t' }), expected, 'tabs'); +}); + +test('array with complex elements', function (t) { + t.plan(2); + + var obj = [1, { a: 1, b: { c: 1 } }, 'asdf\nsdf']; + + var expectedSpaces = [ + '[', + ' 1,', + ' {', + ' a: 1,', + ' b: {', + ' c: 1', + ' }', + ' },', + ' \'asdf\\nsdf\'', + ']' + ].join('\n'); + var expectedTabs = [ + '[', + ' 1,', + ' {', + ' a: 1,', + ' b: {', + ' c: 1', + ' }', + ' },', + ' \'asdf\\nsdf\'', + ']' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expectedSpaces, 'two'); + t.equal(inspect(obj, { indent: '\t' }), expectedTabs, 'tabs'); +}); + +test('values', function (t) { + t.plan(2); + var obj = [{}, [], { 'a-b': 5 }]; + + var expectedSpaces = [ + '[', + ' {},', + ' [],', + ' {', + ' \'a-b\': 5', + ' }', + ']' + ].join('\n'); + var expectedTabs = [ + '[', + ' {},', + ' [],', + ' {', + ' \'a-b\': 5', + ' }', + ']' + ].join('\n'); + + t.equal(inspect(obj, { indent: 2 }), expectedSpaces, 'two'); + t.equal(inspect(obj, { indent: '\t' }), expectedTabs, 'tabs'); +}); + +test('Map', { skip: typeof Map !== 'function' }, function (t) { + var map = new Map(); + map.set({ a: 1 }, ['b']); + map.set(3, NaN); + + var expectedStringSpaces = [ + 'Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + '}' + ].join('\n'); + var expectedStringTabs = [ + 'Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + '}' + ].join('\n'); + var expectedStringTabsDoubleQuotes = [ + 'Map (2) {', + ' { a: 1 } => [ "b" ],', + ' 3 => NaN', + '}' + ].join('\n'); + + t.equal( + inspect(map, { indent: 2 }), + expectedStringSpaces, + 'Map keys are not indented (two)' + ); + t.equal( + inspect(map, { indent: '\t' }), + expectedStringTabs, + 'Map keys are not indented (tabs)' + ); + t.equal( + inspect(map, { indent: '\t', quoteStyle: 'double' }), + expectedStringTabsDoubleQuotes, + 'Map keys are not indented (tabs + double quotes)' + ); + + t.equal(inspect(new Map(), { indent: 2 }), 'Map (0) {}', 'empty Map should show as empty (two)'); + t.equal(inspect(new Map(), { indent: '\t' }), 'Map (0) {}', 'empty Map should show as empty (tabs)'); + + var nestedMap = new Map(); + nestedMap.set(nestedMap, map); + var expectedNestedSpaces = [ + 'Map (1) {', + ' [Circular] => Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + ' }', + '}' + ].join('\n'); + var expectedNestedTabs = [ + 'Map (1) {', + ' [Circular] => Map (2) {', + ' { a: 1 } => [ \'b\' ],', + ' 3 => NaN', + ' }', + '}' + ].join('\n'); + t.equal(inspect(nestedMap, { indent: 2 }), expectedNestedSpaces, 'Map containing a Map should work (two)'); + t.equal(inspect(nestedMap, { indent: '\t' }), expectedNestedTabs, 'Map containing a Map should work (tabs)'); + + t.end(); +}); + +test('Set', { skip: typeof Set !== 'function' }, function (t) { + var set = new Set(); + set.add({ a: 1 }); + set.add(['b']); + var expectedStringSpaces = [ + 'Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + '}' + ].join('\n'); + var expectedStringTabs = [ + 'Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + '}' + ].join('\n'); + t.equal(inspect(set, { indent: 2 }), expectedStringSpaces, 'new Set([{ a: 1 }, ["b"]]) should show size and contents (two)'); + t.equal(inspect(set, { indent: '\t' }), expectedStringTabs, 'new Set([{ a: 1 }, ["b"]]) should show size and contents (tabs)'); + + t.equal(inspect(new Set(), { indent: 2 }), 'Set (0) {}', 'empty Set should show as empty (two)'); + t.equal(inspect(new Set(), { indent: '\t' }), 'Set (0) {}', 'empty Set should show as empty (tabs)'); + + var nestedSet = new Set(); + nestedSet.add(set); + nestedSet.add(nestedSet); + var expectedNestedSpaces = [ + 'Set (2) {', + ' Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + ' },', + ' [Circular]', + '}' + ].join('\n'); + var expectedNestedTabs = [ + 'Set (2) {', + ' Set (2) {', + ' {', + ' a: 1', + ' },', + ' [ \'b\' ]', + ' },', + ' [Circular]', + '}' + ].join('\n'); + t.equal(inspect(nestedSet, { indent: 2 }), expectedNestedSpaces, 'Set containing a Set should work (two)'); + t.equal(inspect(nestedSet, { indent: '\t' }), expectedNestedTabs, 'Set containing a Set should work (tabs)'); + + t.end(); +});