Skip to content

Commit a46e59d

Browse files
TrottMylesBorins
authored andcommitted
console: implement minimal console.group()
Node.js exposes `console.group()` and `console.groupEnd()` via the inspector. These functions have no apparent effect when called from Node.js without the inspector. We cannot easily hide them when Node.js is started without the inspector because we support opening the inspector during runtime via `inspector.port()`. Implement a minimal `console.group()`/`console.groupEnd()`. More sophisticated implementations are possible, but they can be done in userland and/or features can be added to this at a later time. `console.groupCollapsed()` is implemented as an alias for `console.group()`. PR-URL: #14910 Fixes: #1716 Ref: #12675 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent 67fde14 commit a46e59d

File tree

3 files changed

+159
-3
lines changed

3 files changed

+159
-3
lines changed

doc/api/console.md

+27
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,32 @@ If formatting elements (e.g. `%d`) are not found in the first string then
288288
[`util.inspect()`][] is called on each argument and the resulting string
289289
values are concatenated. See [`util.format()`][] for more information.
290290

291+
### console.group([...label])
292+
<!-- YAML
293+
added: REPLACEME
294+
-->
295+
296+
* `label` {any}
297+
298+
Increases indentation of subsequent lines by two spaces.
299+
300+
If one or more `label`s are provided, those are printed first without the
301+
additional indentation.
302+
303+
### console.groupCollapsed()
304+
<!-- YAML
305+
added: REPLACEME
306+
-->
307+
308+
An alias for [`console.group()`][].
309+
310+
### console.groupEnd()
311+
<!-- YAML
312+
added: REPLACEME
313+
-->
314+
315+
Decreases indentation of subsequent lines by two spaces.
316+
291317
### console.info([data][, ...args])
292318
<!-- YAML
293319
added: v0.1.100
@@ -392,6 +418,7 @@ added: v0.1.100
392418
The `console.warn()` function is an alias for [`console.error()`][].
393419

394420
[`console.error()`]: #console_console_error_data_args
421+
[`console.group()`]: #console_console_group_label
395422
[`console.log()`]: #console_console_log_data_args
396423
[`console.time()`]: #console_console_time_label
397424
[`console.timeEnd()`]: #console_console_timeend_label

lib/console.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
const util = require('util');
2525
const kCounts = Symbol('counts');
2626

