Skip to content
This repository was archived by the owner on Feb 1, 2022. It is now read-only.

Commit 7f48c95

Browse files
author
Jan Krems
committed
feat: watchers & exec
1 parent bc82ecc commit 7f48c95

File tree

2 files changed

+193
-2
lines changed

2 files changed

+193
-2
lines changed

lib/internal/inspect-repl.js

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,90 @@ function convertResultToError(result) {
9595
return err;
9696
}
9797

98+
class RemoteObject {
99+
constructor(attributes) {
100+
Object.assign(this, attributes);
101+
if (this.type === 'number') {
102+
this.value = this.unserializableValue ? +this.unserializableValue : +this.value;
103+
}
104+
}
105+
106+
[util.inspect.custom](depth, opts) {
107+
function formatProperty(prop) {
108+
switch (prop.type) {
109+
case 'string':
110+
case 'undefined':
111+
return util.inspect(prop.value, opts);
112+
113+
case 'number':
114+
case 'boolean':
115+
return opts.stylize(prop.value, prop.type);
116+
117+
case 'object':
118+
case 'symbol':
119+
if (prop.subtype === 'date') {
120+
return util.inspect(new Date(prop.value), opts);
121+
}
122+
if (prop.subtype === 'array') {
123+
return opts.stylize(prop.value, 'special');
124+
}
125+
return opts.stylize(prop.value, prop.subtype || 'special');
126+
127+
default:
128+
return prop.value;
129+
}
130+
}
131+
switch (this.type) {
132+
case 'boolean':
133+
case 'number':
134+
case 'string':
135+
case 'undefined':
136+
return util.inspect(this.value, opts);
137+
138+
case 'symbol':
139+
return opts.stylize(this.description, 'special');
140+
141+
case 'object':
142+
switch (this.subtype) {
143+
case 'date':
144+
return util.inspect(new Date(this.description), opts);
145+
146+
case 'null':
147+
return util.inspect(null, opts);
148+
149+
case 'regexp':
150+
return opts.stylize(this.description, 'regexp');
151+
152+
default:
153+
break;
154+
}
155+
if (this.preview) {
156+
const props = this.preview.properties
157+
.map((prop, idx) => {
158+
const value = formatProperty(prop);
159+
if (prop.name === `${idx}`) return value;
160+
return `${prop.name}: ${value}`;
161+
});
162+
if (this.preview.overflow) {
163+
props.push('...');
164+
}
165+
const singleLine = props.join(', ');
166+
const propString = singleLine.length > 60 ? props.join(',\n ') : singleLine;
167+
return this.subtype === 'array' ? `[ ${propString} ]` : `{ ${propString} }`;
168+
}
169+
return this.description;
170+
171+
default:
172+
return this.description;
173+
}
174+
}
175+
}
176+
177+
function convertResultToRemoteObject({ result, wasThrown }) {
178+
if (wasThrown) return convertResultToError(result);
179+
return new RemoteObject(result);
180+
}
181+
98182
function copyOwnProperties(target, source) {
99183
Object.getOwnPropertyNames(source).forEach((prop) => {
100184
Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
@@ -122,6 +206,11 @@ function createRepl(inspector) {
122206

123207
const print = inspector.print.bind(inspector);
124208

209+
const INSPECT_OPTIONS = { colors: inspector.stdout.isTTY };
210+
function inspect(value) {
211+
return util.inspect(value, INSPECT_OPTIONS);
212+
}
213+
125214
function isCurrentScript(script) {
126215
return selectedFrame && selectedFrame.location.scriptId === script.scriptId;
127216
}
@@ -199,8 +288,49 @@ function createRepl(inspector) {
199288
}
200289
}
201290

202-
function watchers() {
203-
return Promise.resolve(watchedExpressions);
291+
function debugEval(code) {
292+
// Repl asked for scope variables
293+
if (code === '.scope') {
294+
return Promise.reject('client.reqScopes not implemented');
295+
}
296+
297+
const params = {
298+
callFrameId: selectedFrame.callFrameId,
299+
expression: code,
300+
objectGroup: 'node-inspect',
301+
generatePreview: true,
302+
};
303+
304+
return Debugger.evaluateOnCallFrame(params)
305+
.then(convertResultToRemoteObject);
306+
}
307+
308+
function watchers(verbose = false) {
309+
if (!watchedExpressions.length) {
310+
return Promise.resolve();
311+
}
312+
313+
const inspectValue = expr =>
314+
debugEval(expr)
315+
// .then(formatValue)
316+
.catch(error => `<${error.message}>`);
317+
318+
return Promise.all(watchedExpressions.map(inspectValue))
319+
.then(values => {
320+
if (verbose) print('Watchers:');
321+
322+
watchedExpressions.forEach((expr, idx) => {
323+
const prefix = `${leftPad(idx, ' ', watchedExpressions.length - 1)}: ${expr} =`;
324+
const value = inspect(values[idx], { colors: true });
325+
if (value.indexOf('\n') === -1) {
326+
print(`${prefix} ${value}`);
327+
} else {
328+
print(`${prefix}\n ${value.split('\n').join('\n ')}`);
329+
}
330+
});
331+
332+
if (verbose) print('');
333+
});
204334
}
205335

206336
// List source code
@@ -414,6 +544,27 @@ function createRepl(inspector) {
414544
return listBreakpoints();
415545
},
416546

547+
exec(expr) {
548+
return debugEval(expr);
549+
},
550+
551+
get watchers() {
552+
return watchers();
553+
},
554+
555+
watch(expr) {
556+
watchedExpressions.push(expr);
557+
},
558+
559+
unwatch(expr) {
560+
const index = watchedExpressions.indexOf(expr);
561+
562+
// Unwatch by expression
563+
// or
564+
// Unwatch by watcher number
565+
watchedExpressions.splice(index !== -1 ? index : +expr, 1);
566+
},
567+
417568
scripts: listScripts,
418569
setBreakpoint,
419570
list,

test/cli/watchers.test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
const { test } = require('tap');
3+
4+
const startCLI = require('./start-cli');
5+
6+
test('stepping through breakpoints', (t) => {
7+
const cli = startCLI(['examples/break.js']);
8+
9+
function onFatal(error) {
10+
cli.quit();
11+
throw error;
12+
}
13+
14+
return cli.waitFor(/break/)
15+
.then(() => cli.waitForPrompt())
16+
.then(() => cli.command('watch("x")'))
17+
.then(() => cli.command('watch("\\"Hello\\"")'))
18+
.then(() => cli.command('watch("42")'))
19+
.then(() => cli.command('watch("NaN")'))
20+
.then(() => cli.command('watch("true")'))
21+
.then(() => cli.command('watch("[1, 2]")'))
22+
.then(() => cli.command('watch("process.env")'))
23+
.then(() => cli.command('watchers'))
24+
.then(() => {
25+
t.match(cli.output, 'x is not defined');
26+
})
27+
.then(() => cli.command('unwatch("42")'))
28+
.then(() => cli.stepCommand('n'))
29+
.then(() => {
30+
t.match(cli.output, '0: x = 10');
31+
t.match(cli.output, '1: "Hello" = \'Hello\'');
32+
t.match(cli.output, '2: NaN = NaN');
33+
t.match(cli.output, '3: true = true');
34+
t.match(cli.output, '4: [1, 2] = [ 1, 2 ]');
35+
t.match(cli.output, /5: process\.env =\n\s+\{[\s\S]+,\n\s+\.\.\. \}/,
36+
'shows "..." for process.env');
37+
})
38+
.then(() => cli.quit())
39+
.then(null, onFatal);
40+
});

0 commit comments

Comments
 (0)