Skip to content

Add support for customDescriptionGenerators (for compiler debugging) #40308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .vscode/launch.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this comment will be obsolete in the next VS Code release.

// 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"
}
]
}
175 changes: 169 additions & 6 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ namespace ts {
return formatEnum(flags, (<any>ts).ObjectFlags, /*isFlags*/ true);
}

export function formatFlowFlags(flags: FlowFlags | undefined): string {
return formatEnum(flags, (<any>ts).FlowFlags, /*isFlags*/ true);
}

let isDebugInfoEnabled = false;

interface ExtendedDebugModule {
Expand All @@ -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<Node> | undefined;

function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) {
if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line no-in-operator
Object.defineProperties(array, {
__tsDebuggerDisplay: {
value(this: NodeArray<Node>, 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<Node>) {
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<Node>;
attachNodeArrayDebugInfoWorker(nodeArrayProto);
}
Object.setPrototypeOf(array, nodeArrayProto);
}
else {
// not running in an es2015 environment, attach the method directly.
attachNodeArrayDebugInfoWorker(array);
}
}
}
Expand Down Expand Up @@ -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((<ObjectType>this).objectFlags) : ""; } },
__debugTypeToString: {
Expand All @@ -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)); } },
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ namespace ts {
if (elements.transformFlags === undefined) {
aggregateChildrenFlags(elements as MutableNodeArray<T>);
}
Debug.attachNodeArrayDebugInfo(elements);
return elements;
}

Expand All @@ -520,6 +521,7 @@ namespace ts {
setTextRangePosEnd(array, -1, -1);
array.hasTrailingComma = !!hasTrailingComma;
aggregateChildrenFlags(array);
Debug.attachNodeArrayDebugInfo(array);
return array;
}

Expand Down
6 changes: 5 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down