From 32515e4705b1a45e6f9dc62dd6eff78384cd53c4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 15 Oct 2022 00:03:22 -0700 Subject: [PATCH] Move compiler-debug into Debug namespace, which allows the compiler to be tree shaken This debug code was added quite a while ago, constructed such that we wouldn't have to ship this code to our users. However, this is the sole place in the compiler project where the ts namespace "escapes" the bundle. By moving this debug code into the compiler itself, we no longer have any references to the ts namespace itself for our bundles that don't export anything (tsc, typingsInstaller). This lets bundlers tree shake the compiler, reducing the size of our output by _5.7 MB_ (a ridiculous improvement for _adding_ code). --- Herebyfile.mjs | 19 +- src/compiler/debug.ts | 429 +++++++++++++++++++++++++++---- src/debug/compilerDebug.ts | 508 ------------------------------------- src/debug/tsconfig.json | 8 - src/tsconfig.json | 1 - 5 files changed, 391 insertions(+), 574 deletions(-) delete mode 100644 src/debug/compilerDebug.ts delete mode 100644 src/debug/tsconfig.json diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 08f790ae624a9..4e39cf9a41788 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -364,15 +364,6 @@ function entrypointBuildTask(options) { return { build, bundle, shim, main, watch }; } -const { main: compilerDebug } = entrypointBuildTask({ - name: "compiler-debug", - buildDeps: [generateDiagnostics], - project: "src/debug", - srcEntrypoint: "./src/debug/compilerDebug.ts", - builtEntrypoint: "./built/local/debug/compilerDebug.js", - output: "./built/local/compiler-debug.js", -}); - const { main: tsc, watch: watchTsc } = entrypointBuildTask({ name: "tsc", @@ -382,7 +373,7 @@ const { main: tsc, watch: watchTsc } = entrypointBuildTask({ srcEntrypoint: "./src/tsc/tsc.ts", builtEntrypoint: "./built/local/tsc/tsc.js", output: "./built/local/tsc.js", - mainDeps: [generateLibs, compilerDebug], + mainDeps: [generateLibs], }); export { tsc, watchTsc }; @@ -395,7 +386,7 @@ const { main: services, build: buildServices, watch: watchServices } = entrypoin srcEntrypoint: "./src/typescript/typescript.ts", builtEntrypoint: "./built/local/typescript/typescript.js", output: "./built/local/typescript.js", - mainDeps: [generateLibs, compilerDebug], + mainDeps: [generateLibs], bundlerOptions: { exportIsTsObject: true }, }); export { services, watchServices }; @@ -420,7 +411,7 @@ const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({ srcEntrypoint: "./src/tsserver/server.ts", builtEntrypoint: "./built/local/tsserver/server.js", output: "./built/local/tsserver.js", - mainDeps: [generateLibs, compilerDebug], + mainDeps: [generateLibs], // Even though this seems like an exectuable, so could be the default CJS, // this is used in the browser too. Do the same thing that we do for our // libraries and generate an IIFE with name `ts`, as to not pollute the global @@ -453,7 +444,7 @@ const { main: lssl, build: buildLssl, watch: watchLssl } = entrypointBuildTask({ srcEntrypoint: "./src/tsserverlibrary/tsserverlibrary.ts", builtEntrypoint: "./built/local/tsserverlibrary/tsserverlibrary.js", output: "./built/local/tsserverlibrary.js", - mainDeps: [generateLibs, compilerDebug], + mainDeps: [generateLibs], bundlerOptions: { exportIsTsObject: true }, }); export { lssl, watchLssl }; @@ -485,7 +476,7 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({ srcEntrypoint: "./src/testRunner/_namespaces/Harness.ts", builtEntrypoint: "./built/local/testRunner/runner.js", output: testRunner, - mainDeps: [generateLibs, compilerDebug], + mainDeps: [generateLibs], bundlerOptions: { // Ensure we never drop any dead code, which might be helpful while debugging. treeShaking: false, diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts index 591cd8945d28f..adb2d539c8a4f 100644 --- a/src/compiler/debug.ts +++ b/src/compiler/debug.ts @@ -1,18 +1,18 @@ import * as ts from "./_namespaces/ts"; import { - AnyFunction, AssertionLevel, BigIntLiteralType, CheckMode, compareValues, EmitFlags, every, FlowFlags, FlowNode, - FlowNodeBase, formatStringFromArgs, getDirectoryPath, getEffectiveModifierFlagsNoCache, getEmitFlags, getOwnKeys, + AnyFunction, AssertionLevel, BigIntLiteralType, CheckMode, compareValues, EmitFlags, every, FlowFlags, FlowLabel, FlowNode, + FlowNodeBase, FlowSwitchClause, formatStringFromArgs, getEffectiveModifierFlagsNoCache, getEmitFlags, getOwnKeys, getParseTreeNode, getSourceFileOfNode, getSourceTextOfNodeFromSourceFile, hasProperty, idText, IntrinsicType, isArrayTypeNode, isBigIntLiteral, isCallSignatureDeclaration, isConditionalTypeNode, isConstructorDeclaration, - isConstructorTypeNode, isConstructSignatureDeclaration, isFunctionTypeNode, isGeneratedIdentifier, + isConstructorTypeNode, isConstructSignatureDeclaration, isDefaultClause, isFunctionTypeNode, isGeneratedIdentifier, isGetAccessorDeclaration, isIdentifier, isImportTypeNode, isIndexedAccessTypeNode, isIndexSignatureDeclaration, isInferTypeNode, isIntersectionTypeNode, isLiteralTypeNode, isMappedTypeNode, isNamedTupleMember, isNumericLiteral, isOptionalTypeNode, isParameter, isParenthesizedTypeNode, isParseTreeNode, isPrivateIdentifier, isRestTypeNode, isSetAccessorDeclaration, isStringLiteral, isThisTypeNode, isTupleTypeNode, isTypeLiteralNode, isTypeOperatorNode, isTypeParameterDeclaration, isTypePredicateNode, isTypeQueryNode, isTypeReferenceNode, isUnionTypeNode, LiteralType, map, Map, MatchingKeys, ModifierFlags, Node, NodeArray, NodeFlags, nodeIsSynthesized, noop, objectAllocator, - ObjectFlags, ObjectType, RelationComparisonResult, RequireResult, resolvePath, Signature, SignatureCheckMode, - SignatureFlags, SnippetKind, SortedReadonlyArray, stableSort, Symbol, SymbolFlags, symbolName, SyntaxKind, sys, + ObjectFlags, ObjectType, RelationComparisonResult, Set, Signature, SignatureCheckMode, + SignatureFlags, SnippetKind, SortedReadonlyArray, stableSort, Symbol, SymbolFlags, symbolName, SyntaxKind, TransformFlags, Type, TypeFacts, TypeFlags, TypeMapKind, TypeMapper, unescapeLeadingUnderscores, VarianceFlags, version, Version, zipWith, } from "./_namespaces/ts"; @@ -434,29 +434,6 @@ export namespace Debug { 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); - } - let flowNodeProto: FlowNodeBase | undefined; function attachFlowNodeDebugInfoWorker(flowNode: FlowNodeBase) { @@ -717,21 +694,6 @@ export namespace Debug { } } - // 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; } @@ -834,4 +796,385 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") } return mapper; } + + export function printControlFlowGraph(flowNode: FlowNode) { + return console.log(formatControlFlowGraph(flowNode)); + } + + export function formatControlFlowGraph(flowNode: FlowNode) { + let nextDebugFlowId = -1; + + function getDebugFlowNodeId(f: FlowNode) { + if (!f.id) { + f.id = nextDebugFlowId; + nextDebugFlowId--; + } + return f.id; + } + + const enum BoxCharacter { + lr = "─", + ud = "│", + dr = "╭", + dl = "╮", + ul = "╯", + ur = "╰", + udr = "├", + udl = "┤", + dlr = "┬", + ulr = "┴", + udlr = "╫", + } + + const enum Connection { + None = 0, + Up = 1 << 0, + Down = 1 << 1, + Left = 1 << 2, + Right = 1 << 3, + + UpDown = Up | Down, + LeftRight = Left | Right, + UpLeft = Up | Left, + UpRight = Up | Right, + DownLeft = Down | Left, + DownRight = Down | Right, + UpDownLeft = UpDown | Left, + UpDownRight = UpDown | Right, + UpLeftRight = Up | LeftRight, + DownLeftRight = Down | LeftRight, + UpDownLeftRight = UpDown | LeftRight, + + NoChildren = 1 << 4, + } + + interface FlowGraphNode { + id: number; + flowNode: FlowNode; + edges: FlowGraphEdge[]; + text: string; + lane: number; + endLane: number; + level: number; + circular: boolean | "circularity"; + } + + interface FlowGraphEdge { + source: FlowGraphNode; + target: FlowGraphNode; + } + + const hasAntecedentFlags = + FlowFlags.Assignment | + FlowFlags.Condition | + FlowFlags.SwitchClause | + FlowFlags.ArrayMutation | + FlowFlags.Call | + FlowFlags.ReduceLabel; + + const hasNodeFlags = + FlowFlags.Start | + FlowFlags.Assignment | + FlowFlags.Call | + FlowFlags.Condition | + FlowFlags.ArrayMutation; + + const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null + const nodes: FlowGraphNode[] = []; + const edges: FlowGraphEdge[] = []; + const root = buildGraphNode(flowNode, new Set()); + for (const node of nodes) { + node.text = renderFlowNode(node.flowNode, node.circular); + computeLevel(node); + } + + const height = computeHeight(root); + const columnWidths = computeColumnWidths(height); + computeLanes(root, 0); + return renderGraph(); + + function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { + return !!(f.flags & FlowFlags.SwitchClause); + } + + function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { + return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; + } + + function hasAntecedent(f: FlowNode): f is Extract { + return !!(f.flags & hasAntecedentFlags); + } + + function hasNode(f: FlowNode): f is Extract { + return !!(f.flags & hasNodeFlags); + } + + function getChildren(node: FlowGraphNode) { + const children: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.source === node) { + children.push(edge.target); + } + } + return children; + } + + function getParents(node: FlowGraphNode) { + const parents: FlowGraphNode[] = []; + for (const edge of node.edges) { + if (edge.target === node) { + parents.push(edge.source); + } + } + return parents; + } + + function buildGraphNode(flowNode: FlowNode, seen: Set): FlowGraphNode { + const id = getDebugFlowNodeId(flowNode); + let graphNode = links[id]; + if (graphNode && seen.has(flowNode)) { + graphNode.circular = true; + graphNode = { + id: -1, + flowNode, + edges: [], + text: "", + lane: -1, + endLane: -1, + level: -1, + circular: "circularity" + }; + nodes.push(graphNode); + return graphNode; + } + seen.add(flowNode); + if (!graphNode) { + links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; + nodes.push(graphNode); + if (hasAntecedents(flowNode)) { + for (const antecedent of flowNode.antecedents) { + buildGraphEdge(graphNode, antecedent, seen); + } + } + else if (hasAntecedent(flowNode)) { + buildGraphEdge(graphNode, flowNode.antecedent, seen); + } + } + seen.delete(flowNode); + return graphNode; + } + + function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode, seen: Set) { + const target = buildGraphNode(antecedent, seen); + const edge: FlowGraphEdge = { source, target }; + edges.push(edge); + source.edges.push(edge); + target.edges.push(edge); + } + + function computeLevel(node: FlowGraphNode): number { + if (node.level !== -1) { + return node.level; + } + let level = 0; + for (const parent of getParents(node)) { + level = Math.max(level, computeLevel(parent) + 1); + } + return node.level = level; + } + + function computeHeight(node: FlowGraphNode): number { + let height = 0; + for (const child of getChildren(node)) { + height = Math.max(height, computeHeight(child)); + } + return height + 1; + } + + function computeColumnWidths(height: number) { + const columns: number[] = fill(Array(height), 0); + for (const node of nodes) { + columns[node.level] = Math.max(columns[node.level], node.text.length); + } + return columns; + } + + function computeLanes(node: FlowGraphNode, lane: number) { + if (node.lane === -1) { + node.lane = lane; + node.endLane = lane; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + if (i > 0) lane++; + const child = children[i]; + computeLanes(child, lane); + if (child.endLane > node.endLane) { + lane = child.endLane; + } + } + node.endLane = lane; + } + } + + function getHeader(flags: FlowFlags) { + if (flags & FlowFlags.Start) return "Start"; + if (flags & FlowFlags.BranchLabel) return "Branch"; + if (flags & FlowFlags.LoopLabel) return "Loop"; + if (flags & FlowFlags.Assignment) return "Assignment"; + if (flags & FlowFlags.TrueCondition) return "True"; + if (flags & FlowFlags.FalseCondition) return "False"; + if (flags & FlowFlags.SwitchClause) return "SwitchClause"; + if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; + if (flags & FlowFlags.Call) return "Call"; + if (flags & FlowFlags.ReduceLabel) return "ReduceLabel"; + if (flags & FlowFlags.Unreachable) return "Unreachable"; + throw new Error(); + } + + function getNodeText(node: Node) { + const sourceFile = getSourceFileOfNode(node); + return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); + } + + function renderFlowNode(flowNode: FlowNode, circular: boolean | "circularity") { + let text = getHeader(flowNode.flags); + if (circular) { + text = `${text}#${getDebugFlowNodeId(flowNode)}`; + } + if (hasNode(flowNode)) { + if (flowNode.node) { + text += ` (${getNodeText(flowNode.node)})`; + } + } + else if (isFlowSwitchClause(flowNode)) { + const clauses: string[] = []; + for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { + const clause = flowNode.switchStatement.caseBlock.clauses[i]; + if (isDefaultClause(clause)) { + clauses.push("default"); + } + else { + clauses.push(getNodeText(clause.expression)); + } + } + text += ` (${clauses.join(", ")})`; + } + return circular === "circularity" ? `Circular(${text})` : text; + } + + function renderGraph() { + const columnCount = columnWidths.length; + const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; + const lanes: string[] = fill(Array(laneCount), ""); + const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); + const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); + + // build connectors + for (const node of nodes) { + grid[node.level][node.lane] = node; + const children = getChildren(node); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + let connector: Connection = Connection.Right; + if (child.lane === node.lane) connector |= Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < children.length - 1) connector |= Connection.Down; + connectors[node.level][child.lane] |= connector; + } + if (children.length === 0) { + connectors[node.level][node.lane] |= Connection.NoChildren; + } + const parents = getParents(node); + for (let i = 0; i < parents.length; i++) { + const parent = parents[i]; + let connector: Connection = Connection.Left; + if (i > 0) connector |= Connection.Up; + if (i < parents.length - 1) connector |= Connection.Down; + connectors[node.level - 1][parent.lane] |= connector; + } + } + + // fill in missing connectors + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < laneCount; lane++) { + const left = column > 0 ? connectors[column - 1][lane] : 0; + const above = lane > 0 ? connectors[column][lane - 1] : 0; + let connector = connectors[column][lane]; + if (!connector) { + if (left & Connection.Right) connector |= Connection.LeftRight; + if (above & Connection.Down) connector |= Connection.UpDown; + connectors[column][lane] = connector; + } + } + } + + for (let column = 0; column < columnCount; column++) { + for (let lane = 0; lane < lanes.length; lane++) { + const connector = connectors[column][lane]; + const fill = connector & Connection.Left ? BoxCharacter.lr : " "; + const node = grid[column][lane]; + if (!node) { + if (column < columnCount - 1) { + writeLane(lane, repeat(fill, columnWidths[column] + 1)); + } + } + else { + writeLane(lane, node.text); + if (column < columnCount - 1) { + writeLane(lane, " "); + writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); + } + } + writeLane(lane, getBoxCharacter(connector)); + writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); + } + } + + return `\n${lanes.join("\n")}\n`; + + function writeLane(lane: number, text: string) { + lanes[lane] += text; + } + } + + function getBoxCharacter(connector: Connection) { + switch (connector) { + case Connection.UpDown: return BoxCharacter.ud; + case Connection.LeftRight: return BoxCharacter.lr; + case Connection.UpLeft: return BoxCharacter.ul; + case Connection.UpRight: return BoxCharacter.ur; + case Connection.DownLeft: return BoxCharacter.dl; + case Connection.DownRight: return BoxCharacter.dr; + case Connection.UpDownLeft: return BoxCharacter.udl; + case Connection.UpDownRight: return BoxCharacter.udr; + case Connection.UpLeftRight: return BoxCharacter.ulr; + case Connection.DownLeftRight: return BoxCharacter.dlr; + case Connection.UpDownLeftRight: return BoxCharacter.udlr; + } + return " "; + } + + function fill(array: T[], value: T) { + if (array.fill) { + array.fill(value); + } + else { + for (let i = 0; i < array.length; i++) { + array[i] = value; + } + } + return array; + } + + function repeat(ch: string, length: number) { + if (ch.repeat) { + return length > 0 ? ch.repeat(length) : ""; + } + let s = ""; + while (s.length < length) { + s += ch; + } + return s; + } + } } diff --git a/src/debug/compilerDebug.ts b/src/debug/compilerDebug.ts deleted file mode 100644 index 906a166332a0d..0000000000000 --- a/src/debug/compilerDebug.ts +++ /dev/null @@ -1,508 +0,0 @@ -interface Node { - kind: number; -} - -type FunctionExpression = Node; -type ArrowFunction = Node; -type MethodDeclaration = Node; -type Expression = Node; -type SourceFile = Node; -type VariableDeclaration = Node; -type BindingElement = Node; -type CallExpression = Node; -type BinaryExpression = Node; - -interface SwitchStatement extends Node { - caseBlock: CaseBlock; -} - -interface CaseBlock extends Node { - clauses: (CaseClause | DefaultClause)[]; -} - -interface CaseClause extends Node { - _caseclauseBrand: any; - expression: Expression; -} - -interface DefaultClause extends Node { - _defaultClauseBrand: any; -} - -interface TypeScriptModule { - readonly SyntaxKind: { - readonly CaseClause: number; - readonly DefaultClause: number; - }; - - readonly FlowFlags: { - readonly Unreachable: number, - readonly Start: number, - readonly BranchLabel: number, - readonly LoopLabel: number, - readonly Assignment: number, - readonly TrueCondition: number, - readonly FalseCondition: number, - readonly SwitchClause: number, - readonly ArrayMutation: number, - readonly Call: number, - readonly ReduceLabel: number, - readonly Referenced: number, - readonly Shared: number, - readonly Label: number, - readonly Condition: number, - }; - - getSourceFileOfNode(node: Node): SourceFile; - getSourceTextOfNodeFromSourceFile(sourceFile: SourceFile, node: Node, includeTrivia?: boolean): string; - isDefaultClause(node: Node): node is DefaultClause; -} - -type FlowNode = - | FlowStart - | FlowLabel - | FlowAssignment - | FlowCall - | FlowCondition - | FlowSwitchClause - | FlowArrayMutation - | FlowReduceLabel - ; - -interface FlowNodeBase { - flags: FlowFlags; - id?: number; -} - -interface FlowStart extends FlowNodeBase { - node?: FunctionExpression | ArrowFunction | MethodDeclaration; -} - -interface FlowLabel extends FlowNodeBase { - antecedents: FlowNode[] | undefined; -} - -interface FlowAssignment extends FlowNodeBase { - node: Expression | VariableDeclaration | BindingElement; - antecedent: FlowNode; -} - -interface FlowCall extends FlowNodeBase { - node: CallExpression; - antecedent: FlowNode; -} - -interface FlowCondition extends FlowNodeBase { - node: Expression; - antecedent: FlowNode; -} - -interface FlowSwitchClause extends FlowNodeBase { - switchStatement: SwitchStatement; - clauseStart: number; - clauseEnd: number; - antecedent: FlowNode; -} - -interface FlowArrayMutation extends FlowNodeBase { - node: CallExpression | BinaryExpression; - antecedent: FlowNode; -} - -/** @internal */ -export interface FlowReduceLabel extends FlowNodeBase { - target: FlowLabel; - antecedents: FlowNode[]; - antecedent: FlowNode; -} - -type FlowFlags = number; -let FlowFlags: TypeScriptModule["FlowFlags"]; -let getSourceFileOfNode: TypeScriptModule["getSourceFileOfNode"]; -let getSourceTextOfNodeFromSourceFile: TypeScriptModule["getSourceTextOfNodeFromSourceFile"]; -let isDefaultClause: TypeScriptModule["isDefaultClause"]; - -/** @internal */ -export function init(ts: TypeScriptModule) { - FlowFlags = ts.FlowFlags; - getSourceFileOfNode = ts.getSourceFileOfNode; - getSourceTextOfNodeFromSourceFile = ts.getSourceTextOfNodeFromSourceFile; - isDefaultClause = ts.isDefaultClause; -} - -let nextDebugFlowId = -1; - -function getDebugFlowNodeId(f: FlowNode) { - if (!f.id) { - f.id = nextDebugFlowId; - nextDebugFlowId--; - } - return f.id; -} - -/** @internal */ -export function formatControlFlowGraph(flowNode: FlowNode) { - const enum BoxCharacter { - lr = "─", - ud = "│", - dr = "╭", - dl = "╮", - ul = "╯", - ur = "╰", - udr = "├", - udl = "┤", - dlr = "┬", - ulr = "┴", - udlr = "╫", - } - - const enum Connection { - Up = 1 << 0, - Down = 1 << 1, - Left = 1 << 2, - Right = 1 << 3, - - UpDown = Up | Down, - LeftRight = Left | Right, - UpLeft = Up | Left, - UpRight = Up | Right, - DownLeft = Down | Left, - DownRight = Down | Right, - UpDownLeft = UpDown | Left, - UpDownRight = UpDown | Right, - UpLeftRight = Up | LeftRight, - DownLeftRight = Down | LeftRight, - UpDownLeftRight = UpDown | LeftRight, - - NoChildren = 1 << 4, - } - - interface FlowGraphNode { - id: number; - flowNode: FlowNode; - edges: FlowGraphEdge[]; - text: string; - lane: number; - endLane: number; - level: number; - circular: boolean | "circularity"; - } - - interface FlowGraphEdge { - source: FlowGraphNode; - target: FlowGraphNode; - } - - const hasAntecedentFlags = - FlowFlags.Assignment | - FlowFlags.Condition | - FlowFlags.SwitchClause | - FlowFlags.ArrayMutation | - FlowFlags.Call | - FlowFlags.ReduceLabel; - - const hasNodeFlags = - FlowFlags.Start | - FlowFlags.Assignment | - FlowFlags.Call | - FlowFlags.Condition | - FlowFlags.ArrayMutation; - - const links: Record = Object.create(/*o*/ null); // eslint-disable-line no-null/no-null - const nodes: FlowGraphNode[] = []; - const edges: FlowGraphEdge[] = []; - const root = buildGraphNode(flowNode, new Set()); - for (const node of nodes) { - node.text = renderFlowNode(node.flowNode, node.circular); - computeLevel(node); - } - - const height = computeHeight(root); - const columnWidths = computeColumnWidths(height); - computeLanes(root, 0); - return renderGraph(); - - function isFlowSwitchClause(f: FlowNode): f is FlowSwitchClause { - return !!(f.flags & FlowFlags.SwitchClause); - } - - function hasAntecedents(f: FlowNode): f is FlowLabel & { antecedents: FlowNode[] } { - return !!(f.flags & FlowFlags.Label) && !!(f as FlowLabel).antecedents; - } - - function hasAntecedent(f: FlowNode): f is Extract { - return !!(f.flags & hasAntecedentFlags); - } - - function hasNode(f: FlowNode): f is Extract { - return !!(f.flags & hasNodeFlags); - } - - function getChildren(node: FlowGraphNode) { - const children: FlowGraphNode[] = []; - for (const edge of node.edges) { - if (edge.source === node) { - children.push(edge.target); - } - } - return children; - } - - function getParents(node: FlowGraphNode) { - const parents: FlowGraphNode[] = []; - for (const edge of node.edges) { - if (edge.target === node) { - parents.push(edge.source); - } - } - return parents; - } - - function buildGraphNode(flowNode: FlowNode, seen: Set): FlowGraphNode { - const id = getDebugFlowNodeId(flowNode); - let graphNode = links[id]; - if (graphNode && seen.has(flowNode)) { - graphNode.circular = true; - graphNode = { - id: -1, - flowNode, - edges: [], - text: "", - lane: -1, - endLane: -1, - level: -1, - circular: "circularity" - }; - nodes.push(graphNode); - return graphNode; - } - seen.add(flowNode); - if (!graphNode) { - links[id] = graphNode = { id, flowNode, edges: [], text: "", lane: -1, endLane: -1, level: -1, circular: false }; - nodes.push(graphNode); - if (hasAntecedents(flowNode)) { - for (const antecedent of flowNode.antecedents) { - buildGraphEdge(graphNode, antecedent, seen); - } - } - else if (hasAntecedent(flowNode)) { - buildGraphEdge(graphNode, flowNode.antecedent, seen); - } - } - seen.delete(flowNode); - return graphNode; - } - - function buildGraphEdge(source: FlowGraphNode, antecedent: FlowNode, seen: Set) { - const target = buildGraphNode(antecedent, seen); - const edge: FlowGraphEdge = { source, target }; - edges.push(edge); - source.edges.push(edge); - target.edges.push(edge); - } - - function computeLevel(node: FlowGraphNode): number { - if (node.level !== -1) { - return node.level; - } - let level = 0; - for (const parent of getParents(node)) { - level = Math.max(level, computeLevel(parent) + 1); - } - return node.level = level; - } - - function computeHeight(node: FlowGraphNode): number { - let height = 0; - for (const child of getChildren(node)) { - height = Math.max(height, computeHeight(child)); - } - return height + 1; - } - - function computeColumnWidths(height: number) { - const columns: number[] = fill(Array(height), 0); - for (const node of nodes) { - columns[node.level] = Math.max(columns[node.level], node.text.length); - } - return columns; - } - - function computeLanes(node: FlowGraphNode, lane: number) { - if (node.lane === -1) { - node.lane = lane; - node.endLane = lane; - const children = getChildren(node); - for (let i = 0; i < children.length; i++) { - if (i > 0) lane++; - const child = children[i]; - computeLanes(child, lane); - if (child.endLane > node.endLane) { - lane = child.endLane; - } - } - node.endLane = lane; - } - } - - function getHeader(flags: FlowFlags) { - if (flags & FlowFlags.Start) return "Start"; - if (flags & FlowFlags.BranchLabel) return "Branch"; - if (flags & FlowFlags.LoopLabel) return "Loop"; - if (flags & FlowFlags.Assignment) return "Assignment"; - if (flags & FlowFlags.TrueCondition) return "True"; - if (flags & FlowFlags.FalseCondition) return "False"; - if (flags & FlowFlags.SwitchClause) return "SwitchClause"; - if (flags & FlowFlags.ArrayMutation) return "ArrayMutation"; - if (flags & FlowFlags.Call) return "Call"; - if (flags & FlowFlags.ReduceLabel) return "ReduceLabel"; - if (flags & FlowFlags.Unreachable) return "Unreachable"; - throw new Error(); - } - - function getNodeText(node: Node) { - const sourceFile = getSourceFileOfNode(node); - return getSourceTextOfNodeFromSourceFile(sourceFile, node, /*includeTrivia*/ false); - } - - function renderFlowNode(flowNode: FlowNode, circular: boolean | "circularity") { - let text = getHeader(flowNode.flags); - if (circular) { - text = `${text}#${getDebugFlowNodeId(flowNode)}`; - } - if (hasNode(flowNode)) { - if (flowNode.node) { - text += ` (${getNodeText(flowNode.node)})`; - } - } - else if (isFlowSwitchClause(flowNode)) { - const clauses: string[] = []; - for (let i = flowNode.clauseStart; i < flowNode.clauseEnd; i++) { - const clause = flowNode.switchStatement.caseBlock.clauses[i]; - if (isDefaultClause(clause)) { - clauses.push("default"); - } - else { - clauses.push(getNodeText(clause.expression)); - } - } - text += ` (${clauses.join(", ")})`; - } - return circular === "circularity" ? `Circular(${text})` : text; - } - - function renderGraph() { - const columnCount = columnWidths.length; - const laneCount = nodes.reduce((x, n) => Math.max(x, n.lane), 0) + 1; - const lanes: string[] = fill(Array(laneCount), ""); - const grid: (FlowGraphNode | undefined)[][] = columnWidths.map(() => Array(laneCount)); - const connectors: Connection[][] = columnWidths.map(() => fill(Array(laneCount), 0)); - - // build connectors - for (const node of nodes) { - grid[node.level][node.lane] = node; - const children = getChildren(node); - for (let i = 0; i < children.length; i++) { - const child = children[i]; - let connector: Connection = Connection.Right; - if (child.lane === node.lane) connector |= Connection.Left; - if (i > 0) connector |= Connection.Up; - if (i < children.length - 1) connector |= Connection.Down; - connectors[node.level][child.lane] |= connector; - } - if (children.length === 0) { - connectors[node.level][node.lane] |= Connection.NoChildren; - } - const parents = getParents(node); - for (let i = 0; i < parents.length; i++) { - const parent = parents[i]; - let connector: Connection = Connection.Left; - if (i > 0) connector |= Connection.Up; - if (i < parents.length - 1) connector |= Connection.Down; - connectors[node.level - 1][parent.lane] |= connector; - } - } - - // fill in missing connectors - for (let column = 0; column < columnCount; column++) { - for (let lane = 0; lane < laneCount; lane++) { - const left = column > 0 ? connectors[column - 1][lane] : 0; - const above = lane > 0 ? connectors[column][lane - 1] : 0; - let connector = connectors[column][lane]; - if (!connector) { - if (left & Connection.Right) connector = Connection.LeftRight; - if (above & Connection.Down) connector |= Connection.UpDown; - connectors[column][lane] = connector; - } - } - } - - for (let column = 0; column < columnCount; column++) { - for (let lane = 0; lane < lanes.length; lane++) { - const connector = connectors[column][lane]; - const fill = connector & Connection.Left ? BoxCharacter.lr : " "; - const node = grid[column][lane]; - if (!node) { - if (column < columnCount - 1) { - writeLane(lane, repeat(fill, columnWidths[column] + 1)); - } - } - else { - writeLane(lane, node.text); - if (column < columnCount - 1) { - writeLane(lane, " "); - writeLane(lane, repeat(fill, columnWidths[column] - node.text.length)); - } - } - writeLane(lane, getBoxCharacter(connector)); - writeLane(lane, connector & Connection.Right && column < columnCount - 1 && !grid[column + 1][lane] ? BoxCharacter.lr : " "); - } - } - - return `\n${lanes.join("\n")}\n`; - - function writeLane(lane: number, text: string) { - lanes[lane] += text; - } - } - - function getBoxCharacter(connector: Connection) { - switch (connector) { - case Connection.UpDown: return BoxCharacter.ud; - case Connection.LeftRight: return BoxCharacter.lr; - case Connection.UpLeft: return BoxCharacter.ul; - case Connection.UpRight: return BoxCharacter.ur; - case Connection.DownLeft: return BoxCharacter.dl; - case Connection.DownRight: return BoxCharacter.dr; - case Connection.UpDownLeft: return BoxCharacter.udl; - case Connection.UpDownRight: return BoxCharacter.udr; - case Connection.UpLeftRight: return BoxCharacter.ulr; - case Connection.DownLeftRight: return BoxCharacter.dlr; - case Connection.UpDownLeftRight: return BoxCharacter.udlr; - } - return " "; - } - - function fill(array: T[], value: T) { - if (array.fill) { - array.fill(value); - } - else { - for (let i = 0; i < array.length; i++) { - array[i] = value; - } - } - return array; - } - - function repeat(ch: string, length: number) { - if (ch.repeat) { - return length > 0 ? ch.repeat(length) : ""; - } - let s = ""; - while (s.length < length) { - s += ch; - } - return s; - } -} diff --git a/src/debug/tsconfig.json b/src/debug/tsconfig.json deleted file mode 100644 index ab17c2cab1a99..0000000000000 --- a/src/debug/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../tsconfig-base", - "compilerOptions": { - "target": "es2019", - "lib": ["es2019"], - }, - "include": ["**/*"] -} diff --git a/src/tsconfig.json b/src/tsconfig.json index 213e1eda3c3b2..1cfab491559d3 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,7 +4,6 @@ "references": [ { "path": "./cancellationToken" }, { "path": "./compiler" }, - { "path": "./debug" }, { "path": "./deprecatedCompat" }, { "path": "./executeCommandLine" }, { "path": "./harness" },