Skip to content

Commit 069f005

Browse files
committed
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 <[email protected]>
1 parent 265a47d commit 069f005

File tree

4 files changed

+174
-23
lines changed

4 files changed

+174
-23
lines changed

doc/api/util.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ stream.write('With ES6');
485485
<!-- YAML
486486
added: v0.3.0
487487
changes:
488+
- version: REPLACEME
489+
pr-url: https://github.com/nodejs/node/pull/REPLACEME
490+
description: The `numericSeparator` option is supported now.
488491
- version:
489492
- v14.6.0
490493
- v12.19.0
@@ -606,6 +609,8 @@ changes:
606609
set to `'set'`, only getters with a corresponding setter are inspected.
607610
This might cause side effects depending on the getter function.
608611
**Default:** `false`.
612+
* `numericSeparator` {boolean} If set to `true`, a underscore is used to
613+
separate thousands in all bigint and numbers. **Default:** `false`.
609614
* Returns: {string} The representation of `object`.
610615

611616
The `util.inspect()` method returns a string representation of `object` that is

lib/internal/util/inspect.js

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ const inspectDefaultOptions = ObjectSeal({
168168
breakLength: 80,
169169
compact: 3,
170170
sorted: false,
171-
getters: false
171+
getters: false,
172+
numericSeparator: false,
172173
});
173174

174175
const kObjectType = 0;
@@ -244,6 +245,7 @@ function getUserOptions(ctx, isCrossContext) {
244245
compact: ctx.compact,
245246
sorted: ctx.sorted,
246247
getters: ctx.getters,
248+
numericSeparator: ctx.numericSeparator,
247249
...ctx.userOptions
248250
};
249251

