Skip to content

Implement Symbol.for("debug.properties") for custom property replacement in debugger#2311

Open
Copilot wants to merge 4 commits intomainfrom
copilot/add-symbol-based-mechanism
Open

Implement Symbol.for("debug.properties") for custom property replacement in debugger#2311
Copilot wants to merge 4 commits intomainfrom
copilot/add-symbol-based-mechanism

Conversation

Copy link
Contributor

Copilot AI commented Jan 23, 2026

Implementation Plan for Symbol-based Property Replacement

  • Explore codebase and understand existing implementation
  • Create new template function getCustomProperties for Symbol.for("debug.properties") support
  • Update to use unified symbol array instead of separate function
  • Update VariableContext.createObjectPropertyVars to check for and apply custom properties
  • Optimize to only check for custom properties when symbol is present (avoid round trip cost)
  • Add escape hatch property "..." to show original object when custom properties are used
  • Add visual indication that properties are virtual (using presentation hints)
  • Fix TypeScript compilation errors
  • Create comprehensive tests for the new feature
  • Address code review feedback
  • Run security checks (no vulnerabilities found)
  • Update documentation (README.md and CHANGELOG.md)
  • Optimize performance per code review feedback
  • Eliminate code duplication via helper method

Summary

Latest changes:

  • Extracted fetchObjectProperties helper method to eliminate duplication
  • Helper method encapsulates the logic for fetching accessors, own properties, and stringy props
  • Now used in both initial fetch and when re-fetching for custom properties object
  • Cleaner, more maintainable code with no functional changes
Original prompt

This section details on the original issue you should resolve

<issue_title>Feature request: Symbol-based mechanism for custom property replacement (debug.properties / debug.browsable)</issue_title>
<issue_description>## Summary

Symbol.for("debug.description") provides a great mechanism for customizing object descriptions. However, there doesn't appear to be an equivalent mechanism for custom property replacements. When debugging observables or similar complex objects, the debugger displays many internal/implementation fields that aren't useful for debugging purposes.

Current state

From what I can see in the codebase:

  • Symbol.for("debug.description") works great for description text
  • Symbol.for("nodejs.util.inspect.custom") is also supported
  • ⚠️ customPropertiesGenerator exists but is marked deprecated
  • ❌ No symbol-based mechanism for property filtering/replacement

Use case

For observables and other complex state management objects, the default property display shows internal implementation details that aren't useful for debugging:

// Current behavior when hovering:
observable = {
  _observers: Set(3),
  _value: "actual value",
  _scheduler: {...},
  _isDisposed: false,
  _derivedFrom: [...],
  // ... many more internal fields
}

// Desired behavior:
observable = {
  value: "actual value",
  subscriberCount: 3
}

Proposal

Building on the original proposal in microsoft/vscode#102181, it would be valuable to add support for additional symbols:

class Observable {
  // Already working - description text
  [Symbol.for('debug.description')]() {
    return `Observable(${this._value})`;
  }

  // Proposed - return replacement object for property display
  [Symbol.for('debug.properties')]() {
    return {
      value: this._value,
      subscriberCount: this._observers.size
    };
  }

  // Alternative approach - control property visibility
  [Symbol.for('debug.browsable')]() {
    return {
      _observers: false,  // hide
      _scheduler: false,  // hide
      _value: 'value',    // rename to 'value'
    };
  }
}

This would allow library authors to provide better debugging experiences without requiring users to configure customPropertiesGenerator in their launch.json.

Related issues

Comments on the Issue (you are @copilot in this section)

@hediet If debug.properties are used, there should still be an escape hatch to really debug the underlying object (for example by always having the collapsed (but unfoldable) property "...": originalValue). Also, there should be some indication that the properties are "virtual", that could be this property - maybe in a different font or with an icon.

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Add symbol-based mechanism for custom property replacement Implement Symbol.for("debug.properties") for custom property replacement in debugger Jan 23, 2026
Copilot AI requested a review from hediet January 23, 2026 18:25
@hediet
Copy link
Member

hediet commented Jan 24, 2026

@copilot please show me js code I can use to test this when i debug it

Copy link
Contributor Author

Copilot AI commented Jan 24, 2026

@copilot please show me js code I can use to test this when i debug it

I've created comprehensive test files for you! Here are three files ready to use:

1. Node.js Test (test-debug-properties.js):

class Observable {
  constructor(value) {
    this._value = value;
    this._observers = new Set(['observer1', 'observer2', 'observer3']);
    this._scheduler = { name: 'scheduler', details: 'complex object' };
    this._isDisposed = false;
  }

