question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Consider adding a mechanism akin to .NET's 'DebuggerTypeProxyAttribute' for JS/TS debugging

See original GitHub issue

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:

  1. A developer define a well-known Symbol to add as a key to a class (i.e., const debuggerProxySym = Symbol.for("vscode:debugger-type-proxy").
  2. A developer can add that symbol as a method of the class (i.e., class C { [debuggerProxySym]() { ... } }.
  3. In the launch.config, the well-known symbol identity could be added as a configuration option.
  4. When a value with the configured symbol is shown in the “Watch” or “Locals” window, that method is called and its result is used in place of the original value.

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 {}, where DebuggerTypeProxy 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:

  • A debugger-display symbol to provide a simplified text view for a value in the “Locals” or “Watch” window, similar to .NET’s DebuggerDisplayAttribute.
  • A debugger-visualizer symbol to further control how a value in “Locals” or “Watch” is rendered (though this is obviously more complex).
  • A 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’s DebuggerHiddenAttribute.
  • As well as symbols for other, related attributes such as 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, and Type to make our lives easier, but first-class support for this behavior would be much more appealing:

What we do today: image

What would be nice to have in the future:

// debug.ts
namespace ts.Debug {
  export const debuggerDisplay = Symbol.for("vscode:debugger-display");
  export const debuggerTypeProxy = Symbol.for("vscode:debugger-type-proxy");
  export const debuggerHidden = Symbol.for("vscode:debugger-hidden");
  export const debuggerBrowsable = Symbol.for("vscode:debugger-browsable");
}
// core.ts
export function map<T, U>(array: T[] | undefined, cb: (v: T) => U): U[] | undefined { ... }
// F11 steps over calls to `map`, but still steps into `cb`...
map[Debug.debuggerHidden] = true;

// utilities.ts
function Symbol() { ... }
Symbol.prototype[Debug.debuggerDisplay] = "{escapedName}";

function Type() { ... }
Type.prototype[Debug.debuggerTypeProxy] = function() { 
  return { 
    ...this,
    [Debug.debuggerDisplay]: "{name} {flagsString}",
    [Debug.debuggerBrowsable]: { name: false, flagsString: false },
    name: this.symbol?.escapedName,
    flagsString: Debug.formatTypeFlags(this.flags)
  }
};
// launch.json
{
  // ...
  "configurations": [
    {
      // ...
      "debugSymbols": {
        "display": ["vscode:debugger-display", "nodejs.util.inspect.custom"], // the key for `require("util").inspect.custom` in NodeJS
        "typeProxy": ["vscode:debugger-type-proxy"],
        "hidden": ["vscode:debugger-hidden"],
        "browsable": ["vscode:debugger-browsable"]
      }
    }
  ]
}

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:13 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
digeffcommented, Nov 4, 2020

@justingrant I’m not sure if this launch.json parameter has been released yet, if it is:

customDescriptionGenerator:            'function (def) { if (this.toString) return this.toString(); else return def }' 

should call this.toString() to generate the description of objects shown in variables, watch, etc…

1reaction
hillincommented, Sep 8, 2022

@hillin __registerGlobalInspect write where to use this?

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

No results found

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found