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

Commit f6b4b20

Browse files
author
Jan Krems
committed
feat: Nicer inspect of remote values
1 parent b8c9b14 commit f6b4b20

1 file changed

Lines changed: 119 additions & 62 deletions

File tree

lib/node-inspect.js

Lines changed: 119 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,59 @@ function getRelativePath(filename) {
205205
return filename;
206206
}
207207

208-
function formatPreview({ properties, subtype }) {
209-
const object = properties.reduce((obj, { name, value }) => {
210-
obj[name] = value;
211-
return obj;
212-
}, subtype === 'array' ? [] : {});
213-
return object;
208+
function stylizeWithColor(str, styleType) {
209+
const style = util.inspect.styles[styleType];
210+
211+
if (style) {
212+
const [start, end] = util.inspect.colors[style];
213+
return `\u001b[${start}m${str}\u001b[${end}m`;
214+
}
215+
return str;
216+
}
217+
218+
function formatFunction({ className, description }, opts) {
219+
const fnNameMatch = (description).match(/^(?:function\*? )?([^(\s]+)\(/);
220+
const fnName = fnNameMatch ? `: ${fnNameMatch[1]}` : '';
221+
const formatted = `[${className}${fnName}]`;
222+
return opts.colors ? stylizeWithColor(formatted, 'special') : formatted;
223+
}
224+
225+
function formatPropertyValue(prop) {
226+
const { value, type, subtype } = prop;
227+
if (subtype === 'array') {
228+
return stylizeWithColor(value, 'special');
229+
} else if (subtype === 'regexp') {
230+
return stylizeWithColor(value, 'regexp');
231+
} else if (subtype === 'date') {
232+
const date = new Date(value);
233+
return stylizeWithColor(date.toISOString(), 'date');
234+
} else if (type === 'object') {
235+
return stylizeWithColor(value, 'special');
236+
} else if (type === 'function') {
237+
return formatFunction({ className: 'Foo', description: 'bar' }, { colors: true });
238+
}
239+
return util.inspect(value, { colors: true });
240+
}
241+
242+
function formatPreview({ properties, subtype, description }, opts) {
243+
if (subtype === 'regexp') {
244+
return opts.colors ? stylizeWithColor(description, 'regexp') : description;
245+
} else if (subtype === 'date') {
246+
const date = new Date(description);
247+
return opts.colors ? stylizeWithColor(date.toISOString(), 'date') : description;
248+
}
249+
250+
function formatPropertyPair(prop) {
251+
return `${prop.name}: ${formatPropertyValue(prop)}`;
252+
}
253+
254+
const propertyFormatter = subtype === 'array' ? formatPropertyValue : formatPropertyPair;
255+
const formattedProps = properties.map(propertyFormatter).join('\n ');
256+
257+
if (subtype === 'array') {
258+
return `[ ${formattedProps} ]`;
259+
}
260+
return `{ ${formattedProps} }`;
214261
}
215262

216263
function extractErrorMessage(stack) {
@@ -219,21 +266,53 @@ function extractErrorMessage(stack) {
219266
return m ? m[1] : stack;
220267
}
221268

222-
function formatValue({ result, wasThrown }) {
223-
const { className, description, preview, type, value } = result;
224-
if (wasThrown) {
225-
const err = new Error(extractErrorMessage(description));
226-
err.stack = description;
227-
Object.defineProperty(err, 'name', { value: className });
228-
return err;
229-
}
230-
if (type === 'object') {
231-
if (preview && preview.properties) {
232-
return formatPreview(preview);
269+
const REMOTE_OBJ_INSPECT = Symbol('remoteObjectInspect');
270+
const REMOTE_OBJ_DATA = Symbol('remoteObjectData');
271+
class RemoteObject {
272+
constructor(remoteObject) {
273+
this[REMOTE_OBJ_DATA] = remoteObject;
274+
Object.assign(this, remoteObject);
275+
}
276+
277+
// Future: [util.inspect.custom]() { ... }
278+
[REMOTE_OBJ_INSPECT](recurseTimes, ctx) {
279+
const opts = Object.assign({}, ctx, { depth: 0 });
280+
const { description, type, preview, value } = this[REMOTE_OBJ_DATA];
281+
if (type === 'object') {
282+
if (preview && preview.properties) {
283+
return formatPreview(preview, opts);
284+
}
285+
return opts.colors ? stylizeWithColor(description, 'special') : description;
286+
} else if (type === 'function') {
287+
return formatFunction(this[REMOTE_OBJ_DATA], opts);
233288
}
234-
return description;
289+
return util.inspect(value, ctx);
235290
}
236-
return value;
291+
}
292+
293+
function createRemoteObjectProxyHandler(/* client */) {
294+
return {
295+
get(target, name) {
296+
if (name === REMOTE_OBJ_INSPECT || name === REMOTE_OBJ_DATA) return target[name];
297+
if (name === 'then') return undefined; // This is gonna be tricky. :(
298+
console.log('use client to get %j', name);
299+
return undefined;
300+
},
301+
302+
set(target, name, value) {
303+
throw new Error(`Modifying remote objects is not implemented yet; .${name} = ${value}`);
304+
},
305+
};
306+
}
307+
308+
function createRemoteAwareWriter(colors) {
309+
return function remoteAwareWriter(value, originalOpts) {
310+
const opts = Object.assign({}, originalOpts, { colors });
311+
if (value && value[REMOTE_OBJ_INSPECT]) {
312+
return value[REMOTE_OBJ_INSPECT](0, opts);
313+
}
314+
return util.inspect(value, opts);
315+
};
237316
}
238317

239318
function toCallback(promise, callback) {
@@ -408,6 +487,17 @@ function createCommandContext(inspector) {
408487
}
409488
}
410489

490+
function convertResultToRemoteObject({ result, wasThrown }) {
491+
const { className, description } = result;
492+
if (wasThrown) {
493+
const err = new Error(extractErrorMessage(description));
494+
err.stack = description;
495+
Object.defineProperty(err, 'name', { value: className });
496+
return err;
497+
}
498+
return new Proxy(new RemoteObject(result), createRemoteObjectProxyHandler(inspector.client));
499+
}
500+
411501
const ctx = {
412502
debugEval(code) {
413503
// Repl asked for scope variables
@@ -423,7 +513,7 @@ function createCommandContext(inspector) {
423513
};
424514

425515
return Debugger.evaluateOnCallFrame(params)
426-
.then(formatValue);
516+
.then(convertResultToRemoteObject);
427517
},
428518

429519
exec(code) {
@@ -553,6 +643,12 @@ class Inspector {
553643
['Debugger', 'Runtime'].forEach(domain => {
554644
this[domain] = createAgentProxy(domain, this);
555645
});
646+
this.handleDebugEvent = (fullName, params) => {
647+
const [domain, name] = fullName.split('.');
648+
if (domain in this) {
649+
this[domain].emit(name, params);
650+
}
651+
};
556652

557653
// Two eval modes are available: controlEval and debugEval
558654
// But controlEval is used by default
@@ -580,6 +676,8 @@ class Inspector {
580676
opts.useColors = false;
581677
}
582678

679+
opts.writer = createRemoteAwareWriter(opts.useColors !== false);
680+
583681
this.repl = Repl.start(opts);
584682

585683
// Do not print useless warning
@@ -632,24 +730,6 @@ class Inspector {
632730
});
633731
}
634732

635-
handlePaused({ callFrames /* , reason, hitBreakpoints */ }) {
636-
// this.pause();
637-
638-
// Save execution context's data
639-
const topFrame = callFrames[0];
640-
// const { scriptId, lineNumber } = this.client.currentSourceLocation = topFrame.location;
641-
this.client.currentFrame = topFrame.callFrameId;
642-
643-
// const script = this.scripts[scriptId];
644-
// const scriptUrl = script ? getRelativePath(script.url) : '[unknown]';
645-
// this.print(`${reason} in ${scriptUrl}:${lineNumber + 1}`);
646-
647-
// this.watchers(true)
648-
// .then(() => this.list(2))
649-
// .then(() => {
650-
// this.resume();
651-
// }, this.error.bind(this));
652-
}
653733

654734
controlEval(code, context, filename, callback) {
655735
/* eslint no-param-reassign: 0 */
@@ -802,12 +882,6 @@ class Inspector {
802882
}
803883
}
804884

805-
handleScriptParsed({ scriptId, url }) {
806-
if (url) {
807-
this.scripts[scriptId] = { url };
808-
}
809-
}
810-
811885
// Spawns child process (and restores breakpoints)
812886
trySpawn(cb) {
813887
const breakpoints = this.breakpoints || [];
@@ -867,14 +941,7 @@ class Inspector {
867941
const client = this.client = new Client();
868942
let connectionAttempts = 0;
869943

870-
client.on('debugEvent', (fullName, params) => {
871-
const [domain, name] = fullName.split('.');
872-
if (domain in this) {
873-
this[domain].emit(name, params);
874-
}
875-
});
876-
877-
client.on('Debugger.scriptParsed', this.handleScriptParsed.bind(this));
944+
client.on('debugEvent', this.handleDebugEvent);
878945

879946
client.once('ready', () => {
880947
// Restore breakpoints
@@ -895,16 +962,6 @@ class Inspector {
895962
this.resume();
896963
});
897964

898-
client.on('unhandledResponse', res => {
899-
this.pause();
900-
this.print(`\nunhandled res:${JSON.stringify(res)}`);
901-
this.resume();
902-
});
903-
904-
client.on('Debugger.paused', res => {
905-
this.handlePaused(res);
906-
});
907-
908965
const attemptConnect = () => {
909966
++connectionAttempts;
910967
this.stdout.write('.');

0 commit comments

Comments
 (0)