Skip to content

Commit a2f8f99

Browse files
committed
improve
1 parent 193b16b commit a2f8f99

4 files changed

Lines changed: 120 additions & 64 deletions

File tree

src/adapter/templates/getStringyProps.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
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
@@ -40,6 +40,7 @@ export const getStringyProps = templateFunction(function(
4040
maxLength: number,
4141
customToString: (defaultRepr: string) => unknown,
4242
) {
43+
let customProps = false;
4344
const out: Record<string, string> = {};
4445
const defaultPlaceholder = '<<default preview>>';
4546
if (typeof this !== 'object' || !this) {
@@ -69,7 +70,7 @@ export const getStringyProps = templateFunction(function(
6970

7071
if (typeof value === 'object' && value) {
7172
let str: string | undefined;
72-
for (const sym of runtimeArgs[0]) {
73+
for (const sym of runtimeArgs[0].slice(0, 2)) {
7374
if (typeof value[sym] !== 'function') {
7475
continue;
7576
}
@@ -91,7 +92,14 @@ export const getStringyProps = templateFunction(function(
9192
}
9293
}
9394

94-
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 };
95103
});
96104

97105
export const getToStringIfCustom = templateFunction(function(

src/adapter/variableStore.ts

Lines changed: 84 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { getArrayProperties } from './templates/getArrayProperties';
2727
import { getArraySlots } from './templates/getArraySlots';
2828
import { getNodeChildren } from './templates/getNodeChildren';
2929
import {
30+
DescriptionSymbols,
3031
getCustomProperties,
3132
getDescriptionSymbols,
3233
getStringyProps,
@@ -174,6 +175,8 @@ interface IContextInit {
174175
presentationHint?: Dap.VariablePresentationHint;
175176
/** How this variable should be sorted in results, in ascending numeric order. */
176177
sortOrder?: number;
178+
/** Indicates if this variable represents a custom property from debug.properties. */
179+
isCustomProperty?: boolean;
177180
}
178181

179182
interface IContextSettings {
@@ -195,6 +198,10 @@ class VariableContext {
195198
public readonly presentationHint?: Dap.VariablePresentationHint;
196199
/** Sort order set from the parent. */
197200
public readonly sortOrder: number;
201+
/**
202+
* Indicates if this variable represents a custom property from debug.properties.
203+
*/
204+
public readonly isCustomProperty?: boolean;
198205

199206
public get customDescriptionGenerator() {
200207
return this.settings.customDescriptionGenerator;
@@ -213,6 +220,7 @@ class VariableContext {
213220
this.name = ctx.name;
214221
this.presentationHint = ctx.presentationHint;
215222
this.sortOrder = ctx.sortOrder || SortOrder.Default;
223+
this.isCustomProperty = ctx.isCustomProperty;
216224
}
217225

218226
/**
@@ -355,7 +363,6 @@ class VariableContext {
355363
): Promise<Variable[]> {
356364
const properties: (Promise<Variable[]> | Variable[])[] = [];
357365
let originalObject: Cdp.Runtime.RemoteObject | undefined;
358-
let hasCustomProperties = false;
359366

360367
if (this.settings.customPropertiesGenerator) {
361368
const { result, errorDescription } = await this.evaluateCodeForObject(
@@ -394,40 +401,33 @@ class VariableContext {
394401
);
395402
if (!accessorsProperties || !ownProperties) return [];
396403

397-
// Check for Symbol.for("debug.properties") custom property replacement
398-
// Only do this if we haven't already applied customPropertiesGenerator and if not explicitly skipped
399-
if (!this.settings.customPropertiesGenerator && !skipSymbolBasedCustomProperties) {
400-
// Check if the object has Symbol.for("debug.properties") by looking for it in the properties
401-
const hasDebugPropertiesSymbol = [...accessorsProperties.result, ...ownProperties.result]
402-
.some(
403-
p => p.symbol?.description === 'Symbol(debug.properties)',
404-
);
404+
if (
405+
!this.settings.customPropertiesGenerator && stringyProps.customProps
406+
&& !skipSymbolBasedCustomProperties
407+
) {
408+
try {
409+
const customPropsResult = await this.cdp.Runtime.callFunctionOn({
410+
functionDeclaration: getCustomProperties.decl(),
411+
arguments: [await this.getDescriptionSymbols(object.objectId)],
412+
objectId: object.objectId,
413+
throwOnSideEffect: true,
414+
});
405415

406-
if (hasDebugPropertiesSymbol) {
407-
try {
408-
const customPropsResult = await this.cdp.Runtime.callFunctionOn({
409-
functionDeclaration: getCustomProperties.decl(),
410-
arguments: [await this.getDescriptionSymbols(object.objectId)],
411-
objectId: object.objectId,
412-
throwOnSideEffect: true,
413-
});
414-
415-
if (customPropsResult && customPropsResult.result.objectId) {
416-
// Store the original object for the escape hatch
417-
originalObject = object;
418-
hasCustomProperties = true;
419-
// Replace with the custom properties object and re-fetch its properties
420-
object = customPropsResult.result;
421-
422-
// Re-fetch properties for the custom properties object
423-
[accessorsProperties, ownProperties, stringyProps] = await this.fetchObjectProperties(
424-
object.objectId!,
425-
);
426-
if (!accessorsProperties || !ownProperties) return [];
427-
}
428-
} catch {
429-
// If anything goes wrong, just use the original object
416+
if (customPropsResult && customPropsResult.result.objectId) {
417+
// Store the original object for the escape hatch
418+
originalObject = object;
419+
420+
// Replace with the custom properties object and re-fetch its properties
421+
object = customPropsResult.result;
422+
423+
// Re-fetch properties for the custom properties object
424+
[accessorsProperties, ownProperties, stringyProps] = await this.fetchObjectProperties(
425+
object.objectId!,
426+
);
427+
if (!accessorsProperties || !ownProperties) return [];
430428
}
429+
} catch {
430+
// If anything goes wrong, just use the original object
431431
}
432432
}
433433

@@ -462,15 +462,22 @@ class VariableContext {
462462
// Push own properties & accessors and symbols
463463
for (const propertiesCollection of [propertiesMap.values(), propertySymbols.values()]) {
464464
for (const p of propertiesCollection) {
465-
const contextInit = hasCustomProperties
466-
? { presentationHint: { kind: 'virtual' as const } }
465+
if ('symbol' in p && p.symbol?.description === `Symbol(${DescriptionSymbols.Properties})`) {
466+
continue;
467+
}
468+
469+
const contextInit = originalObject
470+
? {
471+
presentationHint: { kind: 'virtual' as const },
472+
isCustomProperty: !!originalObject,
473+
}
467474
: undefined;
468475
properties.push(
469476
this.createPropertyVar(
470477
p,
471478
object,
472-
stringyProps?.hasOwnProperty(p.name)
473-
? localizeIndescribable(stringyProps[p.name])
479+
stringyProps.out?.hasOwnProperty(p.name)
480+
? localizeIndescribable(stringyProps.out[p.name])
474481
: undefined,
475482
contextInit,
476483
),
@@ -479,14 +486,19 @@ class VariableContext {
479486
}
480487

481488
for (const property of ownProperties.privateProperties ?? []) {
482-
const presentationHint = hasCustomProperties
483-
? { kind: 'virtual' as const, visibility: 'private' as const }
484-
: { visibility: 'private' as const };
485-
properties.push(
486-
this.createPropertyVar(property, object, undefined, {
487-
presentationHint,
489+
const contextInit = originalObject
490+
? {
491+
presentationHint: { kind: 'virtual' as const, visibility: 'private' as const },
488492
sortOrder: SortOrder.Private,
489-
}),
493+
isCustomProperty: !!originalObject,
494+
}
495+
: {
496+
presentationHint: { visibility: 'private' as const },
497+
sortOrder: SortOrder.Private,
498+
isCustomProperty: !!originalObject,
499+
};
500+
properties.push(
501+
this.createPropertyVar(property, object, undefined, contextInit),
490502
);
491503
}
492504

@@ -562,6 +574,7 @@ class VariableContext {
562574
const ctx: Required<IContextInit> = {
563575
name: p.name,
564576
sortOrder: SortOrder.Default,
577+
isCustomProperty: false,
565578
...contextInit,
566579
presentationHint: {
567580
...contextInit?.presentationHint,
@@ -654,7 +667,7 @@ class Variable implements IVariable {
654667
* Gets the accessor though which this object can be read.
655668
*/
656669
public get accessor(): string {
657-
const { parent, name } = this.context;
670+
const { parent, name, isCustomProperty } = this.context;
658671
if (parent instanceof AccessorVariable) {
659672
return parent.accessor;
660673
}
@@ -663,6 +676,19 @@ class Variable implements IVariable {
663676
return this.context.name;
664677
}
665678

679+
if (isCustomProperty) {
680+
const prefix = `${parent.accessor}[Symbol.for(${
681+
JSON.stringify(DescriptionSymbols.Properties)
682+
})]()`;
683+
if (isNumberOrNumeric(name)) {
684+
return `${prefix}[${name}]`;
685+
}
686+
if (identifierRe.test(name)) {
687+
return `${prefix}.${name}`;
688+
}
689+
return `${prefix}[${JSON.stringify(name)}]`;
690+
}
691+
666692
// Maps and sets:
667693
const grandparent = parent.context.parent;
668694
if (grandparent instanceof Variable) {
@@ -953,6 +979,19 @@ class ObjectVariable extends Variable implements IMemoryReadable {
953979
* Used as the "..." escape hatch to view raw object properties.
954980
*/
955981
class PropertiesGeneratorEscapeHatchVariable extends ObjectVariable {
982+
/**
983+
* Override accessor to return the parent's accessor (the original object).
984+
* This ensures children of the escape hatch use the correct accessor path
985+
* (e.g., `c._b` instead of `c["..."]._b`).
986+
*/
987+
public override get accessor(): string {
988+
const { parent } = this.context;
989+
if (parent instanceof Variable) {
990+
return parent.accessor;
991+
}
992+
return super.accessor;
993+
}
994+
956995
public override getChildren(_params: Dap.VariablesParamsExtended) {
957996
return this.context.createObjectPropertyVars(
958997
this.remoteObject,

src/test/variables/variables-setvariable-evaluatename.txt

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@ undefined
1616
b["[[Prototype]]"]
1717
b["[[Prototype]]"]
1818
c
19-
c._b
20-
c.$a
21-
c[42]
22-
c.c
23-
c["d d"]
24-
c.e
25-
c.e.nested
26-
c.e.nested[0]
27-
c.e.nested[0].obj
28-
c.e.nested[0]["[[Prototype]]"]
29-
c.e.nested.length
30-
c.e.nested["[[Prototype]]"]
31-
c.e.nested["[[Prototype]]"]
32-
c.e["[[Prototype]]"]
33-
c["Symbol(wut)"]
19+
c[Symbol.for("debug.properties")]()[2]
20+
c[Symbol.for("debug.properties")]().a
21+
c[Symbol.for("debug.properties")]()["c c"]
22+
c
23+
c._b
24+
c.$a
25+
c[42]
26+
c.c
27+
c["d d"]
28+
c.e
29+
c.e.nested
30+
c.e.nested[0]
31+
c.e.nested[0].obj
32+
c.e.nested[0]["[[Prototype]]"]
33+
c.e.nested.length
34+
c.e.nested["[[Prototype]]"]
35+
c.e.nested["[[Prototype]]"]
36+
c.e["[[Prototype]]"]
37+
c["Symbol(wut)"]
38+
c["[[Prototype]]"]
3439
c["[[Prototype]]"]
3540
this

src/test/variables/variablesTest.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,8 @@ describe('variables', () => {
480480
let b = [1, 2, 3, 4];
481481
b.prop = '';
482482
let c = { $a: 1, _b: 2, c: 3, 'd d': 4, [42]: 5,
483-
e: { nested: [{ obj: true }]}, [Symbol('wut')]: 'wut' };
483+
e: { nested: [{ obj: true }]}, [Symbol('wut')]: 'wut',
484+
[Symbol.for('debug.properties')]: () => ({ a: 1, 2: 3, 'c c': 4 }) };
484485
debugger;
485486
})();
486487
`,
@@ -499,6 +500,9 @@ describe('variables', () => {
499500
};
500501

501502
await walkVariables(p.dap, v, (variable, depth) => {
503+
if (depth > 10) {
504+
return false;
505+
}
502506
p.log(' '.repeat(depth) + variable.evaluateName);
503507
return (
504508
!variable.name.startsWith('__')

0 commit comments

Comments
 (0)