From d89635381a65229c5ab5b0c73de15347b86235a2 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 2 Sep 2020 20:05:50 -0700 Subject: [PATCH] Add support for vscode-js-debug's customDescriptionGenerators (#40308) --- .vscode/launch.template.json | 14 ++- src/compiler/debug.ts | 175 +++++++++++++++++++++++++++- src/compiler/factory/nodeFactory.ts | 2 + src/compiler/types.ts | 6 +- 4 files changed, 188 insertions(+), 9 deletions(-) diff --git a/.vscode/launch.template.json b/.vscode/launch.template.json index 07e919ba96..ca3ac8d2b8 100644 --- a/.vscode/launch.template.json +++ b/.vscode/launch.template.json @@ -47,14 +47,24 @@ "console": "integratedTerminal", "outFiles": [ "${workspaceRoot}/built/local/run.js" - ] + ], + + // NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions + // here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until + // this feature is shipping in insiders or to a release: + // "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" }, { // See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code "type": "node", "request": "attach", "name": "Attach to VS Code TS Server via Port", - "processId": "${command:PickProcess}" + "processId": "${command:PickProcess}", + + // NOTE: To use this, you must switch the "type" above to "pwa-node". You may also need to follow the instructions + // here: https://github.com/microsoft/vscode-js-debug#nightly-extension to use the js-debug nightly until + // this feature is shipping in insiders or to a release: + // "customDescriptionGenerator": "'__tsDebuggerDisplay' in this ? this.__tsDebuggerDisplay(defaultValue) : defaultValue" } ] } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index a22c51ad18..8e3ce1cfa4 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -371,6 +371,10 @@ namespace ts { return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); } + export function formatFlowFlags(flags: FlowFlags | undefined): string { + return formatEnum(flags, (ts).FlowFlags, /*isFlags*/ true); + } + let isDebugInfoEnabled = false; interface ExtendedDebugModule { @@ -396,13 +400,87 @@ namespace ts { return extendedDebug().formatControlFlowGraph(flowNode); } - export function attachFlowNodeDebugInfo(flowNode: FlowNode) { + let flowNodeProto: FlowNodeBase | undefined; + + function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { + if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator + Object.defineProperties(flowNode, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: FlowNodeBase) { + const flowHeader = + this.flags & FlowFlags.Start ? "FlowStart" : + this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : + this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : + this.flags & FlowFlags.Assignment ? "FlowAssignment" : + this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : + this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : + this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : + this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : + this.flags & FlowFlags.Call ? "FlowCall" : + this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : + this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : + "UnknownFlow"; + const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); + return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; + } + }, + __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, + __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } + }); + } + } + + export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { if (isDebugInfoEnabled) { - if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line no-in-operator - Object.defineProperties(flowNode, { - __debugFlowFlags: { get(this: FlowNode) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, - __debugToString: { value(this: FlowNode) { return formatControlFlowGraph(this); } } - }); + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `FlowNode` + // so the method doesn't show up in the watch window. + if (!flowNodeProto) { + flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; + attachFlowNodeDebugInfoWorker(flowNodeProto); + } + Object.setPrototypeOf(flowNode, flowNodeProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachFlowNodeDebugInfoWorker(flowNode); + } + } + } + + let nodeArrayProto: NodeArray | undefined; + + function attachNodeArrayDebugInfoWorker(array: NodeArray) { + if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line no-in-operator + Object.defineProperties(array, { + __tsDebuggerDisplay: { + value(this: NodeArray, defaultValue: string) { + // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of + // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the + // formatted string. + defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); + return `NodeArray ${defaultValue}`; + } + } + }); + } + } + + export function attachNodeArrayDebugInfo(array: NodeArray) { + if (isDebugInfoEnabled) { + if (typeof Object.setPrototypeOf === "function") { + // if we're in es2015, attach the method to a shared prototype for `NodeArray` + // so the method doesn't show up in the watch window. + if (!nodeArrayProto) { + nodeArrayProto = Object.create(Array.prototype) as NodeArray; + attachNodeArrayDebugInfoWorker(nodeArrayProto); + } + Object.setPrototypeOf(array, nodeArrayProto); + } + else { + // not running in an es2015 environment, attach the method directly. + attachNodeArrayDebugInfoWorker(array); } } } @@ -434,10 +512,51 @@ namespace ts { // Add additional properties in debug mode to assist with debugging. Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Symbol) { + const symbolHeader = + this.flags & SymbolFlags.Transient ? "TransientSymbol" : + "Symbol"; + const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; + return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; + } + }, __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } }); Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Type) { + const typeHeader = + this.flags & TypeFlags.Nullable ? "NullableType" : + this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : + this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : + this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : + this.flags & TypeFlags.Enum ? "EnumType" : + this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : + this.flags & TypeFlags.Union ? "UnionType" : + this.flags & TypeFlags.Intersection ? "IntersectionType" : + this.flags & TypeFlags.Index ? "IndexType" : + this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : + this.flags & TypeFlags.Conditional ? "ConditionalType" : + this.flags & TypeFlags.Substitution ? "SubstitutionType" : + this.flags & TypeFlags.TypeParameter ? "TypeParameter" : + this.flags & TypeFlags.Object ? + (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : + (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : + (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : + (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : + (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : + (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : + (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : + "ObjectType" : + "Type"; + const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; + return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; + } + }, __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, __debugTypeToString: { @@ -464,6 +583,50 @@ namespace ts { for (const ctor of nodeConstructors) { if (!ctor.prototype.hasOwnProperty("__debugKind")) { Object.defineProperties(ctor.prototype, { + // for use with vscode-js-debug's new customDescriptionGenerator in launch.json + __tsDebuggerDisplay: { + value(this: Node) { + const nodeHeader = + isGeneratedIdentifier(this) ? "GeneratedIdentifier" : + isIdentifier(this) ? `Identifier '${idText(this)}'` : + isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : + isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : + isNumericLiteral(this) ? `NumericLiteral ${this.text}` : + isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : + isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : + isParameter(this) ? "ParameterDeclaration" : + isConstructorDeclaration(this) ? "ConstructorDeclaration" : + isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : + isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : + isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : + isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : + isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : + isTypePredicateNode(this) ? "TypePredicateNode" : + isTypeReferenceNode(this) ? "TypeReferenceNode" : + isFunctionTypeNode(this) ? "FunctionTypeNode" : + isConstructorTypeNode(this) ? "ConstructorTypeNode" : + isTypeQueryNode(this) ? "TypeQueryNode" : + isTypeLiteralNode(this) ? "TypeLiteralNode" : + isArrayTypeNode(this) ? "ArrayTypeNode" : + isTupleTypeNode(this) ? "TupleTypeNode" : + isOptionalTypeNode(this) ? "OptionalTypeNode" : + isRestTypeNode(this) ? "RestTypeNode" : + isUnionTypeNode(this) ? "UnionTypeNode" : + isIntersectionTypeNode(this) ? "IntersectionTypeNode" : + isConditionalTypeNode(this) ? "ConditionalTypeNode" : + isInferTypeNode(this) ? "InferTypeNode" : + isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : + isThisTypeNode(this) ? "ThisTypeNode" : + isTypeOperatorNode(this) ? "TypeOperatorNode" : + isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : + isMappedTypeNode(this) ? "MappedTypeNode" : + isLiteralTypeNode(this) ? "LiteralTypeNode" : + isNamedTupleMember(this) ? "NamedTupleMember" : + isImportTypeNode(this) ? "ImportTypeNode" : + formatSyntaxKind(this.kind); + return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; + } + }, __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } }, diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index a9679f453f..2121f28d5e 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -509,6 +509,7 @@ namespace ts { if (elements.transformFlags === undefined) { aggregateChildrenFlags(elements as MutableNodeArray); } + Debug.attachNodeArrayDebugInfo(elements); return elements; } @@ -520,6 +521,7 @@ namespace ts { setTextRangePosEnd(array, -1, -1); array.hasTrailingComma = !!hasTrailingComma; aggregateChildrenFlags(array); + Debug.attachNodeArrayDebugInfo(array); return array; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 86596b9a30..9f2335cbb3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5021,7 +5021,11 @@ namespace ts { /* @internal */ RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral, /* @internal */ - PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType + PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType, + + // Object flags that uniquely identify the kind of ObjectType + /* @internal */ + ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray, } /* @internal */