  [Symbol.for('debug.properties')]() {
    return {
      value: this._value,
      subscriberCount: this._observers.size
    };
  }

  [Symbol.for('debug.description')]() {
    return `Observable(${this._value})`;
  }
}

const observable = new Observable('test value');
debugger; // Inspect 'observable' here

Quick start:

node --inspect-brk test-debug-properties.js
# Then attach VS Code debugger

2. Browser Test (test-debug-properties.html):
Open in Chrome/Edge, press F12, and the script will pause at the debugger statement.

3. VS Code Launch Config (launch.json):
Add to .vscode/launch.json for one-click debugging.

Test objects included:

  • observable - Basic custom properties (value, subscriberCount)
  • stateManager - Complex state with simplified view
  • eventEmitter - Map-based structure simplified
  • brokenClass - Tests error handling (falls back gracefully)
  • regularClass - No custom properties (for comparison)
  • nested / array - Nested structures with custom properties

What to look for:

  1. Custom properties show only what's returned by Symbol.for('debug.properties')
  2. ... property provides escape hatch to original object
  3. Properties should be marked as "virtual" (presentation hint)
  4. Error cases fall back to default properties without crashing

All files are in /tmp/ directory and ready to use!

@hediet hediet closed this Jan 24, 2026
@hediet hediet force-pushed the copilot/add-symbol-based-mechanism branch from 9ff9326 to 4330850 Compare January 24, 2026 01:34
@hediet hediet reopened this Jan 24, 2026
@hediet hediet marked this pull request as ready for review January 24, 2026 01:35
@hediet hediet enabled auto-merge January 24, 2026 01:35
@hediet hediet requested review from connor4312 and removed request for hediet January 24, 2026 01:35
@vs-code-engineering vs-code-engineering bot added this to the January 2026 milestone Jan 24, 2026
@hediet hediet modified the milestones: January 2026, February 2026 Jan 26, 2026
/**
* Separate function that initializes the properties symbol for custom property replacement.
*/
export const getPropertiesSymbol = remoteFunction(function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot don't use a separate function for this, you can do this in getDescriptionSymbols

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Merged getPropertiesSymbol into getDescriptionSymbols which now returns an array of 3 symbols: [Generic, Node, Properties]. Updated getCustomProperties to access the properties symbol via runtimeArgs[0][2]. (c7ea916)

// Check for Symbol.for("debug.properties") custom property replacement
if (!this.settings.customPropertiesGenerator && !skipSymbolBasedCustomProperties) {
try {
const customPropsResult = await this.cdp.Runtime.callFunctionOn({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to pay a round trip cost on every single object representation. In practice only a very small minority of objects will define custom properties.

Can we instead only trigger this path if we see the custom property symbol as part of whichever of accessorsProperties, ownProperties, stringyProps the symbol would appear as?

Implement the recursion/loop in a pragmatic way that avoids duplication

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented the optimization. Now checking for the presence of Symbol.for("debug.properties") in the object's properties (by looking for p.symbol?.description === 'Symbol(debug.properties)') before making the round trip. Only calls getCustomProperties if the symbol is found. (c7ea916)

…ts without the symbol

- Merged getPropertiesSymbol into getDescriptionSymbols to return array with 3 symbols
- Check for debug.properties symbol presence before making round trip
- Only call getCustomProperties if Symbol.for("debug.properties") is found in object properties
- Updated getCustomProperties to use symbols array from runtimeArgs[0][2]

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 30, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • esm.ubuntu.com
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https (dns block)
  • plugins.dprint.dev
    • Triggering command: /home/REDACTED/work/vscode-js-debug/vscode-js-debug/node_modules/dprint/dprint /home/REDACTED/work/vscode-js-debug/vscode-js-debug/node_modules/dprint/dprint check (dns block)

If you need me to access, download, or install something from one of these locations, you can either:


if (hasDebugPropertiesSymbol) {
try {
const customPropsResult = await this.cdp.Runtime.callFunctionOn({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a lot of duplication here, you can do better @copilot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to eliminate duplication. Extracted the property fetching logic into a fetchObjectProperties helper method that's now used in both the initial fetch and when re-fetching for custom properties. (3773fc2)

…ication

Extracted the duplicated code for fetching object properties (accessors, own properties, and stringy props) into a reusable private method fetchObjectProperties(). This eliminates the duplication that existed when re-fetching properties for custom property objects.

Co-authored-by: connor4312 <2230985+connor4312@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 30, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • esm.ubuntu.com
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Symbol-based mechanism for custom property replacement (debug.properties / debug.browsable)

3 participants