Skip to content

Commit d9a88be

Browse files
authored
Merge pull request #2311 from microsoft/copilot/add-symbol-based-mechanism
Implement Symbol.for("debug.properties") for custom property replacement in debugger
2 parents 6d05b46 + a2f8f99 commit d9a88be

9 files changed

Lines changed: 353 additions & 53 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
This changelog records changes to stable releases since 1.50.2. "TBA" changes here may be available in the [nightly release](https://github.com/microsoft/vscode-js-debug/#nightly-extension) before they're in stable. Note that the minor version (`v1.X.0`) corresponds to the VS Code version js-debug is shipped in, but the patch version (`v1.50.X`) is not meaningful.
44

5+
## Unreleased
6+
7+
- feat: add `Symbol.for("debug.properties")` for custom property replacement in debugger ([vscode#102181](https://github.com/microsoft/vscode/issues/102181))
8+
59
## 1.105 (September 2025)
610

711
- fix: slow sourcemap parsing for minified code ([#2265](https://github.com/microsoft/vscode-js-debug/issues/2265))

src/adapter/templates/getStringyProps.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
import { remoteFunction, templateFunction } from '.';
66

7-
const enum DescriptionSymbols {
7+
export const enum DescriptionSymbols {
88
// Our generic symbol
99
Generic = 'debug.description',
1010
// Node.js-specific symbol that is used for some Node types https://nodejs.org/api/util.html#utilinspectcustom
1111
Node = 'nodejs.util.inspect.custom',
12+
// Symbol for custom property replacement
13+
Properties = 'debug.properties',
1214

1315
// Depth for `nodejs.util.inspect.custom`
1416
Depth = 2,
@@ -20,7 +22,11 @@ const enum DescriptionSymbols {
2022
* use them inside the description functions.
2123
*/
2224
export const getDescriptionSymbols = remoteFunction(function() {
23-
return [Symbol.for(DescriptionSymbols.Generic), Symbol.for(DescriptionSymbols.Node)];
25+
return [
26+
Symbol.for(DescriptionSymbols.Generic),
27+
Symbol.for(DescriptionSymbols.Node),
28+
Symbol.for(DescriptionSymbols.Properties),
29+
];
2430
});
2531

2632
declare const runtimeArgs: [symbol[]];
@@ -34,6 +40,7 @@ export const getStringyProps = templateFunction(function(
3440
maxLength: number,
3541
customToString: (defaultRepr: string) => unknown,
3642
) {
43+
let customProps = false;
3744
const out: Record<string, string> = {};
3845
const defaultPlaceholder = '<<default preview>>';
3946
if (typeof this !== 'object' || !this) {
@@ -63,7 +70,7 @@ export const getStringyProps = templateFunction(function(
6370

6471
if (typeof value === 'object' && value) {
6572
let str: string | undefined;
66-
for (const sym of runtimeArgs[0]) {
73+
for (const sym of runtimeArgs[0].slice(0, 2)) {
6774
if (typeof value[sym] !== 'function') {
6875
continue;
6976
}
@@ -85,7 +92,14 @@ export const getStringyProps = templateFunction(function(
8592
}
8693
}
8794

88-
return out;
95+
try {
96+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
97+
customProps = typeof (this as any)[runtimeArgs[0][2]] === 'function';
98+
} catch {
99+
// ignored
100+
}
101+
102+
return { out, customProps };
89103
});
90104

91105
export const getToStringIfCustom = templateFunction(function(
@@ -133,3 +147,33 @@ export const getToStringIfCustom = templateFunction(function(
133147
}
134148
}
135149
});
150+
151+
/**
152+
* Checks if the object has a custom properties function via Symbol.for("debug.properties")
153+
* and returns the replacement object if it does. Returns undefined otherwise.
154+
* The symbols array is passed via runtimeArgs[0], with properties symbol at index 2.
155+
*/
156+
export const getCustomProperties = templateFunction(function(this: unknown) {
157+
const propertiesSymbol: symbol = (runtimeArgs as unknown as [symbol[]])[0][2];
158+
159+
if (typeof this !== 'object' || !this) {
160+
return undefined;
161+
}
162+
163+
// Check if the object has the debug.properties symbol
164+
if (typeof (this as Record<symbol, () => unknown>)[propertiesSymbol] !== 'function') {
165+
return undefined;
166+
}
167+
168+
try {
169+
const result = (this as Record<symbol, () => unknown>)[propertiesSymbol]();
170+
// Only return if we got a valid object back
171+
if (typeof result === 'object' && result !== null) {
172+
return result;
173+
}
174+
} catch {
175+
// If the function throws, we'll just return undefined and use default properties
176+
}
177+
178+
return undefined;
179+
});

0 commit comments

Comments
 (0)