1import * as ts from "./_namespaces/ts"; 2import { 3 AnyFunction, 4 AssertionLevel, 5 BigIntLiteralType, 6 CheckMode, 7 compareValues, 8 EmitFlags, 9 every, 10 FlowFlags, 11 FlowLabel, 12 FlowNode, 13 FlowNodeBase, 14 FlowSwitchClause, 15 formatStringFromArgs, 16 getEffectiveModifierFlagsNoCache, 17 getEmitFlags, 18 getOwnKeys, 19 getParseTreeNode, 20 getSourceFileOfNode, 21 getSourceTextOfNodeFromSourceFile, 22 hasProperty, 23 idText, 24 IntrinsicType, 25 isArrayTypeNode, 26 isBigIntLiteral, 27 isCallSignatureDeclaration, 28 isConditionalTypeNode, 29 isConstructorDeclaration, 30 isConstructorTypeNode, 31 isConstructSignatureDeclaration, 32 isDefaultClause, 33 isFunctionTypeNode, 34 isGeneratedIdentifier, 35 isGetAccessorDeclaration, 36 isIdentifier, 37 isImportTypeNode, 38 isIndexedAccessTypeNode, 39 isIndexSignatureDeclaration, 40 isInferTypeNode, 41 isIntersectionTypeNode, 42 isLiteralTypeNode, 43 isMappedTypeNode, 44 isNamedTupleMember, 45 isNumericLiteral, 46 isOptionalTypeNode, 47 isParameter, 48 isParenthesizedTypeNode, 49 isParseTreeNode, 50 isPrivateIdentifier, 51 isRestTypeNode, 52 isSetAccessorDeclaration, 53 isStringLiteral, 54 isThisTypeNode, 55 isTupleTypeNode, 56 isTypeLiteralNode, 57 isTypeOperatorNode, 58 isTypeParameterDeclaration, 59 isTypePredicateNode, 60 isTypeQueryNode, 61 isTypeReferenceNode, 62 isUnionTypeNode, 63 LiteralType, 64 map, 65 Map, 66 MatchingKeys, 67 ModifierFlags, 68 Node, 69 NodeArray, 70 NodeFlags, 71 nodeIsSynthesized, 72 noop, 73 objectAllocator, 74 ObjectFlags, 75 ObjectType, 76 RelationComparisonResult, 77 Set, 78 Signature, 79 SignatureCheckMode, 80 SignatureFlags, 81 SnippetKind, 82 SortedReadonlyArray, 83 stableSort, 84 Symbol, 85 SymbolFlags, 86 symbolName, 87 SyntaxKind, 88 TransformFlags, 89 Type, 90 TypeFacts, 91 TypeFlags, 92 TypeMapKind, 93 TypeMapper, 94 unescapeLeadingUnderscores, 95 VarianceFlags, 96 version, 97 Version, 98 zipWith 99} from "./_namespaces/ts"; 100 101/** @internal */ 102export enum LogLevel { 103 Off, 104 Error, 105 Warning, 106 Info, 107 Verbose 108} 109 110/** @internal */ 111export interface LoggingHost { 112 log(level: LogLevel, s: string): void; 113} 114 115/** @internal */ 116export interface DeprecationOptions { 117 message?: string; 118 error?: boolean; 119 since?: Version | string; 120 warnAfter?: Version | string; 121 errorAfter?: Version | string; 122 typeScriptVersion?: Version | string; 123 name?: string; 124} 125 126/** @internal */ 127export namespace Debug { 128 let typeScriptVersion: Version | undefined; 129 130 /* eslint-disable prefer-const */ 131 let currentAssertionLevel = AssertionLevel.None; 132 export let currentLogLevel = LogLevel.Warning; 133 export let isDebugging = false; 134 export let loggingHost: LoggingHost | undefined; 135 export let enableDeprecationWarnings = true; 136 /* eslint-enable prefer-const */ 137 138 type AssertionKeys = MatchingKeys<typeof Debug, AnyFunction>; 139 export function getTypeScriptVersion() { 140 return typeScriptVersion ?? (typeScriptVersion = new Version(version)); 141 } 142 143 export function shouldLog(level: LogLevel): boolean { 144 return currentLogLevel <= level; 145 } 146 147 function logMessage(level: LogLevel, s: string): void { 148 if (loggingHost && shouldLog(level)) { 149 loggingHost.log(level, s); 150 } 151 } 152 153 export function log(s: string): void { 154 logMessage(LogLevel.Info, s); 155 } 156 157 export namespace log { 158 export function error(s: string): void { 159 logMessage(LogLevel.Error, s); 160 } 161 162 export function warn(s: string): void { 163 logMessage(LogLevel.Warning, s); 164 } 165 166 export function log(s: string): void { 167 logMessage(LogLevel.Info, s); 168 } 169 170 export function trace(s: string): void { 171 logMessage(LogLevel.Verbose, s); 172 } 173 } 174 175 const assertionCache: Partial<Record<AssertionKeys, { level: AssertionLevel, assertion: AnyFunction }>> = {}; 176 177 export function getAssertionLevel() { 178 return currentAssertionLevel; 179 } 180 181 export function setAssertionLevel(level: AssertionLevel) { 182 const prevAssertionLevel = currentAssertionLevel; 183 currentAssertionLevel = level; 184 185 if (level > prevAssertionLevel) { 186 // restore assertion functions for the current assertion level (see `shouldAssertFunction`). 187 for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) { 188 const cachedFunc = assertionCache[key]; 189 if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) { 190 (Debug as any)[key] = cachedFunc; 191 assertionCache[key] = undefined; 192 } 193 } 194 } 195 } 196 197 export function shouldAssert(level: AssertionLevel): boolean { 198 return currentAssertionLevel >= level; 199 } 200 201 /** 202 * Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`. 203 * Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level. 204 * @param level The minimum assertion level required. 205 * @param name The name of the current assertion function. 206 */ 207 function shouldAssertFunction<K extends AssertionKeys>(level: AssertionLevel, name: K): boolean { 208 if (!shouldAssert(level)) { 209 assertionCache[name] = { level, assertion: Debug[name] }; 210 (Debug as any)[name] = noop; 211 return false; 212 } 213 return true; 214 } 215 216 export function fail(message?: string, stackCrawlMark?: AnyFunction): never { 217 debugger; 218 const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); 219 if ((Error as any).captureStackTrace) { 220 (Error as any).captureStackTrace(e, stackCrawlMark || fail); 221 } 222 throw e; 223 } 224 225 export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never { 226 return fail( 227 `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, 228 stackCrawlMark || failBadSyntaxKind); 229 } 230 231 export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression { 232 if (!expression) { 233 message = message ? `False expression: ${message}` : "False expression."; 234 if (verboseDebugInfo) { 235 message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); 236 } 237 fail(message, stackCrawlMark || assert); 238 } 239 } 240 241 export function assertEqual<T>(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void { 242 if (a !== b) { 243 const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; 244 fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual); 245 } 246 } 247 248 export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void { 249 if (a >= b) { 250 fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan); 251 } 252 } 253 254 export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { 255 if (a > b) { 256 fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual); 257 } 258 } 259 260 export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void { 261 if (a < b) { 262 fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual); 263 } 264 } 265 266 export function assertIsDefined<T>(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable<T> { 267 // eslint-disable-next-line no-null/no-null 268 if (value === undefined || value === null) { 269 fail(message, stackCrawlMark || assertIsDefined); 270 } 271 } 272 273 export function checkDefined<T>(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T { 274 assertIsDefined(value, message, stackCrawlMark || checkDefined); 275 return value; 276 } 277 278 export function assertEachIsDefined<T extends Node>(value: NodeArray<T>, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray<T>; 279 export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable<T>[]; 280 export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) { 281 for (const v of value) { 282 assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined); 283 } 284 } 285 286 export function checkEachDefined<T, A extends readonly T[]>(value: A, message?: string, stackCrawlMark?: AnyFunction): A { 287 assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined); 288 return value; 289 } 290 291 export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { 292 const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); 293 return fail(`${message} ${detail}`, stackCrawlMark || assertNever); 294 } 295 296 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>; 297 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[]; 298 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; 299 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; 300 export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void; 301 export function assertEachNode(nodes: readonly Node[] | undefined, test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) { 302 if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) { 303 assert( 304 test === undefined || every(nodes, test), 305 message || "Unexpected node.", 306 () => `Node array did not pass test '${getFunctionName(test)}'.`, 307 stackCrawlMark || assertEachNode); 308 } 309 } 310 311 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; 312 export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 313 export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 314 if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) { 315 assert( 316 node !== undefined && (test === undefined || test(node)), 317 message || "Unexpected node.", 318 () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, 319 stackCrawlMark || assertNode); 320 } 321 } 322 323 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>; 324 export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 325 export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 326 if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) { 327 assert( 328 node === undefined || test === undefined || !test(node), 329 message || "Unexpected node.", 330 () => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`, 331 stackCrawlMark || assertNotNode); 332 } 333 } 334 335 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; 336 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; 337 export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 338 export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) { 339 if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) { 340 assert( 341 test === undefined || node === undefined || test(node), 342 message || "Unexpected node.", 343 () => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`, 344 stackCrawlMark || assertOptionalNode); 345 } 346 } 347 348 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 }>; 349 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; 350 export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void; 351 export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) { 352 if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) { 353 assert( 354 kind === undefined || node === undefined || node.kind === kind, 355 message || "Unexpected node.", 356 () => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`, 357 stackCrawlMark || assertOptionalToken); 358 } 359 } 360 361 export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined; 362 export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) { 363 if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) { 364 assert( 365 node === undefined, 366 message || "Unexpected node.", 367 () => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`, 368 stackCrawlMark || assertMissingNode); 369 } 370 } 371 372 /** 373 * Asserts a value has the specified type in typespace only (does not perform a runtime assertion). 374 * This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and 375 * as a result can reduce the number of unnecessary casts. 376 */ 377 export function type<T>(value: unknown): asserts value is T; 378 export function type(_value: unknown) { } 379 380 export function getFunctionName(func: AnyFunction) { 381 if (typeof func !== "function") { 382 return ""; 383 } 384 else if (hasProperty(func, "name")) { 385 return (func as any).name; 386 } 387 else { 388 const text = Function.prototype.toString.call(func); 389 const match = /^function\s+([\w\$]+)\s*\(/.exec(text); 390 return match ? match[1] : ""; 391 } 392 } 393 394 export function formatSymbol(symbol: Symbol): string { 395 return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; 396 } 397 398 /** 399 * Formats an enum value as a string for debugging and debug assertions. 400 */ 401 export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { 402 const members = getEnumMembers(enumObject); 403 if (value === 0) { 404 return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; 405 } 406 if (isFlags) { 407 const result: string[] = []; 408 let remainingFlags = value; 409 for (const [enumValue, enumName] of members) { 410 if (enumValue > value) { 411 break; 412 } 413 if (enumValue !== 0 && enumValue & value) { 414 result.push(enumName); 415 remainingFlags &= ~enumValue; 416 } 417 } 418 if (remainingFlags === 0) { 419 return result.join("|"); 420 } 421 } 422 else { 423 for (const [enumValue, enumName] of members) { 424 if (enumValue === value) { 425 return enumName; 426 } 427 } 428 } 429 return value.toString(); 430 } 431 432 const enumMemberCache = new Map<any, SortedReadonlyArray<[number, string]>>(); 433 434 function getEnumMembers(enumObject: any) { 435 // Assuming enum objects do not change at runtime, we can cache the enum members list 436 // to reuse later. This saves us from reconstructing this each and every time we call 437 // a formatting function (which can be expensive for large enums like SyntaxKind). 438 const existing = enumMemberCache.get(enumObject); 439 if (existing) { 440 return existing; 441 } 442 443 const result: [number, string][] = []; 444 for (const name in enumObject) { 445 const value = enumObject[name]; 446 if (typeof value === "number") { 447 result.push([value, name]); 448 } 449 } 450 451 const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); 452 enumMemberCache.set(enumObject, sorted); 453 return sorted; 454 } 455 456 export function formatSyntaxKind(kind: SyntaxKind | undefined): string { 457 return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false); 458 } 459 460 export function formatSnippetKind(kind: SnippetKind | undefined): string { 461 return formatEnum(kind, (ts as any).SnippetKind, /*isFlags*/ false); 462 } 463 464 export function formatNodeFlags(flags: NodeFlags | undefined): string { 465 return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true); 466 } 467 468 export function formatModifierFlags(flags: ModifierFlags | undefined): string { 469 return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true); 470 } 471 472 export function formatTransformFlags(flags: TransformFlags | undefined): string { 473 return formatEnum(flags, (ts as any).TransformFlags, /*isFlags*/ true); 474 } 475 476 export function formatEmitFlags(flags: EmitFlags | undefined): string { 477 return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true); 478 } 479 480 export function formatSymbolFlags(flags: SymbolFlags | undefined): string { 481 return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true); 482 } 483 484 export function formatTypeFlags(flags: TypeFlags | undefined): string { 485 return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true); 486 } 487 488 export function formatSignatureFlags(flags: SignatureFlags | undefined): string { 489 return formatEnum(flags, (ts as any).SignatureFlags, /*isFlags*/ true); 490 } 491 492 export function formatObjectFlags(flags: ObjectFlags | undefined): string { 493 return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true); 494 } 495 496 export function formatFlowFlags(flags: FlowFlags | undefined): string { 497 return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true); 498 } 499 500 export function formatRelationComparisonResult(result: RelationComparisonResult | undefined): string { 501 return formatEnum(result, (ts as any).RelationComparisonResult, /*isFlags*/ true); 502 } 503 504 export function formatCheckMode(mode: CheckMode | undefined): string { 505 return formatEnum(mode, (ts as any).CheckMode, /*isFlags*/ true); 506 } 507 508 export function formatSignatureCheckMode(mode: SignatureCheckMode | undefined): string { 509 return formatEnum(mode, (ts as any).SignatureCheckMode, /*isFlags*/ true); 510 } 511 512 export function formatTypeFacts(facts: TypeFacts | undefined): string { 513 return formatEnum(facts, (ts as any).TypeFacts, /*isFlags*/ true); 514 } 515 516 let isDebugInfoEnabled = false; 517 518 let flowNodeProto: FlowNodeBase | undefined; 519 520 function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { 521 if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator 522 Object.defineProperties(flowNode, { 523 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 524 __tsDebuggerDisplay: { 525 value(this: FlowNodeBase) { 526 const flowHeader = 527 this.flags & FlowFlags.Start ? "FlowStart" : 528 this.flags & FlowFlags.BranchLabel ? "FlowBranchLabel" : 529 this.flags & FlowFlags.LoopLabel ? "FlowLoopLabel" : 530 this.flags & FlowFlags.Assignment ? "FlowAssignment" : 531 this.flags & FlowFlags.TrueCondition ? "FlowTrueCondition" : 532 this.flags & FlowFlags.FalseCondition ? "FlowFalseCondition" : 533 this.flags & FlowFlags.SwitchClause ? "FlowSwitchClause" : 534 this.flags & FlowFlags.ArrayMutation ? "FlowArrayMutation" : 535 this.flags & FlowFlags.Call ? "FlowCall" : 536 this.flags & FlowFlags.ReduceLabel ? "FlowReduceLabel" : 537 this.flags & FlowFlags.Unreachable ? "FlowUnreachable" : 538 "UnknownFlow"; 539 const remainingFlags = this.flags & ~(FlowFlags.Referenced - 1); 540 return `${flowHeader}${remainingFlags ? ` (${formatFlowFlags(remainingFlags)})`: ""}`; 541 } 542 }, 543 __debugFlowFlags: { get(this: FlowNodeBase) { return formatEnum(this.flags, (ts as any).FlowFlags, /*isFlags*/ true); } }, 544 __debugToString: { value(this: FlowNodeBase) { return formatControlFlowGraph(this); } } 545 }); 546 } 547 } 548 549 export function attachFlowNodeDebugInfo(flowNode: FlowNodeBase) { 550 if (isDebugInfoEnabled) { 551 if (typeof Object.setPrototypeOf === "function") { 552 // if we're in es2015, attach the method to a shared prototype for `FlowNode` 553 // so the method doesn't show up in the watch window. 554 if (!flowNodeProto) { 555 flowNodeProto = Object.create(Object.prototype) as FlowNodeBase; 556 attachFlowNodeDebugInfoWorker(flowNodeProto); 557 } 558 Object.setPrototypeOf(flowNode, flowNodeProto); 559 } 560 else { 561 // not running in an es2015 environment, attach the method directly. 562 attachFlowNodeDebugInfoWorker(flowNode); 563 } 564 } 565 } 566 567 let nodeArrayProto: NodeArray<Node> | undefined; 568 569 function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) { 570 if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator 571 Object.defineProperties(array, { 572 __tsDebuggerDisplay: { 573 value(this: NodeArray<Node>, defaultValue: string) { 574 // An `Array` with extra properties is rendered as `[A, B, prop1: 1, prop2: 2]`. Most of 575 // these aren't immediately useful so we trim off the `prop1: ..., prop2: ...` part from the 576 // formatted string. 577 // This regex can trigger slow backtracking because of overlapping potential captures. 578 // We don't care, this is debug code that's only enabled with a debugger attached - 579 // we're just taking note of it for anyone checking regex performance in the future. 580 defaultValue = String(defaultValue).replace(/(?:,[\s\w\d_]+:[^,]+)+\]$/, "]"); 581 return `NodeArray ${defaultValue}`; 582 } 583 } 584 }); 585 } 586 } 587 588 export function attachNodeArrayDebugInfo(array: NodeArray<Node>) { 589 if (isDebugInfoEnabled) { 590 if (typeof Object.setPrototypeOf === "function") { 591 // if we're in es2015, attach the method to a shared prototype for `NodeArray` 592 // so the method doesn't show up in the watch window. 593 if (!nodeArrayProto) { 594 nodeArrayProto = Object.create(Array.prototype) as NodeArray<Node>; 595 attachNodeArrayDebugInfoWorker(nodeArrayProto); 596 } 597 Object.setPrototypeOf(array, nodeArrayProto); 598 } 599 else { 600 // not running in an es2015 environment, attach the method directly. 601 attachNodeArrayDebugInfoWorker(array); 602 } 603 } 604 } 605 606 /** 607 * Injects debug information into frequently used types. 608 */ 609 export function enableDebugInfo() { 610 if (isDebugInfoEnabled) return; 611 612 // avoid recomputing 613 let weakTypeTextMap: WeakMap<Type, string> | undefined; 614 let weakNodeTextMap: WeakMap<Node, string> | undefined; 615 616 function getWeakTypeTextMap() { 617 if (weakTypeTextMap === undefined) { 618 if (typeof WeakMap === "function") weakTypeTextMap = new WeakMap(); 619 } 620 return weakTypeTextMap; 621 } 622 623 function getWeakNodeTextMap() { 624 if (weakNodeTextMap === undefined) { 625 if (typeof WeakMap === "function") weakNodeTextMap = new WeakMap(); 626 } 627 return weakNodeTextMap; 628 } 629 630 631 // Add additional properties in debug mode to assist with debugging. 632 Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { 633 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 634 __tsDebuggerDisplay: { 635 value(this: Symbol) { 636 const symbolHeader = 637 this.flags & SymbolFlags.Transient ? "TransientSymbol" : 638 "Symbol"; 639 const remainingSymbolFlags = this.flags & ~SymbolFlags.Transient; 640 return `${symbolHeader} '${symbolName(this)}'${remainingSymbolFlags ? ` (${formatSymbolFlags(remainingSymbolFlags)})` : ""}`; 641 } 642 }, 643 __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } 644 }); 645 646 Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { 647 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 648 __tsDebuggerDisplay: { 649 value(this: Type) { 650 const typeHeader = 651 this.flags & TypeFlags.Nullable ? "NullableType" : 652 this.flags & TypeFlags.StringOrNumberLiteral ? `LiteralType ${JSON.stringify((this as LiteralType).value)}` : 653 this.flags & TypeFlags.BigIntLiteral ? `LiteralType ${(this as BigIntLiteralType).value.negative ? "-" : ""}${(this as BigIntLiteralType).value.base10Value}n` : 654 this.flags & TypeFlags.UniqueESSymbol ? "UniqueESSymbolType" : 655 this.flags & TypeFlags.Enum ? "EnumType" : 656 this.flags & TypeFlags.Intrinsic ? `IntrinsicType ${(this as IntrinsicType).intrinsicName}` : 657 this.flags & TypeFlags.Union ? "UnionType" : 658 this.flags & TypeFlags.Intersection ? "IntersectionType" : 659 this.flags & TypeFlags.Index ? "IndexType" : 660 this.flags & TypeFlags.IndexedAccess ? "IndexedAccessType" : 661 this.flags & TypeFlags.Conditional ? "ConditionalType" : 662 this.flags & TypeFlags.Substitution ? "SubstitutionType" : 663 this.flags & TypeFlags.TypeParameter ? "TypeParameter" : 664 this.flags & TypeFlags.Object ? 665 (this as ObjectType).objectFlags & ObjectFlags.ClassOrInterface ? "InterfaceType" : 666 (this as ObjectType).objectFlags & ObjectFlags.Reference ? "TypeReference" : 667 (this as ObjectType).objectFlags & ObjectFlags.Tuple ? "TupleType" : 668 (this as ObjectType).objectFlags & ObjectFlags.Anonymous ? "AnonymousType" : 669 (this as ObjectType).objectFlags & ObjectFlags.Mapped ? "MappedType" : 670 (this as ObjectType).objectFlags & ObjectFlags.ReverseMapped ? "ReverseMappedType" : 671 (this as ObjectType).objectFlags & ObjectFlags.EvolvingArray ? "EvolvingArrayType" : 672 "ObjectType" : 673 "Type"; 674 const remainingObjectFlags = this.flags & TypeFlags.Object ? (this as ObjectType).objectFlags & ~ObjectFlags.ObjectTypeKindMask : 0; 675 return `${typeHeader}${this.symbol ? ` '${symbolName(this.symbol)}'` : ""}${remainingObjectFlags ? ` (${formatObjectFlags(remainingObjectFlags)})` : ""}`; 676 } 677 }, 678 __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, 679 __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this as ObjectType).objectFlags) : ""; } }, 680 __debugTypeToString: { 681 value(this: Type) { 682 // avoid recomputing 683 const map = getWeakTypeTextMap(); 684 let text = map?.get(this); 685 if (text === undefined) { 686 text = this.checker.typeToString(this); 687 map?.set(this, text); 688 } 689 return text; 690 } 691 }, 692 }); 693 694 Object.defineProperties(objectAllocator.getSignatureConstructor().prototype, { 695 __debugFlags: { get(this: Signature) { return formatSignatureFlags(this.flags); } }, 696 __debugSignatureToString: { value(this: Signature) { return this.checker?.signatureToString(this); } } 697 }); 698 699 const nodeConstructors = [ 700 objectAllocator.getNodeConstructor(), 701 objectAllocator.getIdentifierConstructor(), 702 objectAllocator.getTokenConstructor(), 703 objectAllocator.getSourceFileConstructor() 704 ]; 705 706 for (const ctor of nodeConstructors) { 707 if (!hasProperty(ctor.prototype, "__debugKind")) { 708 Object.defineProperties(ctor.prototype, { 709 // for use with vscode-js-debug's new customDescriptionGenerator in launch.json 710 __tsDebuggerDisplay: { 711 value(this: Node) { 712 const nodeHeader = 713 isGeneratedIdentifier(this) ? "GeneratedIdentifier" : 714 isIdentifier(this) ? `Identifier '${idText(this)}'` : 715 isPrivateIdentifier(this) ? `PrivateIdentifier '${idText(this)}'` : 716 isStringLiteral(this) ? `StringLiteral ${JSON.stringify(this.text.length < 10 ? this.text : this.text.slice(10) + "...")}` : 717 isNumericLiteral(this) ? `NumericLiteral ${this.text}` : 718 isBigIntLiteral(this) ? `BigIntLiteral ${this.text}n` : 719 isTypeParameterDeclaration(this) ? "TypeParameterDeclaration" : 720 isParameter(this) ? "ParameterDeclaration" : 721 isConstructorDeclaration(this) ? "ConstructorDeclaration" : 722 isGetAccessorDeclaration(this) ? "GetAccessorDeclaration" : 723 isSetAccessorDeclaration(this) ? "SetAccessorDeclaration" : 724 isCallSignatureDeclaration(this) ? "CallSignatureDeclaration" : 725 isConstructSignatureDeclaration(this) ? "ConstructSignatureDeclaration" : 726 isIndexSignatureDeclaration(this) ? "IndexSignatureDeclaration" : 727 isTypePredicateNode(this) ? "TypePredicateNode" : 728 isTypeReferenceNode(this) ? "TypeReferenceNode" : 729 isFunctionTypeNode(this) ? "FunctionTypeNode" : 730 isConstructorTypeNode(this) ? "ConstructorTypeNode" : 731 isTypeQueryNode(this) ? "TypeQueryNode" : 732 isTypeLiteralNode(this) ? "TypeLiteralNode" : 733 isArrayTypeNode(this) ? "ArrayTypeNode" : 734 isTupleTypeNode(this) ? "TupleTypeNode" : 735 isOptionalTypeNode(this) ? "OptionalTypeNode" : 736 isRestTypeNode(this) ? "RestTypeNode" : 737 isUnionTypeNode(this) ? "UnionTypeNode" : 738 isIntersectionTypeNode(this) ? "IntersectionTypeNode" : 739 isConditionalTypeNode(this) ? "ConditionalTypeNode" : 740 isInferTypeNode(this) ? "InferTypeNode" : 741 isParenthesizedTypeNode(this) ? "ParenthesizedTypeNode" : 742 isThisTypeNode(this) ? "ThisTypeNode" : 743 isTypeOperatorNode(this) ? "TypeOperatorNode" : 744 isIndexedAccessTypeNode(this) ? "IndexedAccessTypeNode" : 745 isMappedTypeNode(this) ? "MappedTypeNode" : 746 isLiteralTypeNode(this) ? "LiteralTypeNode" : 747 isNamedTupleMember(this) ? "NamedTupleMember" : 748 isImportTypeNode(this) ? "ImportTypeNode" : 749 formatSyntaxKind(this.kind); 750 return `${nodeHeader}${this.flags ? ` (${formatNodeFlags(this.flags)})` : ""}`; 751 } 752 }, 753 __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, 754 __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, 755 __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getEffectiveModifierFlagsNoCache(this)); } }, 756 __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, 757 __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, 758 __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, 759 __debugGetText: { 760 value(this: Node, includeTrivia?: boolean) { 761 if (nodeIsSynthesized(this)) return ""; 762 // avoid recomputing 763 const map = getWeakNodeTextMap(); 764 let text = map?.get(this); 765 if (text === undefined) { 766 const parseNode = getParseTreeNode(this); 767 const sourceFile = parseNode && getSourceFileOfNode(parseNode); 768 text = sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; 769 map?.set(this, text); 770 } 771 return text; 772 } 773 } 774 }); 775 } 776 } 777 778 isDebugInfoEnabled = true; 779 } 780 781 function formatDeprecationMessage(name: string, error: boolean | undefined, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 782 let deprecationMessage = error ? "DeprecationError: " : "DeprecationWarning: "; 783 deprecationMessage += `'${name}' `; 784 deprecationMessage += since ? `has been deprecated since v${since}` : "is deprecated"; 785 deprecationMessage += error ? " and can no longer be used." : errorAfter ? ` and will no longer be usable after v${errorAfter}.` : "."; 786 deprecationMessage += message ? ` ${formatStringFromArgs(message, [name], 0)}` : ""; 787 return deprecationMessage; 788 } 789 790 function createErrorDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 791 const deprecationMessage = formatDeprecationMessage(name, /*error*/ true, errorAfter, since, message); 792 return () => { 793 throw new TypeError(deprecationMessage); 794 }; 795 } 796 797 function createWarningDeprecation(name: string, errorAfter: Version | undefined, since: Version | undefined, message: string | undefined) { 798 let hasWrittenDeprecation = false; 799 return () => { 800 if (enableDeprecationWarnings && !hasWrittenDeprecation) { 801 log.warn(formatDeprecationMessage(name, /*error*/ false, errorAfter, since, message)); 802 hasWrittenDeprecation = true; 803 } 804 }; 805 } 806 807 export function createDeprecation(name: string, options: DeprecationOptions & { error: true }): () => never; 808 export function createDeprecation(name: string, options?: DeprecationOptions): () => void; 809 export function createDeprecation(name: string, options: DeprecationOptions = {}) { 810 const version = typeof options.typeScriptVersion === "string" ? new Version(options.typeScriptVersion) : options.typeScriptVersion ?? getTypeScriptVersion(); 811 const errorAfter = typeof options.errorAfter === "string" ? new Version(options.errorAfter) : options.errorAfter; 812 const warnAfter = typeof options.warnAfter === "string" ? new Version(options.warnAfter) : options.warnAfter; 813 const since = typeof options.since === "string" ? new Version(options.since) : options.since ?? warnAfter; 814 const error = options.error || errorAfter && version.compareTo(errorAfter) <= 0; 815 const warn = !warnAfter || version.compareTo(warnAfter) >= 0; 816 return error ? createErrorDeprecation(name, errorAfter, since, options.message) : 817 warn ? createWarningDeprecation(name, errorAfter, since, options.message) : 818 noop; 819 } 820 821 function wrapFunction<F extends (...args: any[]) => any>(deprecation: () => void, func: F): F { 822 return function (this: unknown) { 823 deprecation(); 824 return func.apply(this, arguments); 825 } as F; 826 } 827 828 export function deprecate<F extends (...args: any[]) => any>(func: F, options?: DeprecationOptions): F { 829 const deprecation = createDeprecation(options?.name ?? getFunctionName(func), options); 830 return wrapFunction(deprecation, func); 831 } 832 833 export function formatVariance(varianceFlags: VarianceFlags) { 834 const variance = varianceFlags & VarianceFlags.VarianceMask; 835 let result = 836 variance === VarianceFlags.Invariant ? "in out" : 837 variance === VarianceFlags.Bivariant ? "[bivariant]" : 838 variance === VarianceFlags.Contravariant ? "in" : 839 variance === VarianceFlags.Covariant ? "out" : 840 variance === VarianceFlags.Independent ? "[independent]" : ""; 841 if (varianceFlags & VarianceFlags.Unmeasurable) { 842 result += " (unmeasurable)"; 843 } 844 else if (varianceFlags & VarianceFlags.Unreliable) { 845 result += " (unreliable)"; 846 } 847 return result; 848 } 849 850 export type DebugType = Type & { __debugTypeToString(): string }; // eslint-disable-line @typescript-eslint/naming-convention 851 export class DebugTypeMapper { 852 declare kind: TypeMapKind; 853 __debugToString(): string { // eslint-disable-line @typescript-eslint/naming-convention 854 type<TypeMapper>(this); 855 switch (this.kind) { 856 case TypeMapKind.Function: return this.debugInfo?.() || "(function mapper)"; 857 case TypeMapKind.Simple: return `${(this.source as DebugType).__debugTypeToString()} -> ${(this.target as DebugType).__debugTypeToString()}`; 858 case TypeMapKind.Array: return zipWith<DebugType, DebugType | string, unknown>( 859 this.sources as readonly DebugType[], 860 this.targets as readonly DebugType[] || map(this.sources, () => "any"), 861 (s, t) => `${s.__debugTypeToString()} -> ${typeof t === "string" ? t : t.__debugTypeToString()}`).join(", "); 862 case TypeMapKind.Deferred: return zipWith( 863 this.sources, 864 this.targets, 865 (s, t) => `${(s as DebugType).__debugTypeToString()} -> ${(t() as DebugType).__debugTypeToString()}`).join(", "); 866 case TypeMapKind.Merged: 867 case TypeMapKind.Composite: return `m1: ${(this.mapper1 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")} 868m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n").join("\n ")}`; 869 default: return assertNever(this); 870 } 871 } 872 } 873 874 export function attachDebugPrototypeIfDebug(mapper: TypeMapper): TypeMapper { 875 if (isDebugging) { 876 return Object.setPrototypeOf(mapper, DebugTypeMapper.prototype); 877 } 878 return mapper; 879 } 880 881 export function printControlFlowGraph(flowNode: FlowNode) { 882 return console.log(formatControlFlowGraph(flowNode)); 883 } 884 885 export function formatControlFlowGraph(flowNode: FlowNode) { 886 let nextDebugFlowId = -1; 887 888 function getDebugFlowNodeId(f: FlowNode) { 889 if (!f.id) { 890 f.id = nextDebugFlowId; 891 nextDebugFlowId--; 892 } 893 return f.id; 894 } 895 896 const enum BoxCharacter { 897 lr = "─", 898 ud = "│", 899 dr = "╭", 900 dl = "╮", 901 ul = "╯", 902 ur = "╰", 903 udr = "├", 904 udl = "┤", 905 dlr = "┬", 906 ulr = "┴", 907 udlr = "╫", 908 } 909 910 const enum Connection { 911 None = 0, 912 Up = 1 << 0, 913 Down = 1 << 1, 914 Left = 1 << 2, 915 Right = 1 << 3, 916 917 UpDown = Up | Down, 918 LeftRight = Left | Right, 919 UpLeft = Up | Left, 920 UpRight = Up | Right, 921 DownLeft = Down | Left, 922 DownRight = Down | Right, 923 UpDownLeft = UpDown | Left, 924 UpDownRight = UpDown | Right, 925 UpLeftRight = Up | LeftRight, 926 DownLeftRight = Down | LeftRight, 927 UpDownLeftRight = UpDown | LeftRight, 928 929 NoChildren = 1 << 4, 930 } 931 932 interface FlowGraphNode { 933 id: number; 934 flowNode: FlowNode; 935 edges: FlowGraphEdge[]; 936 text: string; 937 lane: number; 938 endLane: number; 939 level: number; 940 circular: boolean | "circularity"; 941 } 942 943 interface FlowGraphEdge { 944 source: FlowGraphNode; 945 target: FlowGraphNode; 946 } 947 948 const hasAntecedentFlags = 949 FlowFlags.Assignment | 950 FlowFlags.Condition | 951 FlowFlags.SwitchClause | 952 FlowFlags.ArrayMutation | 953 FlowFlags.Call | 954 FlowFlags.ReduceLabel; 955 956 const hasNodeFlags = 957 FlowFlags.Start | 958 FlowFlags.Assignment | 959 FlowFlags.Call | 960 FlowFlags.Condition | 961 FlowFlags.ArrayMutation; 962 963 const links: Record<number, FlowGraphNode> = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null 964 const nodes: FlowGraphNode[] = []; 965 const edges: FlowGraphEdge[] = []; 966 const root = buildGraphNode(flowNode, new Set()); 967 for (const node of nodes) { 968 node.text = renderFlowNode(node.flowNode, node.circular); 969 computeLevel(node); 970 } 971 972 const height = computeHeight(root); 973 const columnWidths = computeColumnWidths(height); 974 computeLanes(root, 0); 975 return renderGraph(); 976 977 function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { 978 return !!(f.flags & FlowFlags.SwitchClause); 979 } 980 981 function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { 982 return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; 983 } 984 985 function hasAntecedent(f: FlowNode): f is Extract<FlowNode, { antecedent: FlowNode }> { 986 return !!(f.flags & hasAntecedentFlags); 987 } 988 989 function hasNode(f: FlowNode): f is Extract<FlowNode, { node?: Node }> { 990 return !!(f.flags & hasNodeFlags); 991 } 992 993 function getChildren(node: FlowGraphNode) { 994 const children: FlowGraphNode[] = []; 995 for (const edge of node.edges) { 996 if (edge.source === node) { 997 children.push(edge.target); 998 } 999 } 1000 return children; 1001 } 1002 1003 function getParents(node: FlowGraphNode) { 1004 const parents: FlowGraphNode[] = []; 1005 for (const edge of node.edges) { 1006 if (edge.target === node) { 1007 parents.push(edge.source); 1008 } 1009 } 1010 return parents; 1011 } 1012 1013 function buildGraphNode(flowNode: FlowNode, seen: Set<FlowNode>): FlowGraphNode { 1014 const id = getDebugFlowNodeId(flowNode); 1015 let graphNode = links[id]; 1016 if (graphNode && seen.has(flowNode)) { 1017 graphNode.circular = true; 1018 graphNode = { 1019 id: -1, 1020 flowNode, 1021 edges: [], 1022 text: "", 1023 lane: -1, 1024 endLane: -1, 1025 level: -1, 1026 circular: "circularity" 1027 }; 1028 nodes.push(graphNode); 1029 return graphNode; 1030 } 1031 seen.add(flowNode); 1032 if (!graphNode) { 1033 links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; 1034 nodes.push(graphNode); 1035 if (hasAntecedents(flowNode)) { 1036 for (const antecedent of flowNode.antecedents) { 1037 buildGraphEdge(graphNode, antecedent, seen); 1038 } 1039 } 1040 else if (hasAntecedent(flowNode)) { 1041 buildGraphEdge(graphNode, flowNode.antecedent, seen); 1042 } 1043 } 1044 seen.delete(flowNode); 1045 return graphNode; 1046 } 1047 1048 function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode, seen: Set<FlowNode>) { 1049 const target = buildGraphNode(antecedent, seen); 1050 const edge: FlowGraphEdge = { source, target }; 1051 edges.push(edge); 1052 source.edges.push(edge); 1053 target.edges.push(edge); 1054 } 1055 1056 function computeLevel(node: FlowGraphNode): number { 1057 if (node.level !== -1) { 1058 return node.level; 1059 } 1060 let level = 0; 1061 for (const parent of getParents(node)) { 1062 level = Math.max(level, computeLevel(parent) + 1); 1063 } 1064 return node.level = level; 1065 } 1066 1067 function computeHeight(node: FlowGraphNode): number { 1068 let height = 0; 1069 for (const child of getChildren(node)) { 1070 height = Math.max(height, computeHeight(child)); 1071 } 1072 return height + 1; 1073 } 1074 1075 function computeColumnWidths(height: number) { 1076 const columns: number[] = fill(Array(height), 0); 1077 for (const node of nodes) { 1078 columns[node.level] = Math.max(columns[node.level], node.text.length); 1079 } 1080 return columns; 1081 } 1082 1083 function computeLanes(node: FlowGraphNode, lane: number) { 1084 if (node.lane === -1) { 1085 node.lane = lane; 1086 node.endLane = lane; 1087 const children = getChildren(node); 1088 for (let i = 0; i < children.length; i++) { 1089 if (i > 0) { 1090 lane++; 1091 } 1092 const child = children[i]; 1093 computeLanes(child, lane); 1094 if (child.endLane > node.endLane) { 1095 lane = child.endLane; 1096 } 1097 } 1098 node.endLane = lane; 1099 } 1100 } 1101 1102 function getHeader(flags: FlowFlags) { 1103 if (flags & FlowFlags.Start) return "Start"; 1104 if (flags & FlowFlags.BranchLabel) return "Branch"; 1105 if (flags & FlowFlags.LoopLabel) return "Loop"; 1106 if (flags & FlowFlags.Assignment) return "Assignment"; 1107 if (flags & FlowFlags.TrueCondition) return "True"; 1108 if (flags & FlowFlags.FalseCondition) return "False"; 1109 if (flags & FlowFlags.SwitchClause) return "SwitchClause"; 1110 if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; 1111 if (flags & FlowFlags.Call) return "Call"; 1112 if (flags & FlowFlags.ReduceLabel) return "ReduceLabel"; 1113 if (flags & FlowFlags.Unreachable) return "Unreachable"; 1114 throw new Error(); 1115 } 1116 1117 function getNodeText(node: Node) { 1118 const sourceFile = getSourceFileOfNode(node); 1119 return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); 1120 } 1121 1122 function renderFlowNode(flowNode: FlowNode, circular: boolean | "circularity") { 1123 let text = getHeader(flowNode.flags); 1124 if (circular) { 1125 text = `${text}#${getDebugFlowNodeId(flowNode)}`; 1126 } 1127 if (hasNode(flowNode)) { 1128 if (flowNode.node) { 1129 text += ` (${getNodeText(flowNode.node)})`; 1130 } 1131 } 1132 else if (isFlowSwitchClause(flowNode)) { 1133 const clauses: string[] = []; 1134 for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { 1135 const clause = flowNode.switchStatement.caseBlock.clauses[i]; 1136 if (isDefaultClause(clause)) { 1137 clauses.push("default"); 1138 } 1139 else { 1140 clauses.push(getNodeText(clause.expression)); 1141 } 1142 } 1143 text += ` (${clauses.join(", ")})`; 1144 } 1145 return circular === "circularity" ? `Circular(${text})` : text; 1146 } 1147 1148 function renderGraph() { 1149 const columnCount = columnWidths.length; 1150 const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; 1151 const lanes: string[] = fill(Array(laneCount), ""); 1152 const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); 1153 const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); 1154 1155 // build connectors 1156 for (const node of nodes) { 1157 grid[node.level][node.lane] = node; 1158 const children = getChildren(node); 1159 for (let i = 0; i < children.length; i++) { 1160 const child = children[i]; 1161 let connector: Connection = Connection.Right; 1162 if (child.lane === node.lane) connector |= Connection.Left; 1163 if (i > 0) connector |= Connection.Up; 1164 if (i < children.length - 1) connector |= Connection.Down; 1165 connectors[node.level][child.lane] |= connector; 1166 } 1167 if (children.length === 0) { 1168 connectors[node.level][node.lane] |= Connection.NoChildren; 1169 } 1170 const parents = getParents(node); 1171 for (let i = 0; i < parents.length; i++) { 1172 const parent = parents[i]; 1173 let connector: Connection = Connection.Left; 1174 if (i > 0) connector |= Connection.Up; 1175 if (i < parents.length - 1) connector |= Connection.Down; 1176 connectors[node.level - 1][parent.lane] |= connector; 1177 } 1178 } 1179 1180 // fill in missing connectors 1181 for (let column = 0; column < columnCount; column++) { 1182 for (let lane = 0; lane < laneCount; lane++) { 1183 const left = column > 0 ? connectors[column - 1][lane] : 0; 1184 const above = lane > 0 ? connectors[column][lane - 1] : 0; 1185 let connector = connectors[column][lane]; 1186 if (!connector) { 1187 if (left & Connection.Right) connector |= Connection.LeftRight; 1188 if (above & Connection.Down) connector |= Connection.UpDown; 1189 connectors[column][lane] = connector; 1190 } 1191 } 1192 } 1193 1194 for (let column = 0; column < columnCount; column++) { 1195 for (let lane = 0; lane < lanes.length; lane++) { 1196 const connector = connectors[column][lane]; 1197 const fill = connector & Connection.Left ? BoxCharacter.lr : " "; 1198 const node = grid[column][lane]; 1199 if (!node) { 1200 if (column < columnCount - 1) { 1201 writeLane(lane, repeat(fill, columnWidths[column] + 1)); 1202 } 1203 } 1204 else { 1205 writeLane(lane, node.text); 1206 if (column < columnCount - 1) { 1207 writeLane(lane, " "); 1208 writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); 1209 } 1210 } 1211 writeLane(lane, getBoxCharacter(connector)); 1212 writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); 1213 } 1214 } 1215 1216 return `\n${lanes.join("\n")}\n`; 1217 1218 function writeLane(lane: number, text: string) { 1219 lanes[lane] += text; 1220 } 1221 } 1222 1223 function getBoxCharacter(connector: Connection) { 1224 switch (connector) { 1225 case Connection.UpDown: return BoxCharacter.ud; 1226 case Connection.LeftRight: return BoxCharacter.lr; 1227 case Connection.UpLeft: return BoxCharacter.ul; 1228 case Connection.UpRight: return BoxCharacter.ur; 1229 case Connection.DownLeft: return BoxCharacter.dl; 1230 case Connection.DownRight: return BoxCharacter.dr; 1231 case Connection.UpDownLeft: return BoxCharacter.udl; 1232 case Connection.UpDownRight: return BoxCharacter.udr; 1233 case Connection.UpLeftRight: return BoxCharacter.ulr; 1234 case Connection.DownLeftRight: return BoxCharacter.dlr; 1235 case Connection.UpDownLeftRight: return BoxCharacter.udlr; 1236 } 1237 return " "; 1238 } 1239 1240 function fill<T>(array: T[], value: T) { 1241 if (array.fill) { 1242 array.fill(value); 1243 } 1244 else { 1245 for (let i = 0; i < array.length; i++) { 1246 array[i] = value; 1247 } 1248 } 1249 return array; 1250 } 1251 1252 function repeat(ch: string, length: number) { 1253 if (ch.repeat) { 1254 return length > 0 ? ch.repeat(length) : ""; 1255 } 1256 let s = ""; 1257 while (s.length < length) { 1258 s += ch; 1259 } 1260 return s; 1261 } 1262 } 1263} 1264