Skip to content

Commit d896353

Browse files
authored
Add support for vscode-js-debug's customDescriptionGenerators (microsoft#40308)
1 parent 38cedc5 commit d896353

File tree

4 files changed

+188
-9
lines changed

4 files changed

+188
-9
lines changed

.vscode/launch.template.json

+12-2
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,24 @@
4747
"console": "integratedTerminal",
4848
"outFiles": [
4949
"${workspaceRoot}/built/local/run.js"
50-
]
50+
],
51+
52+
// NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions
53+
// here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until
54+
// this feature is shipping in insiders or to a release:
55+
// "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue"
5156
},
5257
{
5358
// See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code
5459
"type": "node",
5560
"request": "attach",
5661
"name": "Attach to VS Code TS Server via Port",
57-
"processId": "${command:PickProcess}"
62+
"processId": "${command:PickProcess}",
63+
64+
// NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions
65+
// here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until
66+
// this feature is shipping in insiders or to a release:
67+
// "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue"
5868
}
5969
]
6070
}

src/compiler/debug.ts

+169-6
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,10 @@ namespace ts {
371371
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
372372
}
373373

374+
export function formatFlowFlags(flags: FlowFlags | undefined): string {
375+
return formatEnum(flags, (<any>ts).FlowFlags, /*isFlags*/ true);
376+
}
377+
374378
let isDebugInfoEnabled = false;
375379

376380
interface ExtendedDebugModule {
@@ -396,13 +400,87 @@ namespace ts {
396400
return extendedDebug().formatControlFlowGraph(flowNode);
397401
}
398402

399-
export function attachFlowNodeDebugInfo(flowNode: FlowNode) {
403+
let flowNodeProto: FlowNodeBase | undefined;
404+
405+
function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) {
406+
if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator
407+
Object.defineProperties(flowNode, {
408+
// for use with vscode-js-debug's new customDescriptionGenerator in launch.json
409+
__tsDebuggerDisplay: {
410+
value(this: FlowNodeBase) {
411+
const flowHeader =
412+
this.flags & FlowFlags.Start ? "FlowStart" :
413+
this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" :
414+
this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" :
415+
this.flags & FlowFlags.Assignment ? "FlowAssignment" :
416+
this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" :
417+
this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" :
418+
this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" :
419+
this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" :
420+
this.flags & FlowFlags.Call ? "FlowCall" :
421+
this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" :
422+
this.flags & FlowFlags.Unreachable ? "FlowUnreachable" :
423+
"UnknownFlow";
424+
const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1);
425+
return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`;
426+
}
427+
},
428+
__debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } },
429+
__debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } }
430+
});
431+
}
432+
}
433+
434+
export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) {
400435
if (isDebugInfoEnabled) {
401-
if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator
402-
Object.defineProperties(flowNode, {
403-
__debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } },
404-
__debugToString: { value(this: FlowNode) { return formatControlFlowGraph(this); } }
405-
});
436+
if (typeof Object.setPrototypeOf === "function") {
437+
// if we're in es2015, attach the method to a shared prototype for `FlowNode`
438+
// so the method doesn't show up in the watch window.
439+
if (!flowNodeProto) {
440+
flowNodeProto = Object.create(Object.prototype) as FlowNodeBase;
441+
attachFlowNodeDebugInfoWorker(flowNodeProto);
442+
}
443+
Object.setPrototypeOf(flowNode, flowNodeProto);
444+
}
445+
else {
446+
// not running in an es2015 environment, attach the method directly.
447+
attachFlowNodeDebugInfoWorker(flowNode);
448+
}
449+
}
450+
}
451+
452+
let nodeArrayProto: NodeArray<Node> | undefined;
453+
454+
function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) {
455+
if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line no-in-operator
456+
Object.defineProperties(array, {
457+
__tsDebuggerDisplay: {
458+
value(this: NodeArray<Node>, defaultValue: string) {
459+
// An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of
460+
// these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the
461+
// formatted string.
462+
defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]");
463+
return `NodeArray ${defaultValue}`;
464+
}
465+
}
466+
});
467+
}
468+
}
469+
470+
export function attachNodeArrayDebugInfo(array: NodeArray<Node>) {
471+
if (isDebugInfoEnabled) {
472+
if (typeof Object.setPrototypeOf === "function") {
473+
// if we're in es2015, attach the method to a shared prototype for `NodeArray`
474+
// so the method doesn't show up in the watch window.
475+
if (!nodeArrayProto) {
476+
nodeArrayProto = Object.create(Array.prototype) as NodeArray<Node>;
477+
attachNodeArrayDebugInfoWorker(nodeArrayProto);
478+
}
479+
Object.setPrototypeOf(array, nodeArrayProto);
480+
}
481+
else {
482+
// not running in an es2015 environment, attach the method directly.
483+
attachNodeArrayDebugInfoWorker(array);
406484
}
407485
}
408486
}
@@ -434,10 +512,51 @@ namespace ts {
434512

435513
// Add additional properties in debug mode to assist with debugging.
436514
Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, {
515+
// for use with vscode-js-debug's new customDescriptionGenerator in launch.json
516+
__tsDebuggerDisplay: {
517+
value(this: Symbol) {
518+
const symbolHeader =
519+
this.flags & SymbolFlags.Transient ? "TransientSymbol" :
520+
"Symbol";
521+
const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient;
522+
return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`;
523+
}
524+
},
437525
__debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } }
438526
});
439527

