-
Notifications
You must be signed in to change notification settings - Fork 29.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider adding a mechanism akin to .NET's 'DebuggerTypeProxyAttribute' for JS/TS debugging #102181
Comments
@rbuckton in simpler terms you are proposing a mechanism for the JavaScript debugger to allow end users to provide custom @connor4312 this sounds like a worthwhile feature to have. |
Something like that, yes. It is more than just toString, however. DebuggerTypeProxy in .NET substitutes the value used in the Watch window with another one that could be used to provide more useful context when debugging. Debugger Hidden and DebuggerStepThrough control debugger stepping behavior like "Step Into" and "Step Over". |
Apologies for the delay in responding here. I like this idea, and this is similar to some work that @digeff has been doing in js-debug. His work has been more around setting custom descriptors and properties in the launch.json, but this is less ideal for a number of reasons. I didn't know Symbol.for was available. That makes implementation more streamlined and feels very natural with other modern features like iterators. Could have an optional npm package that exports those symbols, along with TS decorators and designed to avoid producing any code if running a production build with a bundler. I'm not sure that I would want the symboles to be user-configurable, since the presence of a symbol method on objects entails a contract with js-debug that arbitrary symbols might not uphold. (And if the contract is not being broken, I'm not sure why a user would want a custom symbol). I'll put this on the November iteration, for which feature development starts on the 16th (slightly delayed due to grooming from last month and vacations) |
I'm part of the team that's working on the polyfill for the soon-to-be-Stage3 JS Temporal proposal and we'd definitely be interested in a a good way to show a text representation of Temporal objects in the VSCode debugger. Temporal objects have no own properties, only property getters on the prototype. The VSCode debug hover experience is awful today. Example:
Currently, VSCode doesn't seem to be calling FWIW, if all VSCode did was call
I assume that there'd be a default configuration that OSS libraries could use, so that individual debugger users wouldn't have to opt into getting better debug display? The requirement for debugger users to opt in to get Chrome's object formatters is a pretty big adoption blocker. |
@justingrant I'm not sure if this launch.json parameter has been released yet, if it is:
should call this.toString() to generate the description of objects shown in variables, watch, etc... |
Thanks for the input, Justin. It sounds like you have a very similar use case that could be served in the same way.
This is correct |
@justingrant you said:
Just to be precise here: VS Code requests UI strings from a debug adapter via the Debug Adapter Protocol (DAP), it does not interact with programs or debuggers directly. So calling "toString()" must be done in the debug adapter. I do not see an opportunity for "generic support" in VS Code. @connor4312 So this issue is a feature request for js-debug. |
Incredible: this works a treat for me (using decimal) A slight edit to save peeps a few minutes in tweaking their output: add a test for
|
Can we have this for primitives as well? I want my milliseconds to display as dates, preferably with a custom format. |
The __registerGlobalInspect function/** Register a global __inspectObject function to inspect arbitrary value. */
export function __registerGlobalInspect() {
if (window && !(window as any).__inspectObject) {
const inspectSingleObject = (element: any) => {
if (element) {
if (typeof element === 'object') {
if (element.__inspect && typeof element.__inspect === 'function') {
return element.__inspect();
}
if (Array.isArray(element)) {
return `[${element.length}]`;
}
const ctor = Object.getPrototypeOf(element).constructor;
if (ctor === Set) {
return `Set(${element.size})`;
}
if (ctor === Map) {
return `Map(${element.size})`;
}
if (ctor === WeakSet) {
return `WeakSet(${element.size})`;
}
if (ctor === WeakMap) {
return `WeakMap(${element.size})`;
}
if (ctor === Object) {
return '{…}';
}
return Object.getPrototypeOf(element).constructor.name;
}
if (typeof element === 'function') {
return `<Function>`;
}
}
return JSON.stringify(element);
};
const anyWindow = window as any;
anyWindow.__inspectObject = function (target: any) {
if (target.__inspect && typeof target.__inspect === 'function') {
return target.__inspect();
}
const maxPropertyCount = 5;
const elements = [];
if (Array.isArray(target)) {
for (let i = 0; i < Math.min(maxPropertyCount, target.length); ++i) {
elements.push(inspectSingleObject(target[i]));
}
return `(${target.length}) [${elements.join(', ')}${
target.length > maxPropertyCount ? '…' : ''
}]`;
}
for (let key in target) {
if (target.hasOwnProperty(key)) {
if (elements.length >= maxPropertyCount) {
break;
}
elements.push(`${key}: ${inspectSingleObject(target[key])}`);
}
}
return `{${elements.join(', ')}}`;
};
}
} Usage: call "customDescriptionGenerator": "function (def) { return this.__inspect ? this.__inspect() : window?.__inspectObject ? window.__inspectObject(this) : def }" This approach shows custom inspection text if an object has |
@hillin __registerGlobalInspect write where to use this? |
Btw, if an object has a custom (non-native) |
The entrance of your script, i.e. the first line of your script. If you are using a JS framework, call it in the initialization or constructor of the root component. |
Both Firefox and Chromium support essentially the same API for object formatters. However, formatting is done by creating a DOM structure, and Chromium notes that there may be a var formatter = {
header: function(x) {
if (x === custom) {
return ["span", {"style": "background-color: #fcc"}, "Hello!"];
} else {
return null;
}
},
hasBody: function(x) {
return x === custom;
},
body: function(x) {
var selfRef = ["object", {"object": custom}];
return ["ol", {}, ["li", {}, "world!"], ["li", {}, selfRef]];
}
};
window.devtoolsFormatters = [formatter]; While Firefox restricts usable CSS properties, this is still something that is not approachable from the VS Code JavaScript debugger, because:
And on a personal note, the way they're registered on a window global is kind of awkward 😛 So, I think I will adopt the symbol approach. I do not plan to prefix it with class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
[Symbol.for('debug.description')]() {
return `${this.start} -> ${this.end}`;
}
} |
…ct descriptions Fixes microsoft/vscode#102181
…ct descriptions (#1775) Fixes microsoft/vscode#102181
When debugging a program written for .NET in Visual Studio, there are certain attributes that can be used to control how the debugger interacts with various objects. The
DebuggerTypeProxyAttribute
provides a way to inform the debugger how to represent a value in the "Watch" view.JS does not have a way to define such metadata for its types, and while the Decorators proposal is still under active development it would still likely only serve as half the story.
An alternative solution I am proposing is roughly this:
Symbol
to add as a key to a class (i.e.,const debuggerProxySym = Symbol.for("vscode:debugger-type-proxy")
.class C { [debuggerProxySym]() { ... } }
.In this way, configuration is flexible and up to the end user and it doesn't depend on proposing new syntax to ECMAScript. In addition, should the Decorators proposal land as a feature in the future this could also leverage decorators (i.e.,
@DebuggerTypeProxy(() => { ... }) class C {}
, whereDebuggerTypeProxy
merely sets the same kind of symbol, above).Also, the default configuration could conceivably also include the existing
inspect.custom
symbol in NodeJS's"util"
module.A similar design could theoretically be employed to allow for behaviors similar to .NET's other
DebuggerXAttribute
controls, such as:debugger-display
symbol to provide a simplified text view for a value in the "Locals" or "Watch" window, similar to .NET'sDebuggerDisplayAttribute
.debugger-visualizer
symbol to further control how a value in "Locals" or "Watch" is rendered (though this is obviously more complex).debugger-hidden
symbol to force the debugger to step over code inside of a function/method with this property (but still step into methods invoked by the function), similar to .NET'sDebuggerHiddenAttribute
.DebuggerNonUserCodeAttribute
,DebuggerStepThroughAttribute
, etc.An example of how this would help can be seen in the TypeScript compiler itself. Since TypeScript enums are compiled down to number values, its often complex to correlate a flag value back to the enum value. When debugging, we explicitly add additional properties to internal classes like
Node
,Symbol
, andType
to make our lives easier, but first-class support for this behavior would be much more appealing:What we do today:
What would be nice to have in the future:
The text was updated successfully, but these errors were encountered: