mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
util: add numericSeparator to util.inspect
This adds the `numericSeparator` option to util.inspect. Using it separates numbers by thousands adding the underscore accordingly. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de> PR-URL: https://github.com/nodejs/node/pull/41003 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
1 parent
3f51f052ec
commit
85764c0e1a
4 files changed
+194
-23
No files matched your search
@@ -485,6 +485,9 @@ stream.write('With ES6');
|
||||
<!-- YAML
|
||||
added: v0.3.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/41003
|
||||
description: The `numericSeparator` option is supported now.
|
||||
- version:
|
||||
- v14.6.0
|
||||
- v12.19.0
|
||||
@@ -606,6 +609,9 @@ changes:
|
||||
set to `'set'`, only getters with a corresponding setter are inspected.
|
||||
This might cause side effects depending on the getter function.
|
||||
**Default:** `false`.
|
||||
* `numericSeparator` {boolean} If set to `true`, an underscore is used to
|
||||
separate every three digits in all bigints and numbers.
|
||||
**Default:** `false`.
|
||||
* Returns: {string} The representation of `object`.
|
||||
|
||||
The `util.inspect()` method returns a string representation of `object` that is
|
||||
@@ -754,6 +760,21 @@ assert.strict.equal(
|
||||
);
|
||||
```
|
||||
|
||||
The `numericSeparator` option adds an underscore every three digits to all
|
||||
numbers.
|
||||
|
||||
```js
|
||||
const { inspect } = require('util');
|
||||
|
||||
const thousand = 1_000;
|
||||
const million = 1_000_000;
|
||||
const bigNumber = 123_456_789n;
|
||||
const bigDecimal = 1_234.123_45;
|
||||
|
||||
console.log(thousand, million, bigNumber, bigDecimal);
|
||||
// 1_000 1_000_000 123_456_789n 1_234.123_45
|
||||
```
|
||||
|
||||
`util.inspect()` is a synchronous method intended for debugging. Its maximum
|
||||
output length is approximately 128 MB. Inputs that result in longer output will
|
||||
be truncated.
|
||||
|
||||
@@ -26,7 +26,9 @@ const {
|
||||
MathMin,
|
||||
MathRound,
|
||||
MathSqrt,
|
||||
MathTrunc,
|
||||
Number,
|
||||
NumberIsFinite,
|
||||
NumberIsNaN,
|
||||
NumberParseFloat,
|
||||
NumberParseInt,
|
||||
@@ -168,7 +170,8 @@ const inspectDefaultOptions = ObjectSeal({
|
||||
breakLength: 80,
|
||||
compact: 3,
|
||||
sorted: false,
|
||||
getters: false
|
||||
getters: false,
|
||||
numericSeparator: false,
|
||||
});
|
||||
|
||||
const kObjectType = 0;
|
||||
@@ -244,6 +247,7 @@ function getUserOptions(ctx, isCrossContext) {
|
||||
compact: ctx.compact,
|
||||
sorted: ctx.sorted,
|
||||
getters: ctx.getters,
|
||||
numericSeparator: ctx.numericSeparator,
|
||||
...ctx.userOptions
|
||||
};
|
||||
|
||||
@@ -301,7 +305,8 @@ function inspect(value, opts) {
|
||||
breakLength: inspectDefaultOptions.breakLength,
|
||||
compact: inspectDefaultOptions.compact,
|
||||
sorted: inspectDefaultOptions.sorted,
|
||||
getters: inspectDefaultOptions.getters
|
||||
getters: inspectDefaultOptions.getters,
|
||||
numericSeparator: inspectDefaultOptions.numericSeparator,
|
||||
};
|
||||
if (arguments.length > 1) {
|
||||
// Legacy...
|
||||
@@ -949,7 +954,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
|
||||
formatter = formatArrayBuffer;
|
||||
} else if (keys.length === 0 && protoProps === undefined) {
|
||||
return prefix +
|
||||
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
|
||||
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`;
|
||||
}
|
||||
braces[0] = `${prefix}{`;
|
||||
ArrayPrototypeUnshift(keys, 'byteLength');
|
||||
@@ -1434,13 +1439,61 @@ function handleMaxCallStackSize(ctx, err, constructorName, indentationLvl) {
|
||||
assert.fail(err.stack);
|
||||
}
|
||||
|
||||
function formatNumber(fn, value) {
|
||||
// Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0.
|
||||
return fn(ObjectIs(value, -0) ? '-0' : `${value}`, 'number');
|
||||
function addNumericSeparator(integerString) {
|
||||
let result = '';
|
||||
let i = integerString.length;
|
||||
const start = integerString.startsWith('-') ? 1 : 0;
|
||||
for (; i >= start + 4; i -= 3) {
|
||||
result = `_${integerString.slice(i - 3, i)}${result}`;
|
||||
}
|
||||
return i === integerString.length ?
|
||||
integerString :
|
||||
`${integerString.slice(0, i)}${result}`;
|
||||
}
|
||||
|
||||
function formatBigInt(fn, value) {
|
||||
return fn(`${value}n`, 'bigint');
|
||||
function addNumericSeparatorEnd(integerString) {
|
||||
let result = '';
|
||||
let i = 0;
|
||||
for (; i < integerString.length - 3; i += 3) {
|
||||
result += `${integerString.slice(i, i + 3)}_`;
|
||||
}
|
||||
return i === 0 ?
|
||||
integerString :
|
||||
`${result}${integerString.slice(i)}`;
|
||||
}
|
||||
|
||||
function formatNumber(fn, number, numericSeparator) {
|
||||
if (!numericSeparator) {
|
||||
// Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.
|
||||
if (ObjectIs(number, -0)) {
|
||||
return fn('-0', 'number');
|
||||
}
|
||||
return fn(`${number}`, 'number');
|
||||
}
|
||||
const integer = MathTrunc(number);
|
||||
const string = String(integer);
|
||||
if (integer === number) {
|
||||
if (!NumberIsFinite(number) || string.includes('e')) {
|
||||
return fn(string, 'number');
|
||||
}
|
||||
return fn(`${addNumericSeparator(string)}`, 'number');
|
||||
}
|
||||
if (NumberIsNaN(number)) {
|
||||
return fn(string, 'number');
|
||||
}
|
||||
return fn(`${
|
||||
addNumericSeparator(string)
|
||||
}.${
|
||||
addNumericSeparatorEnd(String(number).slice(string.length + 1))
|
||||
}`, 'number');
|
||||
}
|
||||
|
||||
function formatBigInt(fn, bigint, numericSeparator) {
|
||||
const string = String(bigint);
|
||||
if (!numericSeparator) {
|
||||
return fn(`${string}n`, 'bigint');
|
||||
}
|
||||
return fn(`${addNumericSeparator(string)}n`, 'bigint');
|
||||
}
|
||||
|
||||
function formatPrimitive(fn, value, ctx) {
|
||||
@@ -1464,9 +1517,9 @@ function formatPrimitive(fn, value, ctx) {
|
||||
return fn(strEscape(value), 'string') + trailer;
|
||||
}
|
||||
if (typeof value === 'number')
|
||||
return formatNumber(fn, value);
|
||||
return formatNumber(fn, value, ctx.numericSeparator);
|
||||
if (typeof value === 'bigint')
|
||||
return formatBigInt(fn, value);
|
||||
return formatBigInt(fn, value, ctx.numericSeparator);
|
||||
if (typeof value === 'boolean')
|
||||
return fn(`${value}`, 'boolean');
|
||||
if (typeof value === 'undefined')
|
||||
@@ -1583,8 +1636,9 @@ function formatTypedArray(value, length, ctx, ignored, recurseTimes) {
|
||||
const elementFormatter = value.length > 0 && typeof value[0] === 'number' ?
|
||||
formatNumber :
|
||||
formatBigInt;
|
||||
for (let i = 0; i < maxLength; ++i)
|
||||
output[i] = elementFormatter(ctx.stylize, value[i]);
|
||||
for (let i = 0; i < maxLength; ++i) {
|
||||
output[i] = elementFormatter(ctx.stylize, value[i], ctx.numericSeparator);
|
||||
}
|
||||
if (remaining > 0) {
|
||||
output[maxLength] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
|
||||
}
|
||||
@@ -1928,8 +1982,8 @@ function tryStringify(arg) {
|
||||
if (!CIRCULAR_ERROR_MESSAGE) {
|
||||
try {
|
||||
const a = {}; a.a = a; JSONStringify(a);
|
||||
} catch (err) {
|
||||
CIRCULAR_ERROR_MESSAGE = firstErrorLine(err);
|
||||
} catch (circularError) {
|
||||
CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);
|
||||
}
|
||||
}
|
||||
if (err.name === 'TypeError' &&
|
||||
@@ -1952,6 +2006,22 @@ function formatWithOptions(inspectOptions, ...args) {
|
||||
return formatWithOptionsInternal(inspectOptions, args);
|
||||
}
|
||||
|
||||
function formatNumberNoColor(number, options) {
|
||||
return formatNumber(
|
||||
stylizeNoColor,
|
||||
number,
|
||||
options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
|
||||
);
|
||||
}
|
||||
|
||||
function formatBigIntNoColor(bigint, options) {
|
||||
return formatBigInt(
|
||||
stylizeNoColor,
|
||||
bigint,
|
||||
options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
|
||||
);
|
||||
}
|
||||
|
||||
function formatWithOptionsInternal(inspectOptions, args) {
|
||||
const first = args[0];
|
||||
let a = 0;
|
||||
@@ -1973,9 +2043,9 @@ function formatWithOptionsInternal(inspectOptions, args) {
|
||||
case 115: // 's'
|
||||
const tempArg = args[++a];
|
||||
if (typeof tempArg === 'number') {
|
||||
tempStr = formatNumber(stylizeNoColor, tempArg);
|
||||
tempStr = formatNumberNoColor(tempArg, inspectOptions);
|
||||
} else if (typeof tempArg === 'bigint') {
|
||||
tempStr = `${tempArg}n`;
|
||||
tempStr = formatBigIntNoColor(tempArg, inspectOptions);
|
||||
} else if (typeof tempArg !== 'object' ||
|
||||
tempArg === null ||
|
||||
!hasBuiltInToString(tempArg)) {
|
||||
@@ -1995,11 +2065,11 @@ function formatWithOptionsInternal(inspectOptions, args) {
|
||||
case 100: // 'd'
|
||||
const tempNum = args[++a];
|
||||
if (typeof tempNum === 'bigint') {
|
||||
tempStr = `${tempNum}n`;
|
||||
tempStr = formatBigIntNoColor(tempNum, inspectOptions);
|
||||
} else if (typeof tempNum === 'symbol') {
|
||||
tempStr = 'NaN';
|
||||
} else {
|
||||
tempStr = formatNumber(stylizeNoColor, Number(tempNum));
|
||||
tempStr = formatNumberNoColor(Number(tempNum), inspectOptions);
|
||||
}
|
||||
break;
|
||||
case 79: // 'O'
|
||||
@@ -2016,12 +2086,12 @@ function formatWithOptionsInternal(inspectOptions, args) {
|
||||
case 105: // 'i'
|
||||
const tempInteger = args[++a];
|
||||
if (typeof tempInteger === 'bigint') {
|
||||
tempStr = `${tempInteger}n`;
|
||||
tempStr = formatBigIntNoColor(tempInteger, inspectOptions);
|
||||
} else if (typeof tempInteger === 'symbol') {
|
||||
tempStr = 'NaN';
|
||||
} else {
|
||||
tempStr = formatNumber(stylizeNoColor,
|
||||
NumberParseInt(tempInteger));
|
||||
tempStr = formatNumberNoColor(
|
||||
NumberParseInt(tempInteger), inspectOptions);
|
||||
}
|
||||
break;
|
||||
case 102: // 'f'
|
||||
@@ -2029,8 +2099,8 @@ function formatWithOptionsInternal(inspectOptions, args) {
|
||||
if (typeof tempFloat === 'symbol') {
|
||||
tempStr = 'NaN';
|
||||
} else {
|
||||
tempStr = formatNumber(stylizeNoColor,
|
||||
NumberParseFloat(tempFloat));
|
||||
tempStr = formatNumberNoColor(
|
||||
NumberParseFloat(tempFloat), inspectOptions);
|
||||
}
|
||||
break;
|
||||
case 99: // 'c'
|
||||
|
||||
@@ -74,6 +74,37 @@ assert.strictEqual(
|
||||
'1180591620717411303424n 12345678901234567890123n'
|
||||
);
|
||||
|
||||
{
|
||||
const { numericSeparator } = util.inspect.defaultOptions;
|
||||
util.inspect.defaultOptions.numericSeparator = true;
|
||||
|
||||
assert.strictEqual(
|
||||
util.format('%d', 1180591620717411303424),
|
||||
'1.1805916207174113e+21'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
util.format(
|
||||
'%d %s %i', 118059162071741130342, 118059162071741130342, 123_123_123),
|
||||
'118_059_162_071_741_140_000 118_059_162_071_741_140_000 123_123_123'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
util.format(
|
||||
'%d %s',
|
||||
1_180_591_620_717_411_303_424n,
|
||||
12_345_678_901_234_567_890_123n
|
||||
),
|
||||
'1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
util.format('%i', 1_180_591_620_717_411_303_424n),
|
||||
'1_180_591_620_717_411_303_424n'
|
||||
);
|
||||
|
||||
util.inspect.defaultOptions.numericSeparator = numericSeparator;
|
||||
}
|
||||
// Integer format specifier
|
||||
assert.strictEqual(util.format('%i'), '%i');
|
||||
assert.strictEqual(util.format('%i', 42.0), '42');
|
||||
|
||||
@@ -3104,3 +3104,52 @@ assert.strictEqual(
|
||||
"{ ['__proto__']: { a: 1 } }"
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { numericSeparator } = util.inspect.defaultOptions;
|
||||
util.inspect.defaultOptions.numericSeparator = true;
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(1234567891234567891234),
|
||||
'1.234567891234568e+21'
|
||||
);
|
||||
assert.strictEqual(
|
||||
util.inspect(123456789.12345678),
|
||||
'123_456_789.123_456_78'
|
||||
);
|
||||
|
||||
assert.strictEqual(util.inspect(10_000_000), '10_000_000');
|
||||
assert.strictEqual(util.inspect(1_000_000), '1_000_000');
|
||||
assert.strictEqual(util.inspect(100_000), '100_000');
|
||||
assert.strictEqual(util.inspect(99_999.9), '99_999.9');
|
||||
assert.strictEqual(util.inspect(9_999), '9_999');
|
||||
assert.strictEqual(util.inspect(999), '999');
|
||||
assert.strictEqual(util.inspect(NaN), 'NaN');
|
||||
assert.strictEqual(util.inspect(Infinity), 'Infinity');
|
||||
assert.strictEqual(util.inspect(-Infinity), '-Infinity');
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(new Float64Array([100_000_000])),
|
||||
'Float64Array(1) [ 100_000_000 ]'
|
||||
);
|
||||
assert.strictEqual(
|
||||
util.inspect(new BigInt64Array([9_100_000_100n])),
|
||||
'BigInt64Array(1) [ 9_100_000_100n ]'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(123456789),
|
||||
'123_456_789'
|
||||
);
|
||||
assert.strictEqual(
|
||||
util.inspect(123456789n),
|
||||
'123_456_789n'
|
||||
);
|
||||
|
||||
util.inspect.defaultOptions.numericSeparator = numericSeparator;
|
||||
|
||||
assert.strictEqual(
|
||||
util.inspect(123456789.12345678, { numericSeparator: true }),
|
||||
'123_456_789.123_456_78'
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user