@@ -301,7 +303,8 @@ function inspect(value, opts) {
301303
breakLength: inspectDefaultOptions.breakLength,
302304
compact: inspectDefaultOptions.compact,
303305
sorted: inspectDefaultOptions.sorted,
304-
getters: inspectDefaultOptions.getters
306+
getters: inspectDefaultOptions.getters,
307+
numericSeparator: inspectDefaultOptions.numericSeparator,
305308
};
306309
if (arguments.length > 1) {
307310
// Legacy...
@@ -949,7 +952,7 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
949952
formatter = formatArrayBuffer;
950953
} else if (keys.length === 0 && protoProps === undefined) {
951954
return prefix +
952-
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength)} }`;
955+
`{ byteLength: ${formatNumber(ctx.stylize, value.byteLength, false)} }`;
953956
}
954957
braces[0] = `${prefix}{`;
955958
ArrayPrototypeUnshift(keys, 'byteLength');
@@ -1370,13 +1373,61 @@ function handleMaxCallStackSize(ctx, err, constructorName, indentationLvl) {
13701373
assert.fail(err.stack);
13711374
}
13721375

1373-
function formatNumber(fn, value) {
1374-
// Format -0 as '-0'. Checking `value === -0` won't distinguish 0 from -0.
1375-
return fn(ObjectIs(value, -0) ? '-0' : `${value}`, 'number');
1376+
function addNumericSeparator(integerString) {
1377+
let result = '';
1378+
let i = integerString.length;
1379+
const start = integerString.startsWith('-') ? 1 : 0;
1380+
for (; i >= start + 4; i -= 3) {
1381+
result = `_${integerString.slice(i - 3, i)}${result}`;
1382+
}
1383+
return i === integerString.length ?
1384+
integerString :
1385+
`${integerString.slice(0, i)}${result}`;
1386+
}
1387+
1388+
function addNumericSeparatorEnd(integerString) {
1389+
let result = '';
1390+
let i = 0;
1391+
for (; i < integerString.length - 3; i += 3) {
1392+
result += `${integerString.slice(i, i + 3)}_`;
1393+
}
1394+
return i === 0 ?
1395+
integerString :
1396+
`${result}${integerString.slice(i)}`;
1397+
}
1398+
1399+
function formatNumber(fn, number, numericSeparator) {
1400+
if (!numericSeparator) {
1401+
// Format -0 as '-0'. Checking `number === -0` won't distinguish 0 from -0.
1402+
if (ObjectIs(number, -0)) {
1403+
return fn('-0', 'number');
1404+
}
1405+
return fn(`${number}`, 'number');
1406+
}
1407+
const integer = Math.trunc(number);
1408+
const string = String(integer);
1409+
if (integer === number) {
1410+
if (!isFinite(number) || string.includes('e')) {
1411+
return fn(string, 'number');
1412+
}
1413+
return fn(`${addNumericSeparator(string)}`, 'number');
1414+
}
1415+
if (NumberIsNaN(number)) {
1416+
return fn(string, 'number');
1417+
}
1418+
return fn(`${
1419+
addNumericSeparator(string)
1420+
}.${
1421+
addNumericSeparatorEnd(String(number).slice(string.length + 1))
1422+
}`, 'number');
13761423
}
13771424

1378-
function formatBigInt(fn, value) {
1379-
return fn(`${value}n`, 'bigint');
1425+
function formatBigInt(fn, bigint, numericSeparator) {
1426+
const string = String(bigint);
1427+
if (!numericSeparator) {
1428+
return fn(`${string}n`, 'bigint');
1429+
}
1430+
return fn(`${addNumericSeparator(string)}n`, 'bigint');
13801431
}
13811432

13821433
function formatPrimitive(fn, value, ctx) {
@@ -1400,9 +1451,9 @@ function formatPrimitive(fn, value, ctx) {
14001451
return fn(strEscape(value), 'string') + trailer;
14011452
}
14021453
if (typeof value === 'number')
1403-
return formatNumber(fn, value);
1454+
return formatNumber(fn, value, ctx.numericSeparator);
14041455
if (typeof value === 'bigint')
1405-
return formatBigInt(fn, value);
1456+
return formatBigInt(fn, value, ctx.numericSeparator);
14061457
if (typeof value === 'boolean')
14071458
return fn(`${value}`, 'boolean');
14081459
if (typeof value === 'undefined')
@@ -1519,8 +1570,9 @@ function formatTypedArray(value, length, ctx, ignored, recurseTimes) {
15191570
const elementFormatter = value.length > 0 && typeof value[0] === 'number' ?
15201571
formatNumber :
15211572
formatBigInt;
1522-
for (let i = 0; i < maxLength; ++i)
1523-
output[i] = elementFormatter(ctx.stylize, value[i]);
1573+
for (let i = 0; i < maxLength; ++i) {
1574+
output[i] = elementFormatter(ctx.stylize, value[i], ctx.numericSeparator);
1575+
}
15241576
if (remaining > 0) {
15251577
output[maxLength] = `... ${remaining} more item${remaining > 1 ? 's' : ''}`;
15261578
}
@@ -1864,8 +1916,8 @@ function tryStringify(arg) {
18641916
if (!CIRCULAR_ERROR_MESSAGE) {
18651917
try {
18661918
const a = {}; a.a = a; JSONStringify(a);
1867-
} catch (err) {
1868-
CIRCULAR_ERROR_MESSAGE = firstErrorLine(err);
1919+
} catch (circularError) {
1920+
CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);
18691921
}
18701922
}
18711923
if (err.name === 'TypeError' &&
@@ -1888,6 +1940,22 @@ function formatWithOptions(inspectOptions, ...args) {
18881940
return formatWithOptionsInternal(inspectOptions, args);
18891941
}
18901942

1943+
function formatNumberNoColor(number, options) {
1944+
return formatNumber(
1945+
stylizeNoColor,
1946+
number,
1947+
options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
1948+
)
1949+
}
1950+
1951+
function formatBigIntNoColor(bigint, options) {
1952+
return formatBigInt(
1953+
stylizeNoColor,
1954+
bigint,
1955+
options?.numericSeparator ?? inspectDefaultOptions.numericSeparator
1956+
)
1957+
}
1958+
18911959
function formatWithOptionsInternal(inspectOptions, args) {
18921960
const first = args[0];
18931961
let a = 0;
@@ -1909,9 +1977,9 @@ function formatWithOptionsInternal(inspectOptions, args) {
19091977
case 115: // 's'
19101978
const tempArg = args[++a];
19111979
if (typeof tempArg === 'number') {
1912-
tempStr = formatNumber(stylizeNoColor, tempArg);
1980+
tempStr = formatNumberNoColor(tempArg, inspectOptions);
19131981
} else if (typeof tempArg === 'bigint') {
1914-
tempStr = `${tempArg}n`;
1982+
tempStr = formatBigIntNoColor(tempArg, inspectOptions);
19151983
} else if (typeof tempArg !== 'object' ||
19161984
tempArg === null ||
19171985
!hasBuiltInToString(tempArg)) {
@@ -1931,11 +1999,11 @@ function formatWithOptionsInternal(inspectOptions, args) {
19311999
case 100: // 'd'
19322000
const tempNum = args[++a];
19332001
if (typeof tempNum === 'bigint') {
1934-
tempStr = `${tempNum}n`;
2002+
tempStr = formatBigIntNoColor(tempNum, inspectOptions);
19352003
} else if (typeof tempNum === 'symbol') {
19362004
tempStr = 'NaN';
19372005
} else {
1938-
tempStr = formatNumber(stylizeNoColor, Number(tempNum));
2006+
tempStr = formatNumberNoColor(Number(tempNum), inspectOptions);
19392007
}
19402008
break;
19412009
case 79: // 'O'
@@ -1952,21 +2020,19 @@ function formatWithOptionsInternal(inspectOptions, args) {
19522020
case 105: // 'i'
19532021
const tempInteger = args[++a];
19542022
if (typeof tempInteger === 'bigint') {
1955-
tempStr = `${tempInteger}n`;
2023+
tempStr = formatBigIntNoColor(tempInteger, inspectOptions);
19562024
} else if (typeof tempInteger === 'symbol') {
19572025
tempStr = 'NaN';
19582026
} else {
1959-
tempStr = formatNumber(stylizeNoColor,
1960-
NumberParseInt(tempInteger));
2027+
tempStr = formatNumberNoColor(NumberParseInt(tempInteger), inspectOptions);
19612028
}
19622029
break;
19632030
case 102: // 'f'
19642031
const tempFloat = args[++a];
19652032
if (typeof tempFloat === 'symbol') {
19662033
tempStr = 'NaN';
19672034
} else {
1968-
tempStr = formatNumber(stylizeNoColor,
1969-
NumberParseFloat(tempFloat));
2035+
tempStr = formatNumberNoColor(NumberParseFloat(tempFloat), inspectOptions);
19702036
}
19712037
break;
19722038
case 99: // 'c'

test/parallel/test-util-format.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,37 @@ assert.strictEqual(
7474
'1180591620717411303424n 12345678901234567890123n'
7575
);
7676

77+
{
78+
const { numericSeparator } = util.inspect.defaultOptions;
79+
util.inspect.defaultOptions.numericSeparator = true;
80+
81+
assert.strictEqual(
82+
util.format('%d', 1180591620717411303424),
83+
'1.1805916207174113e+21'
84+
);
85+
86+
assert.strictEqual(
87+
util.format(
88+
'%d %s %i', 118059162071741130342, 118059162071741130342, 123_123_123),
89+
'118_059_162_071_741_140_000 118_059_162_071_741_140_000 123_123_123'
90+
);
91+
92+
assert.strictEqual(
93+
util.format(
94+
'%d %s',
95+
1_180_591_620_717_411_303_424n,
96+
12_345_678_901_234_567_890_123n
97+
),
98+
'1_180_591_620_717_411_303_424n 12_345_678_901_234_567_890_123n'
99+
);
100+
101+
assert.strictEqual(
102+
util.format('%i', 1_180_591_620_717_411_303_424n),
103+
'1_180_591_620_717_411_303_424n'
104+
);
105+
106+
util.inspect.defaultOptions.numericSeparator = numericSeparator;
107+
}
77108
// Integer format specifier
78109
assert.strictEqual(util.format('%i'), '%i');
79110
assert.strictEqual(util.format('%i', 42.0), '42');

test/parallel/test-util-inspect.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,3 +3104,52 @@ assert.strictEqual(
31043104
"{ ['__proto__']: { a: 1 } }"
31053105
);
31063106
}
3107+
3108+
{
3109+
const { numericSeparator } = util.inspect.defaultOptions;
3110+
util.inspect.defaultOptions.numericSeparator = true;
3111+
3112+
assert.strictEqual(
3113+
util.inspect(1234567891234567891234),
3114+
'1.234567891234568e+21'
3115+
);
3116+
assert.strictEqual(
3117+
util.inspect(123456789.12345678),
3118+
'123_456_789.123_456_78'
3119+
);
3120+
3121+
assert.strictEqual(util.inspect(10_000_000), '10_000_000');
3122+
assert.strictEqual(util.inspect(1_000_000), '1_000_000');
3123+
assert.strictEqual(util.inspect(100_000), '100_000');
3124+
assert.strictEqual(util.inspect(99_999.9), '99_999.9');
3125+
assert.strictEqual(util.inspect(9_999), '9_999');
3126+
assert.strictEqual(util.inspect(999), '999');
3127+
assert.strictEqual(util.inspect(NaN), 'NaN');
3128+
assert.strictEqual(util.inspect(Infinity), 'Infinity');
3129+
assert.strictEqual(util.inspect(-Infinity), '-Infinity');
3130+
3131+
assert.strictEqual(
3132+
util.inspect(new Float64Array([100_000_000])),
3133+
'Float64Array(1) [ 100_000_000 ]'
3134+
);
3135+
assert.strictEqual(
3136+
util.inspect(new BigInt64Array([9_100_000_100n])),
3137+
'BigInt64Array(1) [ 9_100_000_100n ]'
3138+
);
3139+
3140+
assert.strictEqual(
3141+
util.inspect(123456789),
3142+
'123_456_789'
3143+
);
3144+
assert.strictEqual(
3145+
util.inspect(123456789n),
3146+
'123_456_789n'
3147+
);
3148+
3149+
util.inspect.defaultOptions.numericSeparator = numericSeparator;
3150+
3151+
assert.strictEqual(
3152+
util.inspect(123456789.12345678, { numericSeparator: true }),
3153+
'123_456_789.123_456_78'
3154+
);
3155+
}

0 commit comments

Comments
 (0)