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).
This commit is contained in:
Jake Bailey 2022-10-15 00:03:22 -07:00
parent c5a9573787
commit 25a85d1faa
5 changed files with 391 additions and 574 deletions

View File

@ -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,

View File

@ -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<ExtendedDebugModule>;
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<number, FlowGraphNode> = 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<FlowNode, { antecedent: FlowNode }> {
return !!(f.flags & hasAntecedentFlags);
}
function hasNode(f: FlowNode): f is Extract<FlowNode, { node?: Node }> {
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<FlowNode>): 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<FlowNode>) {
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<T>(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;
}
}
}

View File

@ -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<number, FlowGraphNode> = 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<FlowNode, { antecedent: FlowNode }> {
return !!(f.flags & hasAntecedentFlags);
}
function hasNode(f: FlowNode): f is Extract<FlowNode, { node?: Node }> {
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<FlowNode>): 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<FlowNode>) {
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<T>(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;
}
}

View File

@ -1,8 +0,0 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"target": "es2019",
"lib": ["es2019"],
},
"include": ["**/*"]
}

View File

@ -4,7 +4,6 @@
"references": [
{ "path": "./cancellationToken" },
{ "path": "./compiler" },
{ "path": "./debug" },
{ "path": "./deprecatedCompat" },
{ "path": "./executeCommandLine" },
{ "path": "./harness" },