/* @internal */ namespace ts { export namespace Debug { /* eslint-disable prefer-const */ export let currentAssertionLevel = AssertionLevel.None; export let isDebugging = false; /* eslint-enable prefer-const */ export function shouldAssert(level: AssertionLevel): boolean { return currentAssertionLevel >= level; } export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void { if (!expression) { if (verboseDebugInfo) { message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); } fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); } } export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { if (a !== b) { const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; fail(`Expected ${a} === ${b}. ${message}`); } } export function assertLessThan(a: number, b: number, msg?: string): void { if (a >= b) { fail(`Expected ${a} < ${b}. ${msg || ""}`); } } export function assertLessThanOrEqual(a: number, b: number): void { if (a > b) { fail(`Expected ${a} <= ${b}`); } } export function assertGreaterThanOrEqual(a: number, b: number): void { if (a < b) { fail(`Expected ${a} >= ${b}`); } } export function fail(message?: string, stackCrawlMark?: AnyFunction): never { debugger; const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); if ((Error).captureStackTrace) { (Error).captureStackTrace(e, stackCrawlMark || fail); } throw e; } export function assertDefined(value: T | null | undefined, message?: string): T { // eslint-disable-next-line no-null/no-null if (value === undefined || value === null) return fail(message); return value; } export function assertEachDefined(value: A, message?: string): A { for (const v of value) { assertDefined(v, message); } return value; } export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); return fail(`${message} ${detail}`, stackCrawlMark || assertNever); } export function getFunctionName(func: AnyFunction) { if (typeof func !== "function") { return ""; } else if (func.hasOwnProperty("name")) { return (func).name; } else { const text = Function.prototype.toString.call(func); const match = /^function\s+([\w\$]+)\s*\(/.exec(text); return match ? match[1] : ""; } } export function formatSymbol(symbol: Symbol): string { return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; } /** * Formats an enum value as a string for debugging and debug assertions. */ export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { const members = getEnumMembers(enumObject); if (value === 0) { return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; } if (isFlags) { let result = ""; let remainingFlags = value; for (const [enumValue, enumName] of members) { if (enumValue > value) { break; } if (enumValue !== 0 && enumValue & value) { result = `${result}${result ? "|" : ""}${enumName}`; remainingFlags &= ~enumValue; } } if (remainingFlags === 0) { return result; } } else { for (const [enumValue, enumName] of members) { if (enumValue === value) { return enumName; } } } return value.toString(); } function getEnumMembers(enumObject: any) { const result: [number, string][] = []; for (const name in enumObject) { const value = enumObject[name]; if (typeof value === "number") { result.push([value, name]); } } return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); } export function formatSyntaxKind(kind: SyntaxKind | undefined): string { return formatEnum(kind, (ts).SyntaxKind, /*isFlags*/ false); } export function formatNodeFlags(flags: NodeFlags | undefined): string { return formatEnum(flags, (ts).NodeFlags, /*isFlags*/ true); } export function formatModifierFlags(flags: ModifierFlags | undefined): string { return formatEnum(flags, (ts).ModifierFlags, /*isFlags*/ true); } export function formatTransformFlags(flags: TransformFlags | undefined): string { return formatEnum(flags, (ts).TransformFlags, /*isFlags*/ true); } export function formatEmitFlags(flags: EmitFlags | undefined): string { return formatEnum(flags, (ts).EmitFlags, /*isFlags*/ true); } export function formatSymbolFlags(flags: SymbolFlags | undefined): string { return formatEnum(flags, (ts).SymbolFlags, /*isFlags*/ true); } export function formatTypeFlags(flags: TypeFlags | undefined): string { return formatEnum(flags, (ts).TypeFlags, /*isFlags*/ true); } export function formatObjectFlags(flags: ObjectFlags | undefined): string { return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); } export function failBadSyntaxKind(node: Node, message?: string): never { return fail( `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, failBadSyntaxKind); } export const assertEachNode = shouldAssert(AssertionLevel.Normal) ? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert( test === undefined || every(nodes, test), message || "Unexpected node.", () => `Node array did not pass test '${getFunctionName(test)}'.`, assertEachNode) : noop; export const assertNode = shouldAssert(AssertionLevel.Normal) ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( test === undefined || test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`, assertNode) : noop; export const assertNotNode = shouldAssert(AssertionLevel.Normal) ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( test === undefined || !test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, assertNode) : noop; export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( test === undefined || node === undefined || test(node), message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`, assertOptionalNode) : noop; export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) ? (node: Node, kind: SyntaxKind, message?: string): void => assert( kind === undefined || node === undefined || node.kind === kind, message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, assertOptionalToken) : noop; export const assertMissingNode = shouldAssert(AssertionLevel.Normal) ? (node: Node, message?: string): void => assert( node === undefined, message || "Unexpected node.", () => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`, assertMissingNode) : noop; let isDebugInfoEnabled = false; interface ExtendedDebugModule { init(_ts: typeof ts): void; formatControlFlowGraph(flowNode: FlowNode): string; } let extendedDebugModule: ExtendedDebugModule | undefined; function extendedDebug() { enableDebugInfo(); if (!extendedDebugModule) { throw new Error("Debugging helpers could not be loaded."); } return extendedDebugModule; } export function printControlFlowGraph(flowNode: FlowNode) { return console.log(formatControlFlowGraph(flowNode)); } export function formatControlFlowGraph(flowNode: FlowNode) { return extendedDebug().formatControlFlowGraph(flowNode); } export function attachFlowNodeDebugInfo(flowNode: FlowNode) { 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); } } }); } } } /** * Injects debug information into frequently used types. */ export function enableDebugInfo() { if (isDebugInfoEnabled) return; // Add additional properties in debug mode to assist with debugging. Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } }); Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, __debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } }, }); const nodeConstructors = [ objectAllocator.getNodeConstructor(), objectAllocator.getIdentifierConstructor(), objectAllocator.getTokenConstructor(), objectAllocator.getSourceFileConstructor() ]; for (const ctor of nodeConstructors) { if (!ctor.prototype.hasOwnProperty("__debugKind")) { Object.defineProperties(ctor.prototype, { __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } }, __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, __debugGetText: { value(this: Node, includeTrivia?: boolean) { if (nodeIsSynthesized(this)) return ""; const parseNode = getParseTreeNode(this); const sourceFile = parseNode && getSourceFileOfNode(parseNode); return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; } } }); } } // attempt to load extended debugging information try { if (sys && sys.require) { const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); const result = sys.require(basePath, "./compiler-debug") as RequireResult; if (!result.error) { result.module.init(ts); extendedDebugModule = result.module; } } } catch { // do nothing } isDebugInfoEnabled = true; } } }