Skip to content

Commit

Permalink
fix: improve previews of objects and sets (#1726)
Browse files Browse the repository at this point in the history
Objects and sets now show their values under the first expansion instead
of requiring the user to expand an additional level. Also, their
accessors are fixed which allows "copy value" and "copy as expression"/

![](https://memes.peet.io/img/23-06-2bb71702-f805-46d8-aff2-6cf3b8dd40c1.png)
  • Loading branch information
connor4312 authored Jun 10, 2023
1 parent 9bf6c47 commit 339c332
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This changelog records changes to stable releases since 1.50.2. "TBA" changes he
- fix: terminal launches sometimes sending commands too soon ([#1642](https://github.com/microsoft/vscode-js-debug/issues/1642))
- fix: step into `eval` when `pauseForSourceMap` is true does not pause on next available line ([#1692](https://github.com/microsoft/vscode-js-debug/issues/1692))
- fix: useWebview debug sessions getting stuck if program exits without attaching ([#1666](https://github.com/microsoft/vscode-js-debug/issues/1666))
- fix: improve the display of map and set entries
- fix: do not to translate "promise rejection" ([#1658](https://github.com/microsoft/vscode-js-debug/issues/1658))
- fix: breakpoints not hitting early on in nested sourcemapped programs ([#1704](https://github.com/microsoft/vscode-js-debug/issues/1704))
- fix: sourcemap predictor not filtering nested session on windows ([#1719](https://github.com/microsoft/vscode-js-debug/issues/1719))
Expand Down
46 changes: 44 additions & 2 deletions src/adapter/objectPreview/betterTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ export type TFunction = {
// defined in V8, undefined in Hermes
description?: string;
};
export type FunctionPreview = { type: 'function'; subtype: undefined; description: string };
export type FunctionPreview = {
type: 'function';
subtype: undefined;
description: string;
entries?: undefined;
properties?: undefined;
overflow?: undefined;
};
export type FunctionObj = TFunction;

export type TNode = {
Expand All @@ -38,6 +45,32 @@ export type TNode = {
export type NodePreview = TNode & ObjectPreview;
export type NodeObj = TNode & { preview: NodePreview };

export type TSet = {
type: 'object';
subtype: 'set';
className: string;
description: string;
};
export type SetPreview = TSet & {
entries: { key?: undefined; value: AnyPreview }[];
properties: PropertyPreview[];
overflow: boolean;
};
export type SetObj = TSet & { preview: SetPreview };

export type TMap = {
type: 'object';
subtype: 'map';
className: string;
description: string;
};
export type MapPreview = TMap & {
entries: { key: AnyPreview; value: AnyPreview }[];
properties: PropertyPreview[];
overflow: boolean;
};
export type MapObj = TMap & { preview: MapPreview };

export type TString = {
type: 'string';
value: string;
Expand Down Expand Up @@ -110,6 +143,8 @@ export type AnyObject =
| ObjectObj
| NodeObj
| ArrayObj
| SetObj
| MapObj
| ErrorObj
| RegExpObj
| FunctionObj
Expand All @@ -120,6 +155,8 @@ export type AnyObject =
| NullObj;
export type AnyPreview =
| ObjectPreview
| SetPreview
| MapPreview
| NodePreview
| ArrayPreview
| ErrorPreview
Expand All @@ -130,7 +167,12 @@ export type AnyPreview =
| UndefinedPreview
| NullPreview;

export type PreviewAsObjectType = NodePreview | FunctionPreview | ObjectPreview;
export type PreviewAsObjectType =
| NodePreview
| FunctionPreview
| ObjectPreview
| MapPreview
| SetPreview;
export type Numeric = NumberPreview | BigintPreview | TSpecialNumber;
export type Primitive =
| NullPreview
Expand Down
2 changes: 1 addition & 1 deletion src/adapter/objectPreview/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ function renderArrayPreview(preview: ObjectPreview.ArrayPreview, characterBudget
}

function renderObjectPreview(
preview: ObjectPreview.ObjectPreview | ObjectPreview.NodePreview,
preview: ObjectPreview.PreviewAsObjectType,
characterBudget: number,
format: Dap.ValueFormat | undefined,
): string {
Expand Down
64 changes: 60 additions & 4 deletions src/adapter/variableStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { generate } from 'astring';
import { inject, injectable } from 'inversify';
import Cdp from '../cdp/api';
import { ICdpApi } from '../cdp/connection';
import { flatten, isInstanceOf } from '../common/objUtils';
import { flatten, isInstanceOf, once } from '../common/objUtils';
import { parseSource, statementsToFunction } from '../common/sourceCodeManipulations';
import { IRenameProvider } from '../common/sourceMaps/renameProvider';
import { AnyLaunchConfiguration } from '../configuration';
Expand All @@ -16,6 +16,7 @@ import { IDapApi } from '../dap/connection';
import * as errors from '../dap/errors';
import { ProtocolError } from '../dap/protocolError';
import * as objectPreview from './objectPreview';
import { MapPreview, SetPreview } from './objectPreview/betterTypes';
import { PreviewContextType } from './objectPreview/contexts';
import { StackFrame, StackTrace } from './stackTrace';
import { getSourceSuffix, RemoteException, RemoteObjectId } from './templates';
Expand Down Expand Up @@ -254,8 +255,12 @@ class VariableContext {
return this.createVariable(ArrayVariable, ctx, object);
}

if (object.objectId && !objectPreview.subtypesWithoutPreview.has(object.subtype)) {
return this.createVariable(ObjectVariable, ctx, object, customStringRepr);
if (object.objectId) {
if (object.subtype === 'map' || object.subtype === 'set') {
return this.createVariable(SetOrMapVariable, ctx, object, customStringRepr);
} else if (!objectPreview.subtypesWithoutPreview.has(object.subtype)) {
return this.createVariable(ObjectVariable, ctx, object, customStringRepr);
}
}

return this.createVariable(Variable, ctx, object);
Expand Down Expand Up @@ -493,6 +498,8 @@ class VariableContext {
}
}

const isNumberOrNumeric = (s: string | number) => typeof s === 'number' || /^[0-9]+$/.test(s);

class Variable implements IVariable {
public id = getVariableId();

Expand Down Expand Up @@ -524,7 +531,19 @@ class Variable implements IVariable {
return parent.accessor;
}

if (typeof name === 'number' || /^[0-9]+$/.test(name)) {
// Maps and sets:
const grandparent = parent.context.parent;
if (grandparent instanceof Variable) {
if ((this.remoteObject.subtype as string) === 'internal#entry') {
return `[...${grandparent.accessor}.entries()][${+name}]`;
}

if ((parent.remoteObject.subtype as string) === 'internal#entry') {
return `${parent.accessor}[${this.name === 'key' ? 0 : 1}]`;
}
}

if (isNumberOrNumeric(name)) {
return `${parent.accessor}[${name}]`;
}

Expand Down Expand Up @@ -792,6 +811,43 @@ class ObjectVariable extends Variable implements IMemoryReadable {
}
}

const entriesVariableName = '[[Entries]]';

class SetOrMapVariable extends ObjectVariable {
private readonly size?: number;
public readonly isMap: boolean;
private readonly baseChildren = once(() => super.getChildren({ variablesReference: this.id }));

constructor(context: VariableContext, remoteObject: Cdp.Runtime.RemoteObject) {
super(context, remoteObject, NoCustomStringRepr);
const cast = remoteObject.preview as MapPreview | SetPreview;
this.isMap = cast.subtype === 'map';
this.size = Number(cast.properties.find(p => p.name === 'size')?.value) ?? undefined;
}

public override async toDap(previewContext: PreviewContextType): Promise<Dap.Variable> {
const dap = await super.toDap(previewContext);
if (this.size && this.size > 100) {
dap.indexedVariables = this.size;
}

return dap;
}

public override async getChildren(params: Dap.VariablesParams): Promise<Variable[]> {
const baseChildren = await this.baseChildren();
const entryChildren = await baseChildren
.find(c => c.name === entriesVariableName)
?.getChildren(params);

return [
// filter to only show the actualy entries, not the array prototype/length
...(entryChildren || []).filter(v => v.sortOrder === SortOrder.Default),
...baseChildren.filter(c => c.name !== entriesVariableName),
];
}
}

class ArrayVariable extends ObjectVariable {
private length = 0;

Expand Down
2 changes: 1 addition & 1 deletion src/test/evaluate/evaluate-repl.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ result: 1234567890n


> result: Map(1) {size: 1, hello => ƒ ()}
> 0: {"hello" => function() { return 'world' }}
size: 1
> [[Entries]]: Array(1)
> [[Prototype]]: Map

result: 42
Expand Down
3 changes: 2 additions & 1 deletion src/test/evaluate/evaluate-supports-bigint-map-keys-1277.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
> result: Map(2) {size: 2, 1n => one, 2n => two}
> 0: {1n => "one"}
> 1: {2n => "two"}
size: 2
> [[Entries]]: Array(2)
> [[Prototype]]: Map

0 comments on commit 339c332

Please sign in to comment.