diff --git a/index.js b/index.js index 050a4a6..aae69b2 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,10 @@ var booleanValueOf = Boolean.prototype.valueOf; var objectToString = Object.prototype.toString; var functionToString = Function.prototype.toString; var match = String.prototype.match; +var $slice = String.prototype.slice; +var $replace = String.prototype.replace; var test = RegExp.prototype.test; +var $floor = Math.floor; var bigIntValueOf = typeof BigInt === 'function' ? BigInt.prototype.valueOf : null; var gOPS = Object.getOwnPropertySymbols; var symToString = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? Symbol.prototype.toString : null; @@ -35,6 +38,28 @@ var gPO = (typeof Reflect === 'function' ? Reflect.getPrototypeOf : Object.getPr : null ); +function addNumericSeparator(num, str) { + if ( + num === Infinity + || num === -Infinity + || num !== num + || (num && num > -1000 && num < 1000) + || test.call(/e/, str) + ) { + return str; + } + var sepRegex = /[0-9](?=(?:[0-9]{3})+(?![0-9]))/g; + if (typeof num === 'number') { + var int = num < 0 ? -$floor(-num) : $floor(num); // trunc(num) + if (int !== num) { + var intStr = String(int); + var dec = $slice.call(str, intStr.length + 1); + return $replace.call(intStr, sepRegex, '$&_') + '.' + $replace.call($replace.call(dec, /([0-9]{3})/g, '$&_'), /_$/, ''); + } + } + return $replace.call(str, sepRegex, '$&_'); +} + var inspectCustom = require('./util.inspect').custom; var inspectSymbol = inspectCustom && isSymbol(inspectCustom) ? inspectCustom : null; @@ -63,8 +88,12 @@ module.exports = function inspect_(obj, options, depth, seen) { && 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`'); + throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`'); + } + if (has(opts, 'numericSeparator') && typeof opts.numericSeparator !== 'boolean') { + throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`'); } + var numericSeparator = opts.numericSeparator; if (typeof obj === 'undefined') { return 'undefined'; @@ -83,10 +112,12 @@ module.exports = function inspect_(obj, options, depth, seen) { if (obj === 0) { return Infinity / obj > 0 ? '0' : '-0'; } - return String(obj); + var str = String(obj); + return numericSeparator ? addNumericSeparator(obj, str) : str; } if (typeof obj === 'bigint') { - return String(obj) + 'n'; + var bigIntStr = String(obj) + 'n'; + return numericSeparator ? addNumericSeparator(obj, bigIntStr) : bigIntStr; } var maxDepth = typeof opts.depth === 'undefined' ? 5 : opts.depth; diff --git a/package.json b/package.json index 933c22a..32c4225 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "aud": "^1.1.5", "auto-changelog": "^2.3.0", "core-js": "^2.6.12", + "es-value-fixtures": "^1.2.1", "eslint": "^8.4.0", "for-each": "^0.3.3", "functions-have-names": "^1.2.2", diff --git a/readme.markdown b/readme.markdown index 4cbe08d..9ff6bec 100644 --- a/readme.markdown +++ b/readme.markdown @@ -55,6 +55,7 @@ Additional options: - `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 (either undere the `util.inspect.custom` symbol, or the `inspect` property). When the string `'symbol'`, only the symbol method will be invoked. Default `true`. - `indent`: must be "\t", `null`, or a positive integer. Default `null`. + - `numericSeparator`: must be a boolean, if present. Default `false`. If `true`, all numbers will be printed with numeric separators (eg, `1234.5678` will be printed as `'1_234.567_8'`) # install diff --git a/test/bigint.js b/test/bigint.js index 311b067..4ecc31d 100644 --- a/test/bigint.js +++ b/test/bigint.js @@ -42,5 +42,17 @@ test('bigint', { skip: typeof BigInt === 'undefined' }, function (t) { ); }); + t.test('numericSeparator', function (st) { + st.equal(inspect(BigInt(0), { numericSeparator: false }), '0n', '0n, numericSeparator false'); + st.equal(inspect(BigInt(0), { numericSeparator: true }), '0n', '0n, numericSeparator true'); + + st.equal(inspect(BigInt(1234), { numericSeparator: false }), '1234n', '1234n, numericSeparator false'); + st.equal(inspect(BigInt(1234), { numericSeparator: true }), '1_234n', '1234n, numericSeparator true'); + st.equal(inspect(BigInt(-1234), { numericSeparator: false }), '-1234n', '1234n, numericSeparator false'); + st.equal(inspect(BigInt(-1234), { numericSeparator: true }), '-1_234n', '1234n, numericSeparator true'); + + st.end(); + }); + t.end(); }); diff --git a/test/number.js b/test/number.js index 448304e..8f287e8 100644 --- a/test/number.js +++ b/test/number.js @@ -1,5 +1,8 @@ -var inspect = require('../'); var test = require('tape'); +var v = require('es-value-fixtures'); +var forEach = require('for-each'); + +var inspect = require('../'); test('negative zero', function (t) { t.equal(inspect(0), '0', 'inspect(0) === "0"'); @@ -10,3 +13,46 @@ test('negative zero', function (t) { t.end(); }); + +test('numericSeparator', function (t) { + forEach(v.nonBooleans, function (nonBoolean) { + t['throws']( + function () { inspect(true, { numericSeparator: nonBoolean }); }, + TypeError, + inspect(nonBoolean) + ' is not a boolean' + ); + }); + + t.test('3 digit numbers', function (st) { + var failed = false; + for (var i = -999; i < 1000; i += 1) { + var actual = inspect(i); + var actualSepNo = inspect(i, { numericSeparator: false }); + var actualSepYes = inspect(i, { numericSeparator: true }); + var expected = String(i); + if (actual !== expected || actualSepNo !== expected || actualSepYes !== expected) { + failed = true; + t.equal(actual, expected); + t.equal(actualSepNo, expected); + t.equal(actualSepYes, expected); + } + } + + st.notOk(failed, 'all 3 digit numbers passed'); + + st.end(); + }); + + t.equal(inspect(1e3), '1000', '1000'); + t.equal(inspect(1e3, { numericSeparator: false }), '1000', '1000, numericSeparator false'); + t.equal(inspect(1e3, { numericSeparator: true }), '1_000', '1000, numericSeparator true'); + t.equal(inspect(-1e3), '-1000', '-1000'); + t.equal(inspect(-1e3, { numericSeparator: false }), '-1000', '-1000, numericSeparator false'); + t.equal(inspect(-1e3, { numericSeparator: true }), '-1_000', '-1000, numericSeparator true'); + + t.equal(inspect(1234.5678, { numericSeparator: true }), '1_234.567_8', 'fractional numbers get separators'); + t.equal(inspect(1234.56789, { numericSeparator: true }), '1_234.567_89', 'fractional numbers get separators'); + t.equal(inspect(1234.567891, { numericSeparator: true }), '1_234.567_891', 'fractional numbers get separators'); + + t.end(); +});