440528
Object.defineProperties(objectAllocator.getTypeConstructor().prototype, {
529+
// for use with vscode-js-debug's new customDescriptionGenerator in launch.json
530+
__tsDebuggerDisplay: {
531+
value(this: Type) {
532+
const typeHeader =
533+
this.flags & TypeFlags.Nullable ? "NullableType" :
534+
this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` :
535+
this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` :
536+
this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" :
537+
this.flags & TypeFlags.Enum ? "EnumType" :
538+
this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` :
539+
this.flags & TypeFlags.Union ? "UnionType" :
540+
this.flags & TypeFlags.Intersection ? "IntersectionType" :
541+
this.flags & TypeFlags.Index ? "IndexType" :
542+
this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" :
543+
this.flags & TypeFlags.Conditional ? "ConditionalType" :
544+
this.flags & TypeFlags.Substitution ? "SubstitutionType" :
545+
this.flags & TypeFlags.TypeParameter ? "TypeParameter" :
546+
this.flags & TypeFlags.Object ?
547+
(this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" :
548+
(this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" :
549+
(this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" :
550+
(this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" :
551+
(this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" :
552+
(this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" :
553+
(this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" :
554+
"ObjectType" :
555+
"Type";
556+
const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0;
557+
return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`;
558+
}
559+
},
441560
__debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } },
442561
__debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((<ObjectType>this).objectFlags) : ""; } },
443562
__debugTypeToString: {
@@ -464,6 +583,50 @@ namespace ts {
464583
for (const ctor of nodeConstructors) {
465584
if (!ctor.prototype.hasOwnProperty("__debugKind")) {
466585
Object.defineProperties(ctor.prototype, {
586+
// for use with vscode-js-debug's new customDescriptionGenerator in launch.json
587+
__tsDebuggerDisplay: {
588+
value(this: Node) {
589+
const nodeHeader =
590+
isGeneratedIdentifier(this) ? "GeneratedIdentifier" :
591+
isIdentifier(this) ? `Identifier '${idText(this)}'` :
592+
isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` :
593+
isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` :
594+
isNumericLiteral(this) ? `NumericLiteral ${this.text}` :
595+
isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` :
596+
isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" :
597+
isParameter(this) ? "ParameterDeclaration" :
598+
isConstructorDeclaration(this) ? "ConstructorDeclaration" :
599+
isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" :
600+
isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" :
601+
isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" :
602+
isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" :
603+
isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" :
604+
isTypePredicateNode(this) ? "TypePredicateNode" :
605+
isTypeReferenceNode(this) ? "TypeReferenceNode" :
606+
isFunctionTypeNode(this) ? "FunctionTypeNode" :
607+
isConstructorTypeNode(this) ? "ConstructorTypeNode" :
608+
isTypeQueryNode(this) ? "TypeQueryNode" :
609+
isTypeLiteralNode(this) ? "TypeLiteralNode" :
610+
isArrayTypeNode(this) ? "ArrayTypeNode" :
611+
isTupleTypeNode(this) ? "TupleTypeNode" :
612+
isOptionalTypeNode(this) ? "OptionalTypeNode" :
613+
isRestTypeNode(this) ? "RestTypeNode" :
614+
isUnionTypeNode(this) ? "UnionTypeNode" :
615+
isIntersectionTypeNode(this) ? "IntersectionTypeNode" :
616+
isConditionalTypeNode(this) ? "ConditionalTypeNode" :
617+
isInferTypeNode(this) ? "InferTypeNode" :
618+
isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" :
619+
isThisTypeNode(this) ? "ThisTypeNode" :
620+
isTypeOperatorNode(this) ? "TypeOperatorNode" :
621+
isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" :
622+
isMappedTypeNode(this) ? "MappedTypeNode" :
623+
isLiteralTypeNode(this) ? "LiteralTypeNode" :
624+
isNamedTupleMember(this) ? "NamedTupleMember" :
625+
isImportTypeNode(this) ? "ImportTypeNode" :
626+
formatSyntaxKind(this.kind);
627+
return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`;
628+
}
629+
},
467630
__debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } },
468631
__debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } },
469632
__debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } },

src/compiler/factory/nodeFactory.ts

+2
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ namespace ts {
509509
if (elements.transformFlags === undefined) {
510510
aggregateChildrenFlags(elements as MutableNodeArray<T>);
511511
}
512+
Debug.attachNodeArrayDebugInfo(elements);
512513
return elements;
513514
}
514515

@@ -520,6 +521,7 @@ namespace ts {
520521
setTextRangePosEnd(array, -1, -1);
521522
array.hasTrailingComma = !!hasTrailingComma;
522523
aggregateChildrenFlags(array);
524+
Debug.attachNodeArrayDebugInfo(array);
523525
return array;
524526
}
525527

src/compiler/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -5021,7 +5021,11 @@ namespace ts {
50215021
/* @internal */
50225022
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
50235023
/* @internal */
5024-
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType
5024+
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType,
5025+
5026+
// Object flags that uniquely identify the kind of ObjectType
5027+
/* @internal */
5028+
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,
50255029
}
50265030

50275031
/* @internal */

0 commit comments

Comments
 (0)