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

Commit

Permalink
feat: Nicer inspect of remote values
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Krems committed Sep 7, 2016
1 parent b8c9b14 commit f6b4b20
Showing 1 changed file with 119 additions and 62 deletions.
181 changes: 119 additions & 62 deletions lib/node-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,59 @@ function getRelativePath(filename) {
return filename;
}

function formatPreview({ properties, subtype }) {
const object = properties.reduce((obj, { name, value }) => {
obj[name] = value;
return obj;
}, subtype === 'array' ? [] : {});
return object;
function stylizeWithColor(str, styleType) {
const style = util.inspect.styles[styleType];

if (style) {
const [start, end] = util.inspect.colors[style];
return `\u001b[${start}m${str}\u001b[${end}m`;
}
return str;
}

function formatFunction({ className, description }, opts) {
const fnNameMatch = (description).match(/^(?:function\*? )?([^(\s]+)\(/);
const fnName = fnNameMatch ? `: ${fnNameMatch[1]}` : '';
const formatted = `[${className}${fnName}]`;
return opts.colors ? stylizeWithColor(formatted, 'special') : formatted;
}

function formatPropertyValue(prop) {
const { value, type, subtype } = prop;
if (subtype === 'array') {
return stylizeWithColor(value, 'special');
} else if (subtype === 'regexp') {
return stylizeWithColor(value, 'regexp');
} else if (subtype === 'date') {
const date = new Date(value);
return stylizeWithColor(date.toISOString(), 'date');
} else if (type === 'object') {
return stylizeWithColor(value, 'special');
} else if (type === 'function') {
return formatFunction({ className: 'Foo', description: 'bar' }, { colors: true });
}
return util.inspect(value, { colors: true });
}

function formatPreview({ properties, subtype, description }, opts) {
if (subtype === 'regexp') {
return opts.colors ? stylizeWithColor(description, 'regexp') : description;
} else if (subtype === 'date') {
const date = new Date(description);
return opts.colors ? stylizeWithColor(date.toISOString(), 'date') : description;
}

function formatPropertyPair(prop) {
return `${prop.name}: ${formatPropertyValue(prop)}`;
}

const propertyFormatter = subtype === 'array' ? formatPropertyValue : formatPropertyPair;
const formattedProps = properties.map(propertyFormatter).join('\n ');

if (subtype === 'array') {
return `[ ${formattedProps} ]`;
}
return `{ ${formattedProps} }`;
}

function extractErrorMessage(stack) {
Expand All @@ -219,21 +266,53 @@ function extractErrorMessage(stack) {
return m ? m[1] : stack;
}

function formatValue({ result, wasThrown }) {
const { className, description, preview, type, value } = result;
if (wasThrown) {
const err = new Error(extractErrorMessage(description));
err.stack = description;
Object.defineProperty(err, 'name', { value: className });
return err;
}
if (type === 'object') {
if (preview && preview.properties) {
return formatPreview(preview);
const REMOTE_OBJ_INSPECT = Symbol('remoteObjectInspect');
const REMOTE_OBJ_DATA = Symbol('remoteObjectData');
class RemoteObject {
constructor(remoteObject) {
this[REMOTE_OBJ_DATA] = remoteObject;
Object.assign(this, remoteObject);
}

// Future: [util.inspect.custom]() { ... }
[REMOTE_OBJ_INSPECT](recurseTimes, ctx) {
const opts = Object.assign({}, ctx, { depth: 0 });
const { description, type, preview, value } = this[REMOTE_OBJ_DATA];
if (type === 'object') {
if (preview && preview.properties) {
return formatPreview(preview, opts);
}
return opts.colors ? stylizeWithColor(description, 'special') : description;
} else if (type === 'function') {
return formatFunction(this[REMOTE_OBJ_DATA], opts);
}
return description;
return util.inspect(value, ctx);
}
return value;
}

function createRemoteObjectProxyHandler(/* client */) {
return {
get(target, name) {
if (name === REMOTE_OBJ_INSPECT || name === REMOTE_OBJ_DATA) return target[name];
if (name === 'then') return undefined; // This is gonna be tricky. :(
console.log('use client to get %j', name);
return undefined;
},

set(target, name, value) {
throw new Error(`Modifying remote objects is not implemented yet; .${name} = ${value}`);
},
};
}

function createRemoteAwareWriter(colors) {
return function remoteAwareWriter(value, originalOpts) {
const opts = Object.assign({}, originalOpts, { colors });
if (value && value[REMOTE_OBJ_INSPECT]) {
return value[REMOTE_OBJ_INSPECT](0, opts);
}
return util.inspect(value, opts);
};
}

function toCallback(promise, callback) {
Expand Down Expand Up @@ -408,6 +487,17 @@ function createCommandContext(inspector) {
}
}

function convertResultToRemoteObject({ result, wasThrown }) {
const { className, description } = result;
if (wasThrown) {
const err = new Error(extractErrorMessage(description));
err.stack = description;
Object.defineProperty(err, 'name', { value: className });
return err;
}
return new Proxy(new RemoteObject(result), createRemoteObjectProxyHandler(inspector.client));
}

const ctx = {
debugEval(code) {
// Repl asked for scope variables
Expand All @@ -423,7 +513,7 @@ function createCommandContext(inspector) {
};

return Debugger.evaluateOnCallFrame(params)
.then(formatValue);
.then(convertResultToRemoteObject);
},

exec(code) {
Expand Down Expand Up @@ -553,6 +643,12 @@ class Inspector {
['Debugger', 'Runtime'].forEach(domain => {
this[domain] = createAgentProxy(domain, this);
});
this.handleDebugEvent = (fullName, params) => {
const [domain, name] = fullName.split('.');
if (domain in this) {
this[domain].emit(name, params);
}
};

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

opts.writer = createRemoteAwareWriter(opts.useColors !== false);

this.repl = Repl.start(opts);

// Do not print useless warning
Expand Down Expand Up @@ -632,24 +730,6 @@ class Inspector {
});
}

handlePaused({ callFrames /* , reason, hitBreakpoints */ }) {
// this.pause();

// Save execution context's data
const topFrame = callFrames[0];
// const { scriptId, lineNumber } = this.client.currentSourceLocation = topFrame.location;
this.client.currentFrame = topFrame.callFrameId;

// const script = this.scripts[scriptId];
// const scriptUrl = script ? getRelativePath(script.url) : '[unknown]';
// this.print(`${reason} in ${scriptUrl}:${lineNumber + 1}`);

// this.watchers(true)
// .then(() => this.list(2))
// .then(() => {
// this.resume();
// }, this.error.bind(this));
}

controlEval(code, context, filename, callback) {
/* eslint no-param-reassign: 0 */
Expand Down Expand Up @@ -802,12 +882,6 @@ class Inspector {
}
}

handleScriptParsed({ scriptId, url }) {
if (url) {
this.scripts[scriptId] = { url };
}
}

// Spawns child process (and restores breakpoints)
trySpawn(cb) {
const breakpoints = this.breakpoints || [];
Expand Down Expand Up @@ -867,14 +941,7 @@ class Inspector {
const client = this.client = new Client();
let connectionAttempts = 0;

client.on('debugEvent', (fullName, params) => {
const [domain, name] = fullName.split('.');
if (domain in this) {
this[domain].emit(name, params);
}
});

client.on('Debugger.scriptParsed', this.handleScriptParsed.bind(this));
client.on('debugEvent', this.handleDebugEvent);

client.once('ready', () => {
// Restore breakpoints
Expand All @@ -895,16 +962,6 @@ class Inspector {
this.resume();
});

client.on('unhandledResponse', res => {
this.pause();
this.print(`\nunhandled res:${JSON.stringify(res)}`);
this.resume();
});

client.on('Debugger.paused', res => {
this.handlePaused(res);
});

const attemptConnect = () => {
++connectionAttempts;
this.stdout.write('.');
Expand Down

0 comments on commit f6b4b20

Please sign in to comment.