1/* @internal */ 2namespace ts { 3 export enum LogLevel { 4 Off, 5 Error, 6 Warning, 7 Info, 8 Verbose 9 } 10 11 export interface LoggingHost { 12 log(level: LogLevel, s: string): void; 13 } 14 15 export interface DeprecationOptions { 16 message?: string; 17 error?: boolean; 18 since?: Version | string; 19 warnAfter?: Version | string; 20 errorAfter?: Version | string; 21 typeScriptVersion?: Version | string; 22 name?: string; 23 } 24 25 export namespace Debug { 26 let typeScriptVersion: Version | undefined; 27 28 /* eslint-disable prefer-const */ 29 let currentAssertionLevel = AssertionLevel.None; 30 export let currentLogLevel = LogLevel.Warning; 31 export let isDebugging = false; 32 export let loggingHost: LoggingHost | undefined; 33 export let enableDeprecationWarnings = true; 34 /* eslint-enable prefer-const */ 35 36 type AssertionKeys = MatchingKeys<typeof Debug, AnyFunction>; 37 export function getTypeScriptVersion() { 38 return typeScriptVersion ?? (typeScriptVersion = new Version(version)); 39 } 40 41 export function shouldLog(level: LogLevel): boolean { 42 return currentLogLevel <= level; 43 } 44 45 function logMessage(level: LogLevel, s: string): void { 46 if (loggingHost && shouldLog(level)) { 47 loggingHost.log(level, s); 48 } 49 } 50 51 export function log(s: string): void { 52 logMessage(LogLevel.Info, s); 53 } 54 55 export namespace log { 56 export function error(s: string): void { 57 logMessage(LogLevel.Error, s); 58 } 59 60 export function warn(s: string): void { 61 logMessage(LogLevel.Warning, s); 62 } 63 64 export function log(s: string): void { 65 logMessage(LogLevel.Info, s); 66 } 67 68 export function trace(s: string): void { 69 logMessage(LogLevel.Verbose, s); 70 } 71 } 72 73 const assertionCache: Partial<Record<AssertionKeys, { level: AssertionLevel, assertion: AnyFunction }>> = {}; 74 75 export function getAssertionLevel() { 76 return currentAssertionLevel; 77 } 78 79 export function setAssertionLevel(level: AssertionLevel) { 80 const prevAssertionLevel = currentAssertionLevel; 81 currentAssertionLevel = level; 82 83 if (level > prevAssertionLevel) { 84 // restore assertion functions for the current assertion level (see `shouldAssertFunction`). 85 for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { 86 const cachedFunc = assertionCache[key]; 87 if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { 88 (Debug as any)[key] = cachedFunc; 89 assertionCache[key] = undefined; 90 } 91 } 92 } 93 } 94 95 export function shouldAssert(level: AssertionLevel): boolean { 96 return currentAssertionLevel >= level; 97 } 98 99 /** 100 * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. 101 * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. 102 * @param level The minimum assertion level required. 103 * @param name The name of the current assertion function. 104 */ 105 function shouldAssertFunction<K extends AssertionKeys>(level: AssertionLevel, name: K): boolean { 106 if (!shouldAssert(level)) { 107 assertionCache[name] = { level, assertion: Debug[name] }; 108 (Debug as any)[name] = noop; 109 return false; 110 } 111 return true; 112 } 113 114 export function fail(message?: string, stackCrawlMark?: AnyFunction): never { 115 debugger; 116 const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); 117 if ((Error as any).captureStackTrace) { 118 (Error as any).captureStackTrace(e, stackCrawlMark || fail); 119 } 120 throw e; 121 } 122 123 export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { 124 return fail( 125 `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, 126 stackCrawlMark || failBadSyntaxKind); 127 } 128 129 export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { 130 if (!expression) { 131 message = message ? `False expression: ${message}` : "False expression."; 132 if (verboseDebugInfo) { 133 message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); 134 } 135 fail(message, stackCrawlMark || assert); 136 } 137 } 138 139 export function assertEqual<T>(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { 140 if (a !== b) { 141 const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; 142 fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); 143 } 144 } 145 146 export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { 147 if (a >= b) { 148 fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); 149 } 150 } 151 152 export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { 153 if (a > b) { 154 fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); 155 } 156 } 157 158 export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { 159 if (a < b) { 160 fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); 161 } 162 } 163 164 export function assertIsDefined<T>(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable<T> { 165 // eslint-disable-next-line no-null/no-null 166 if (value === undefined || value === null) { 167 fail(message, stackCrawlMark || assertIsDefined); 168 } 169 } 170 171 export function checkDefined<T>(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { 172 assertIsDefined(value, message, stackCrawlMark || checkDefined); 173 return value; 174 } 175 176 export function assertEachIsDefined<T extends Node>(value: NodeArray<T>, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray<T>; 177 export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable<T>[]; 178 export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { 179 for (const v of value) { 180 assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); 181 } 182 } 183 184 export function checkEachDefined<T, A extends readonly T[]>(value: A, message?: string, stackCrawlMark?: AnyFunction): A { 185 assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); 186 return value; 187 } 188 189 export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { 190 const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); 191 return fail(`${message} ${detail}`, stackCrawlMark || assertNever); 192 } 193 194 export function assertEachNode<T extends Node, U extends T>(nodes: NodeArray<T>, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray<U>; 195 export function assertEachNode<T extends Node, U extends T>(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[]; 196 export function assertEachNode<T extends Node, U extends T>(nodes: NodeArray<T> | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray<U> | undefined; 197 export function assertEachNode<T extends Node, U extends T>(nodes: readonly T[] | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[] | undefined; 198 export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; 199 export function assertEachNode(nodes: readonly Node[] | undefined, test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { 200 if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { 201 assert( 202 test === undefined || every(nodes, test), 203 message || "Unexpected node.", 204 () => `Node array did not pass test '${getFunctionName(test)}'.`, 205 stackCrawlMark || assertEachNode); 206 } 207 } 208 209 export function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; 210 export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 211 export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 212 if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { 213 assert( 214 node !== undefined && (test === undefined || test(node)), 215 message || "Unexpected node.", 216 () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, 217 stackCrawlMark || assertNode); 218 } 219 } 220 221 export function assertNotNode<T extends Node, U extends T>(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude<T, U>; 222 export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 223 export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 224 if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { 225 assert( 226 node === undefined || test === undefined || !test(node), 227 message || "Unexpected node.", 228 () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, 229 stackCrawlMark || assertNotNode); 230 } 231 } 232 233 export function assertOptionalNode<T extends Node, U extends T>(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U; 234 export function assertOptionalNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined; 235 export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 236 export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 237 if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { 238 assert( 239 test === undefined || node === undefined || test(node), 240 message || "Unexpected node.", 241 () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, 242 stackCrawlMark || assertOptionalNode); 243 } 244 } 245 246 export function assertOptionalToken<T extends Node, K extends SyntaxKind>(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract<T, { readonly kind: K }>; 247 export function assertOptionalToken<T extends Node, K extends SyntaxKind>(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract<T, { readonly kind: K }> | undefined; 248 export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 249 export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { 250 if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { 251 assert( 252 kind === undefined || node === undefined || node.kind === kind, 253 message || "Unexpected node.", 254 () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, 255 stackCrawlMark || assertOptionalToken); 256 } 257 } 258 259 export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; 260 export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { 261 if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { 262 assert( 263 node === undefined, 264 message || "Unexpected node.", 265 () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, 266 stackCrawlMark || assertMissingNode); 267 } 268 } 269 270 /** 271 * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). 272 * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and 273 * as a result can reduce the number of unnecessary casts. 274 */ 275 export function type<T>(value: unknown): asserts value is T; 276 export function type(_value: unknown) { } 277 278 export function getFunctionName(func: AnyFunction) { 279 if (typeof func !== "function") { 280 return ""; 281 } 282 else if (hasProperty(func, "name")) { 283 return (func as any).name; 284 } 285 else { 286 const text = Function.prototype.toString.call(func); 287 const match = /^function\s+([\w\$]+)\s*\(/.exec(text); 288 return match ? match[1] : ""; 289 } 290 } 291 292 export function formatSymbol(symbol: Symbol): string { 293 return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; 294 } 295 296 /** 297 * Formats an enum value as a string for debugging and debug assertions. 298 */ 299 export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { 300 const members = getEnumMembers(enumObject); 301 if (value === 0) { 302 return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; 303 } 304 if (isFlags) { 305 const result: string[] = []; 306 let remainingFlags = value; 307 for (const [enumValue, enumName] of members) { 308 if (enumValue > value) { 309 break; 310 } 311 if (enumValue !== 0 && enumValue & value) { 312 result.push(enumName); 313 remainingFlags &= ~enumValue; 314 } 315 } 316 if (remainingFlags === 0) { 317 return result.join("|"); 318 } 319 } 320 else { 321 for (const [enumValue, enumName] of members) { 322 if (enumValue === value) { 323 return enumName; 324 } 325 } 326 } 327 return value.toString(); 328 } 329 330 const enumMemberCache = new Map<any, SortedReadonlyArray<[number, string]>>(); 331 332 function getEnumMembers(enumObject: any) { 333 // Assuming enum objects do not change at runtime, we can cache the enum members list 334 // to reuse later. This saves us from reconstructing this each and every time we call 335 // a formatting function (which can be expensive for large enums like SyntaxKind). 336 const existing = enumMemberCache.get(enumObject); 337 if (existing) { 338 return existing; 339 } 340 341 const result: [number, string][] = []; 342 for (const name in enumObject) { 343 const value = enumObject[name]; 344 if (typeof value === "number") { 345 result.push([value, name]); 346 } 347 } 348 349 const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); 350 enumMemberCache.set(enumObject, sorted); 351 return sorted; 352 } 353 354 export function formatSyntaxKind(kind: SyntaxKind | undefined): string { 355 return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); 356 } 357 358 export function formatSnippetKind(kind: SnippetKind | undefined): string { 359 return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); 360 } 361 362 export function formatNodeFlags(flags: NodeFlags | undefined): string { 363 return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); 364 } 365 366 export function formatModifierFlags(flags: ModifierFlags | undefined): string { 367 return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); 368 } 369 370 export function formatTransformFlags(flags: TransformFlags | undefined): string { 371 return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); 372 } 373 374 export function formatEmitFlags(flags: EmitFlags | undefined): string { 375 return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); 376 } 377 378 export function formatSymbolFlags(flags: SymbolFlags | undefined): string { 379 return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); 380 } 381 382 export function formatTypeFlags(flags: TypeFlags | undefined): string { 383 return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); 384 } 385 386 export function formatSignatureFlags(flags: SignatureFlags | undefined): string { 387 return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); 388 } 389 390 export function formatObjectFlags(flags: ObjectFlags | undefined): string { 391 return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); 392 } 393 394 export function formatFlowFlags(flags: FlowFlags | undefined): string { 395 return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); 396 } 397 398 export function formatRelationComparisonResult(result: RelationComparisonResult | undefined): string { 399 return formatEnum(result, (ts as any).RelationComparisonResult, /*isFlags*/ true); 400 } 401 402 export function formatCheckMode(mode: CheckMode | undefined): string { 403 return formatEnum(mode, (ts as any).CheckMode, /*isFlags*/ true); 404 } 405 406 export function formatSignatureCheckMode(mode: SignatureCheckMode | undefined): string { 407 return formatEnum(mode, (ts as any).SignatureCheckMode, /*isFlags*/ true); 408 } 409 410 export function formatTypeFacts(facts: TypeFacts | undefined): string { 411 return formatEnum(facts, (ts as any).TypeFacts, /*isFlags*/ true); 412 } 413 414 let isDebugInfoEnabled = false; 415 416 interface ExtendedDebugModule { 417 init(_ts: typeof ts): void; 418 formatControlFlowGraph(flowNode: FlowNode): string; 419 } 420 421 let extendedDebugModule: ExtendedDebugModule | undefined; 422 423 function extendedDebug() { 424 enableDebugInfo(); 425 if (!extendedDebugModule) { 426 throw new Error("Debugging helpers could not be loaded."); 427 } 428 return extendedDebugModule; 429 } 430 431 export function printControlFlowGraph(flowNode: FlowNode) { 432 return console.log(formatControlFlowGraph(flowNode)); 433 } 434 435 export function formatControlFlowGraph(flowNode: FlowNode) { 436 return extendedDebug().formatControlFlowGraph(flowNode); 437 } 438 439 let flowNodeProto: FlowNodeBase | undefined; 440 441 function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { 442 if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator 443 Object.defineProperties(flowNode, { 444 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 445 __tsDebuggerDisplay: { 446 value(this: FlowNodeBase) { 447 const flowHeader = 448 this.flags & FlowFlags.Start ? "FlowStart" : 449 this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : 450 this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : 451 this.flags & FlowFlags.Assignment ? "FlowAssignment" : 452 this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : 453 this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : 454 this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : 455 this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : 456 this.flags & FlowFlags.Call ? "FlowCall" : 457 this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : 458 this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : 459 "UnknownFlow"; 460 const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); 461 return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; 462 } 463 }, 464 __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, 465 __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } 466 }); 467 } 468 } 469 470 export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { 471 if (isDebugInfoEnabled) { 472 if (typeof Object.setPrototypeOf === "function") { 473 // if we're in es2015, attach the method to a shared prototype for `FlowNode` 474 // so the method doesn't show up in the watch window. 475 if (!flowNodeProto) { 476 flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; 477 attachFlowNodeDebugInfoWorker(flowNodeProto); 478 } 479 Object.setPrototypeOf(flowNode, flowNodeProto); 480 } 481 else { 482 // not running in an es2015 environment, attach the method directly. 483 attachFlowNodeDebugInfoWorker(flowNode); 484 } 485 } 486 } 487 488 let nodeArrayProto: NodeArray<Node> | undefined; 489 490 function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) { 491 if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator 492 Object.defineProperties(array, { 493 __tsDebuggerDisplay: { 494 value(this: NodeArray<Node>, defaultValue: string) { 495 // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of 496 // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the 497 // formatted string. 498 // This regex can trigger slow backtracking because of overlapping potential captures. 499 // We don't care, this is debug code that's only enabled with a debugger attached - 500 // we're just taking note of it for anyone checking regex performance in the future. 501 defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); 502 return `NodeArray ${defaultValue}`; 503 } 504 } 505 }); 506 } 507 } 508 509 export function attachNodeArrayDebugInfo(array: NodeArray<Node>) { 510 if (isDebugInfoEnabled) { 511 if (typeof Object.setPrototypeOf === "function") { 512 // if we're in es2015, attach the method to a shared prototype for `NodeArray` 513 // so the method doesn't show up in the watch window. 514 if (!nodeArrayProto) { 515 nodeArrayProto = Object.create(Array.prototype) as NodeArray<Node>; 516 attachNodeArrayDebugInfoWorker(nodeArrayProto); 517 } 518 Object.setPrototypeOf(array, nodeArrayProto); 519 } 520 else { 521 // not running in an es2015 environment, attach the method directly. 522 attachNodeArrayDebugInfoWorker(array); 523 } 524 } 525 } 526 527 /** 528 * Injects debug information into frequently used types. 529 */ 530 export function enableDebugInfo() { 531 if (isDebugInfoEnabled) return; 532 533 // avoid recomputing 534 let weakTypeTextMap: WeakMap<Type, string> | undefined; 535 let weakNodeTextMap: WeakMap<Node, string> | undefined; 536 537 function getWeakTypeTextMap() { 538 if (weakTypeTextMap === undefined) { 539 if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); 540 } 541 return weakTypeTextMap; 542 } 543 544 function getWeakNodeTextMap() { 545 if (weakNodeTextMap === undefined) { 546 if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); 547 } 548 return weakNodeTextMap; 549 } 550 551 552 // Add additional properties in debug mode to assist with debugging. 553 Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { 554 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 555 __tsDebuggerDisplay: { 556 value(this: Symbol) { 557 const symbolHeader = 558 this.flags & SymbolFlags.Transient ? "TransientSymbol" : 559 "Symbol"; 560 const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; 561 return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; 562 } 563 }, 564 __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } 565 }); 566 567 Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { 568 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 569 __tsDebuggerDisplay: { 570 value(this: Type) { 571 const typeHeader = 572 this.flags & TypeFlags.Nullable ? "NullableType" : 573 this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : 574 this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : 575 this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : 576 this.flags & TypeFlags.Enum ? "EnumType" : 577 this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : 578 this.flags & TypeFlags.Union ? "UnionType" : 579 this.flags & TypeFlags.Intersection ? "IntersectionType" : 580 this.flags & TypeFlags.Index ? "IndexType" : 581 this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : 582 this.flags & TypeFlags.Conditional ? "ConditionalType" : 583 this.flags & TypeFlags.Substitution ? "SubstitutionType" : 584 this.flags & TypeFlags.TypeParameter ? "TypeParameter" : 585 this.flags & TypeFlags.Object ? 586 (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : 587 (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : 588 (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : 589 (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : 590 (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : 591 (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : 592 (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : 593 "ObjectType" : 594 "Type"; 595 const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; 596 return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; 597 } 598 }, 599 __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, 600 __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, 601 __debugTypeToString: { 602 value(this: Type) { 603 // avoid recomputing 604 const map = getWeakTypeTextMap(); 605 let text = map?.get(this); 606 if (text === undefined) { 607 text = this.checker.typeToString(this); 608 map?.set(this, text); 609 } 610 return text; 611 } 612 }, 613 }); 614 615 Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { 616 __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, 617 __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } 618 }); 619 620 const nodeConstructors = [ 621 objectAllocator.getNodeConstructor(), 622 objectAllocator.getIdentifierConstructor(), 623 objectAllocator.getTokenConstructor(), 624 objectAllocator.getSourceFileConstructor() 625 ]; 626 627 for (const ctor of nodeConstructors) { 628 if (!hasProperty(ctor.prototype, "__debugKind")) { 629 Object.defineProperties(ctor.prototype, { 630 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 631 __tsDebuggerDisplay: { 632 value(this: Node) { 633 const nodeHeader = 634 isGeneratedIdentifier(this) ? "GeneratedIdentifier" : 635 isIdentifier(this) ? `Identifier '${idText(this)}'` : 636 isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : 637 isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : 638 isNumericLiteral(this) ? `NumericLiteral ${this.text}` : 639 isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : 640 isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : 641 isParameter(this) ? "ParameterDeclaration" : 642 isConstructorDeclaration(this) ? "ConstructorDeclaration" : 643 isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : 644 isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : 645 isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : 646 isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : 647 isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : 648 isTypePredicateNode(this) ? "TypePredicateNode" : 649 isTypeReferenceNode(this) ? "TypeReferenceNode" : 650 isFunctionTypeNode(this) ? "FunctionTypeNode" : 651 isConstructorTypeNode(this) ? "ConstructorTypeNode" : 652 isTypeQueryNode(this) ? "TypeQueryNode" : 653 isTypeLiteralNode(this) ? "TypeLiteralNode" : 654 isArrayTypeNode(this) ? "ArrayTypeNode" : 655 isTupleTypeNode(this) ? "TupleTypeNode" : 656 isOptionalTypeNode(this) ? "OptionalTypeNode" : 657 isRestTypeNode(this) ? "RestTypeNode" : 658 isUnionTypeNode(this) ? "UnionTypeNode" : 659 isIntersectionTypeNode(this) ? "IntersectionTypeNode" : 660 isConditionalTypeNode(this) ? "ConditionalTypeNode" : 661 isInferTypeNode(this) ? "InferTypeNode" : 662 isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : 663 isThisTypeNode(this) ? "ThisTypeNode" : 664 isTypeOperatorNode(this) ? "TypeOperatorNode" : 665 isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : 666 isMappedTypeNode(this) ? "MappedTypeNode" : 667 isLiteralTypeNode(this) ? "LiteralTypeNode" : 668 isNamedTupleMember(this) ? "NamedTupleMember" : 669 isImportTypeNode(this) ? "ImportTypeNode" : 670 formatSyntaxKind(this.kind); 671 return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; 672 } 673 }, 674 __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, 675 __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, 676 __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } }, 677 __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, 678 __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, 679 __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, 680 __debugGetText: { 681 value(this: Node, includeTrivia?: boolean) { 682 if (nodeIsSynthesized(this)) return ""; 683 // avoid recomputing 684 const map = getWeakNodeTextMap(); 685 let text = map?.get(this); 686 if (text === undefined) { 687 const parseNode = getParseTreeNode(this); 688 const sourceFile = parseNode && getSourceFileOfNode(parseNode); 689 text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; 690 map?.set(this, text); 691 } 692 return text; 693 } 694 } 695 }); 696 } 697 } 698 699 // attempt to load extended debugging information 700 try { 701 if (sys && sys.require) { 702 const basePath = getDirectoryPath(resolvePath(sys.getExecutingFilePath())); 703 const result = sys.require(basePath, "./compiler-debug") as RequireResult<ExtendedDebugModule>; 704 if (!result.error) { 705 result.module.init(ts); 706 extendedDebugModule = result.module; 707 } 708 } 709 } 710 catch { 711 // do nothing 712 } 713 714 isDebugInfoEnabled = true; 715 } 716 717 function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 718 let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; 719 deprecationMessage += `'${name}' `; 720 deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; 721 deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; 722 deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; 723 return deprecationMessage; 724 } 725 726 function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 727 const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); 728 return () => { 729 throw new TypeError(deprecationMessage); 730 }; 731 } 732 733 function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 734 let hasWrittenDeprecation = false; 735 return () => { 736 if (enableDeprecationWarnings && !hasWrittenDeprecation) { 737 log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); 738 hasWrittenDeprecation = true; 739 } 740 }; 741 } 742 743 export function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; 744 export function createDeprecation(name: string, options?: DeprecationOptions): () => void; 745 export function createDeprecation(name: string, options: DeprecationOptions = {}) { 746 const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); 747 const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; 748 const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; 749 const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; 750 const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; 751 const warn = !warnAfter || version.compareTo(warnAfter) >= 0; 752 return error ? createErrorDeprecation(name, errorAfter, since, options.message) : 753 warn ? createWarningDeprecation(name, errorAfter, since, options.message) : 754 noop; 755 } 756 757 function wrapFunction<F extends (...args: any[]) => any>(deprecation: () => void, func: F): F { 758 return function (this: unknown) { 759 deprecation(); 760 return func.apply(this, arguments); 761 } as F; 762 } 763 764 export function deprecate<F extends (...args: any[]) => any>(func: F, options?: DeprecationOptions): F { 765 const deprecation = createDeprecation(options?.name ?? getFunctionName(func), options); 766 return wrapFunction(deprecation, func); 767 } 768 769 export function formatVariance(varianceFlags: VarianceFlags) { 770 const variance = varianceFlags & VarianceFlags.VarianceMask; 771 let result = 772 variance === VarianceFlags.Invariant ? "in out" : 773 variance === VarianceFlags.Bivariant ? "[bivariant]" : 774 variance === VarianceFlags.Contravariant ? "in" : 775 variance === VarianceFlags.Covariant ? "out" : 776 variance === VarianceFlags.Independent ? "[independent]" : ""; 777 if (varianceFlags & VarianceFlags.Unmeasurable) { 778 result += " (unmeasurable)"; 779 } 780 else if (varianceFlags & VarianceFlags.Unreliable) { 781 result += " (unreliable)"; 782 } 783 return result; 784 } 785 786 export type DebugType = Type & { __debugTypeToString(): string }; // eslint-disable-line @typescript-eslint/naming-convention 787 export class DebugTypeMapper { 788 declare kind: TypeMapKind; 789 __debugToString(): string { // eslint-disable-line @typescript-eslint/naming-convention 790 type<TypeMapper>(this); 791 switch (this.kind) { 792 case TypeMapKind.Function: return this.debugInfo?.() || "(function mapper)"; 793 case TypeMapKind.Simple: return `${(this.source as DebugType).__debugTypeToString()} -> ${(this.target as DebugType).__debugTypeToString()}`; 794 case TypeMapKind.Array: return zipWith<DebugType, DebugType | string, unknown>( 795 this.sources as readonly DebugType[], 796 this.targets as readonly DebugType[] || map(this.sources, () => "any"), 797 (s, t) => `${s.__debugTypeToString()} -> ${typeof t === "string" ? t : t.__debugTypeToString()}`).join(", "); 798 case TypeMapKind.Deferred: return zipWith( 799 this.sources, 800 this.targets, 801 (s, t) => `${(s as DebugType).__debugTypeToString()} -> ${(t() as DebugType).__debugTypeToString()}`).join(", "); 802 case TypeMapKind.Merged: 803 case TypeMapKind.Composite: return `m1: ${(this.mapper1 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")} 804m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")}`; 805 default: return assertNever(this); 806 } 807 } 808 } 809 810 export function attachDebugPrototypeIfDebug(mapper: TypeMapper): TypeMapper { 811 if (isDebugging) { 812 return Object.setPrototypeOf(mapper, DebugTypeMapper.prototype); 813 } 814 return mapper; 815 } 816 } 817} 818