27+
// Track amount of indentation required via `console.group()`.
28+
const kGroupIndent = Symbol('groupIndent');
29+
2730
function Console(stdout, stderr, ignoreErrors = true) {
2831
if (!(this instanceof Console)) {
2932
return new Console(stdout, stderr, ignoreErrors);
@@ -56,6 +59,7 @@ function Console(stdout, stderr, ignoreErrors = true) {
5659
Object.defineProperty(this, '_stderrErrorHandler', prop);
5760

5861
this[kCounts] = new Map();
62+
this[kGroupIndent] = '';
5963

6064
// bind the prototype functions to this Console instance
6165
var keys = Object.keys(Console.prototype);
@@ -110,7 +114,7 @@ function write(ignoreErrors, stream, string, errorhandler) {
110114
Console.prototype.log = function log(...args) {
111115
write(this._ignoreErrors,
112116
this._stdout,
113-
`${util.format.apply(null, args)}\n`,
117+
`${this[kGroupIndent]}${util.format.apply(null, args)}\n`,
114118
this._stdoutErrorHandler);
115119
};
116120

@@ -121,7 +125,7 @@ Console.prototype.info = Console.prototype.log;
121125
Console.prototype.warn = function warn(...args) {
122126
write(this._ignoreErrors,
123127
this._stderr,
124-
`${util.format.apply(null, args)}\n`,
128+
`${this[kGroupIndent]}${util.format.apply(null, args)}\n`,
125129
this._stderrErrorHandler);
126130
};
127131

@@ -133,7 +137,7 @@ Console.prototype.dir = function dir(object, options) {
133137
options = Object.assign({customInspect: false}, options);
134138
write(this._ignoreErrors,
135139
this._stdout,
136-
`${util.inspect(object, options)}\n`,
140+
`${this[kGroupIndent]}${util.inspect(object, options)}\n`,
137141
this._stdoutErrorHandler);
138142
};
139143

@@ -209,6 +213,20 @@ Console.prototype.countReset = function countReset(label = 'default') {
209213
counts.delete(`${label}`);
210214
};
211215

216+
Console.prototype.group = function group(...data) {
217+
if (data.length > 0) {
218+
this.log(...data);
219+
}
220+
this[kGroupIndent] += ' ';
221+
};
222+
223+
Console.prototype.groupCollapsed = Console.prototype.group;
224+
225+
Console.prototype.groupEnd = function groupEnd() {
226+
this[kGroupIndent] =
227+
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
228+
};
229+
212230
module.exports = new Console(process.stdout, process.stderr);
213231
module.exports.Console = Console;
214232

test/parallel/test-console-group.js

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
const assert = require('assert');
5+
const Console = require('console').Console;
6+
7+
let c, stdout, stderr;
8+
9+
function setup() {
10+
stdout = '';
11+
common.hijackStdout(function(data) {
12+
stdout += data;
13+
});
14+
15+
stderr = '';
16+
common.hijackStderr(function(data) {
17+
stderr += data;
18+
});
19+
20+
c = new Console(process.stdout, process.stderr);
21+
}
22+
23+
function teardown() {
24+
common.restoreStdout();
25+
common.restoreStderr();
26+
}
27+
28+
// Basic group() functionality
29+
{
30+
setup();
31+
const expectedOut = 'This is the outer level\n' +
32+
' Level 2\n' +
33+
' Level 3\n' +
34+
' Back to level 2\n' +
35+
'Back to the outer level\n' +
36+
'Still at the outer level\n';
37+
38+
39+
const expectedErr = ' More of level 3\n';
40+
41+
c.log('This is the outer level');
42+
c.group();
43+
c.log('Level 2');
44+
c.group();
45+
c.log('Level 3');
46+
c.warn('More of level 3');
47+
c.groupEnd();
48+
c.log('Back to level 2');
49+
c.groupEnd();
50+
c.log('Back to the outer level');
51+
c.groupEnd();
52+
c.log('Still at the outer level');
53+
54+
assert.strictEqual(stdout, expectedOut);
55+
assert.strictEqual(stderr, expectedErr);
56+
teardown();
57+
}
58+
59+
// Group indentation is tracked per Console instance.
60+
{
61+
setup();
62+
const expectedOut = 'No indentation\n' +
63+
'None here either\n' +
64+
' Now the first console is indenting\n' +
65+
'But the second one does not\n';
66+
const expectedErr = '';
67+
68+
const c2 = new Console(process.stdout, process.stderr);
69+
c.log('No indentation');
70+
c2.log('None here either');
71+
c.group();
72+
c.log('Now the first console is indenting');
73+
c2.log('But the second one does not');
74+
75+
assert.strictEqual(stdout, expectedOut);
76+
assert.strictEqual(stderr, expectedErr);
77+
teardown();
78+
}
79+
80+
// Make sure labels work.
81+
{
82+
setup();
83+
const expectedOut = 'This is a label\n' +
84+
' And this is the data for that label\n';
85+
const expectedErr = '';
86+
87+
c.group('This is a label');
88+
c.log('And this is the data for that label');
89+
90+
assert.strictEqual(stdout, expectedOut);
91+
assert.strictEqual(stderr, expectedErr);
92+
teardown();
93+
}
94+
95+
// Check that console.groupCollapsed() is an alias of console.group()
96+
{
97+
setup();
98+
const expectedOut = 'Label\n' +
99+
' Level 2\n' +
100+
' Level 3\n';
101+
const expectedErr = '';
102+
103+
c.groupCollapsed('Label');
104+
c.log('Level 2');
105+
c.groupCollapsed();
106+
c.log('Level 3');
107+
108+
assert.strictEqual(stdout, expectedOut);
109+
assert.strictEqual(stderr, expectedErr);
110+
teardown();
111+
}

0 commit comments

Comments
 (0)