Skip to content

Commit c40229a

Browse files
committed
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: nodejs#14910 Fixes: nodejs#1716 Ref: nodejs#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 c6da5c8 commit c40229a

File tree

3 files changed

+159
-3
lines changed

3 files changed

+159
-3
lines changed

doc/api/console.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,32 @@ If formatting elements (e.g. `%d`) are not found in the first string then
286286
[`util.inspect()`][] is called on each argument and the resulting string
287287
values are concatenated. See [`util.format()`][] for more information.
288288

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

392418
[`console.error()`]: #console_console_error_data_args
419+
[`console.group()`]: #console_console_group_label
393420
[`console.log()`]: #console_console_log_data_args
394421
[`console.time()`]: #console_console_time_label
395422
[`console.timeEnd()`]: #console_console_timeend_label

lib/console.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const errors = require('internal/errors');
2525
const util = require('util');
2626
const kCounts = Symbol('counts');
2727

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

5962
this[kCounts] = new Map();
63+
this[kGroupIndent] = '';
6064

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

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

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

@@ -214,6 +218,20 @@ Console.prototype.countReset = function countReset(label = 'default') {
214218
counts.delete(`${label}`);
215219
};
216220

221+
Console.prototype.group = function group(...data) {
222+
if (data.length > 0) {
223+
this.log(...data);
224+
}
225+
this[kGroupIndent] += ' ';
226+
};
227+
228+
Console.prototype.groupCollapsed = Console.prototype.group;
229+
230+
Console.prototype.groupEnd = function groupEnd() {
231+
this[kGroupIndent] =
232+
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
233+
};
234+
217235
module.exports = new Console(process.stdout, process.stderr);
218236
module.exports.Console = Console;
219237

test/parallel/test-console-group.js

Lines changed: 111 additions & 0 deletions
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)