Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,48 @@ Returns the `string` after replacing any surrogate code points
(or equivalently, any unpaired surrogate code units) with the
Unicode "replacement character" U+FFFD.

## `util.table(tabularData[, properties][, options])`

<!-- YAML
added: REPLACEME
-->

* `tabularData` {any} The data to render. Arrays, objects, `Map`s and `Set`s
are rendered as tables; any other value is returned as inspected text.
* `properties` {string\[]} An alternate list of properties to use as the table's
columns. By default every own enumerable property of the rows is used.
* `options` {Object} Options forwarded to [`util.inspect()`][] when formatting
each cell.
* Returns: {string}

Returns tabular data formatted with the same layout as [`console.table()`][],
but as a string instead of writing it to a stream. This is useful for logging,
writing to a file, or building output without a console.

```mjs
import { table } from 'node:util';

console.log(table([
{ name: 'alice', age: 30 },
{ name: 'bob', age: 25 },
]));
// ┌─────────┬─────────┬─────┐
// │ (index) │ name │ age │
// ├─────────┼─────────┼─────┤
// │ 0 │ 'alice' │ 30 │
// │ 1 │ 'bob' │ 25 │
// └─────────┴─────────┴─────┘
```

```cjs
const { table } = require('node:util');

console.log(table([
{ name: 'alice', age: 30 },
{ name: 'bob', age: 25 },
]));
```

## `util.transferableAbortController()`

<!-- YAML
Expand Down Expand Up @@ -3914,6 +3956,7 @@ npx codemod@latest @nodejs/util-is
[`Runtime.ScriptId`]: https://chromedevtools.github.io/devtools-protocol/1-3/Runtime/#type-ScriptId
[`assert.deepStrictEqual()`]: assert.md#assertdeepstrictequalactual-expected-message
[`console.error()`]: console.md#consoleerrordata-args
[`console.table()`]: console.md#consoletabletabulardata-properties
[`mime.toString()`]: #mimetostring
[`mimeParams.entries()`]: #mimeparamsentries
[`napi_create_external()`]: n-api.md#napi_create_external
Expand Down
126 changes: 6 additions & 120 deletions lib/internal/console/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
// console. It's exported for backwards compatibility.

const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypePush,
ArrayPrototypeUnshift,
Boolean,
ErrorCaptureStackTrace,
Expand All @@ -17,8 +14,6 @@ const {
ObjectDefineProperties,
ObjectDefineProperty,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectValues,
ReflectApply,
ReflectConstruct,
ReflectOwnKeys,
Expand Down Expand Up @@ -49,14 +44,12 @@ const {
validateObject,
validateOneOf,
} = require('internal/validators');
const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
const {
inspect,
formatWithOptions,
} = require('internal/util/inspect');
const {
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
isMap,
} = require('internal/util/types');
const {
CHAR_UPPERCASE_C: kTraceCount,
Expand All @@ -76,7 +69,7 @@ const kTraceConsoleCategory = 'node,node.console';
const kMaxGroupIndentation = 1000;

// Lazy loaded for startup performance.
let cliTable;
let buildTable;

let utilColors;
function lazyUtilColors() {
Expand Down Expand Up @@ -558,120 +551,13 @@ const consoleMethods = {
if (tabularData === null || typeof tabularData !== 'object')
return this.log(tabularData);

cliTable ??= require('internal/cli_table');
const final = (k, v) => this.log(cliTable(k, v));

const _inspect = (v) => {
const depth = v !== null &&
typeof v === 'object' &&
!isArray(v) &&
ObjectKeys(v).length > 2 ? -1 : 0;
const opt = {
depth,
maxArrayLength: 3,
breakLength: Infinity,
...this[kGetInspectOptions](this._stdout),
};
return inspect(v, opt);
};
const getIndexArray = (length) => ArrayFrom(
{ length }, (_, i) => _inspect(i));

const mapIter = isMapIterator(tabularData);
let isKeyValue = false;
let i = 0;
if (mapIter) {
const res = previewEntries(tabularData, true);
tabularData = res[0];
isKeyValue = res[1];
}

if (isKeyValue || isMap(tabularData)) {
const keys = [];
const values = [];
let length = 0;
if (mapIter) {
for (; i < tabularData.length / 2; ++i) {
ArrayPrototypePush(keys, _inspect(tabularData[i * 2]));
ArrayPrototypePush(values, _inspect(tabularData[i * 2 + 1]));
length++;
}
} else {
for (const { 0: k, 1: v } of tabularData) {
ArrayPrototypePush(keys, _inspect(k));
ArrayPrototypePush(values, _inspect(v));
length++;
}
}
return final([
iterKey, keyKey, valuesKey,
], [
getIndexArray(length),
keys,
values,
]);
}

const setIter = isSetIterator(tabularData);
if (setIter)
tabularData = previewEntries(tabularData);

const setlike = setIter || mapIter || isSet(tabularData);
if (setlike) {
const values = [];
let length = 0;
for (const v of tabularData) {
ArrayPrototypePush(values, _inspect(v));
length++;
}
return final([iterKey, valuesKey], [getIndexArray(length), values]);
}

const map = { __proto__: null };
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);

for (; i < indexKeyArray.length; i++) {
const item = tabularData[indexKeyArray[i]];
const primitive = item === null ||
(typeof item !== 'function' && typeof item !== 'object');
if (properties === undefined && primitive) {
hasPrimitives = true;
valuesKeyArray[i] = _inspect(item);
} else {
const keys = properties || ObjectKeys(item);
for (const key of keys) {
map[key] ??= [];
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}

const keys = ObjectKeys(map);
const values = ObjectValues(map);
if (hasPrimitives) {
ArrayPrototypePush(keys, valuesKey);
ArrayPrototypePush(values, valuesKeyArray);
}
ArrayPrototypeUnshift(keys, indexKey);
ArrayPrototypeUnshift(values, indexKeyArray);

return final(keys, values);
buildTable ??= require('internal/util/table');
return this.log(
buildTable(tabularData, properties, this[kGetInspectOptions](this._stdout)),
);
},
};

const keyKey = 'Key';
const valuesKey = 'Values';
const indexKey = '(index)';
const iterKey = '(iteration index)';

const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);

function noop() {}

for (const method of ReflectOwnKeys(consoleMethods))
Expand Down
142 changes: 142 additions & 0 deletions lib/internal/util/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';

const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeUnshift,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectValues,
} = primordials;

const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
const { inspect } = require('internal/util/inspect');
const {
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
} = require('internal/util/types');

const keyKey = 'Key';
const valuesKey = 'Values';
const indexKey = '(index)';
const iterKey = '(iteration index)';

const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);

/**
* Builds the rows of a table from tabular data and renders them into a string.
* Shared by `console.table` and `util.table`.
* @param {any} tabularData The data to tabulate.
* @param {string[]} [properties] The subset of properties to include as columns.
* @param {object} [inspectOptions] Base options merged into each cell's inspect call.
* @returns {string} The rendered table.
*/
function table(tabularData, properties, inspectOptions) {
// Lazy require to avoid a cycle with the console bootstrap path.
const cliTable = require('internal/cli_table');

const _inspect = (v) => {
const depth = v !== null &&
typeof v === 'object' &&
!isArray(v) &&
ObjectKeys(v).length > 2 ? -1 : 0;
const opt = {
depth,
maxArrayLength: 3,
breakLength: Infinity,
...inspectOptions,
};
return inspect(v, opt);
};
const getIndexArray = (length) => ArrayFrom(
{ length }, (_, i) => _inspect(i));

const mapIter = isMapIterator(tabularData);
let isKeyValue = false;
let i = 0;
if (mapIter) {
const res = previewEntries(tabularData, true);
tabularData = res[0];
isKeyValue = res[1];
}

if (isKeyValue || isMap(tabularData)) {
const keys = [];
const values = [];
let length = 0;
if (mapIter) {
for (; i < tabularData.length / 2; ++i) {
ArrayPrototypePush(keys, _inspect(tabularData[i * 2]));
ArrayPrototypePush(values, _inspect(tabularData[i * 2 + 1]));
length++;
}
} else {
for (const { 0: k, 1: v } of tabularData) {
ArrayPrototypePush(keys, _inspect(k));
ArrayPrototypePush(values, _inspect(v));
length++;
}
}
return cliTable([
iterKey, keyKey, valuesKey,
], [
getIndexArray(length),
keys,
values,
]);
}

const setIter = isSetIterator(tabularData);
if (setIter)
tabularData = previewEntries(tabularData);

const setlike = setIter || mapIter || isSet(tabularData);
if (setlike) {
const values = [];
let length = 0;
for (const v of tabularData) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a normal for loop - both for perf and to avoid needing a primordial for iteration

ArrayPrototypePush(values, _inspect(v));
length++;
}
return cliTable([iterKey, valuesKey], [getIndexArray(length), values]);
}

const map = { __proto__: null };
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);

for (; i < indexKeyArray.length; i++) {
const item = tabularData[indexKeyArray[i]];
const primitive = item === null ||
(typeof item !== 'function' && typeof item !== 'object');
if (properties === undefined && primitive) {
hasPrimitives = true;
valuesKeyArray[i] = _inspect(item);
} else {
const keys = properties || ObjectKeys(item);
for (const key of keys) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here

map[key] ??= [];
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}

const keys = ObjectKeys(map);
const values = ObjectValues(map);
if (hasPrimitives) {
ArrayPrototypePush(keys, valuesKey);
ArrayPrototypePush(values, valuesKeyArray);
}
ArrayPrototypeUnshift(keys, indexKey);
ArrayPrototypeUnshift(values, indexKeyArray);

return cliTable(keys, values);
}

module.exports = table;
Loading
Loading