Skip to content
74 changes: 21 additions & 53 deletions src/compiler/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
FlowSwitchClause,
getEffectiveModifierFlagsNoCache,
getEmitFlags,
getOwnKeys,
getParseTreeNode,
getSourceFileOfNode,
getSourceTextOfNodeFromSourceFile,
Expand Down Expand Up @@ -60,15 +59,13 @@ import {
isUnionTypeNode,
LiteralType,
map,
MatchingKeys,
maxBy,
ModifierFlags,
Node,
NodeArray,
NodeCheckFlags,
NodeFlags,
nodeIsSynthesized,
noop,
objectAllocator,
ObjectFlags,
ObjectType,
Expand Down Expand Up @@ -111,14 +108,22 @@ export interface LoggingHost {

/** @internal */
export namespace Debug {
/* eslint-disable prefer-const */
let currentAssertionLevel = AssertionLevel.None;
export let currentLogLevel = LogLevel.Warning;
export let isDebugging = false;
export let loggingHost: LoggingHost | undefined;
/* eslint-enable prefer-const */
// Why var? It avoids TDZ checks in the runtime which can be costly.
// See: https://github.com/microsoft/TypeScript/issues/52924
// TODO(jakebailey): restore to let/const once Debug is no longer a namespace.
/* eslint-disable no-var */
var currentAssertionLevel = AssertionLevel.None;
export var currentLogLevel = LogLevel.Warning;
export var isDebugging = false;
export var loggingHost: LoggingHost | undefined;

type AssertionKeys = MatchingKeys<typeof Debug, AnyFunction>;
var enumMemberCache = new Map<any, SortedReadonlyArray<[number, string]>>();

var isDebugInfoEnabled = false;

var flowNodeProto: FlowNode | undefined;
var nodeArrayProto: NodeArray<Node> | undefined;
/* eslint-enable no-var */

export function shouldLog(level: LogLevel): boolean {
return currentLogLevel <= level;
Expand Down Expand Up @@ -152,47 +157,18 @@ export namespace Debug {
}
}

const assertionCache: Partial<Record<AssertionKeys, { level: AssertionLevel; assertion: AnyFunction; }>> = {};

export function getAssertionLevel() {
return currentAssertionLevel;
}

export function setAssertionLevel(level: AssertionLevel) {
const prevAssertionLevel = currentAssertionLevel;
currentAssertionLevel = level;

if (level > prevAssertionLevel) {
// restore assertion functions for the current assertion level (see `shouldAssertFunction`).
for (const key of getOwnKeys(assertionCache) as AssertionKeys[]) {
const cachedFunc = assertionCache[key];
if (cachedFunc !== undefined && Debug[key] !== cachedFunc.assertion && level >= cachedFunc.level) {
(Debug as any)[key] = cachedFunc;
assertionCache[key] = undefined;
}
}
}
}

export function shouldAssert(level: AssertionLevel): boolean {
return currentAssertionLevel >= level;
}

/**
* Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`.
* Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level.
* @param level The minimum assertion level required.
* @param name The name of the current assertion function.
*/
function shouldAssertFunction<K extends AssertionKeys>(level: AssertionLevel, name: K): boolean {
if (!shouldAssert(level)) {
assertionCache[name] = { level, assertion: Debug[name] };
(Debug as any)[name] = noop;
return false;
}
return true;
}

export function fail(message?: string, stackCrawlMark?: AnyFunction): never {
// eslint-disable-next-line no-debugger
debugger;
Expand Down Expand Up @@ -281,7 +257,7 @@ export namespace Debug {
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;
export function assertEachNode(nodes: readonly Node[], test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertEachNode(nodes: readonly Node[] | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
test === undefined || every(nodes, test),
message || "Unexpected node.",
Expand All @@ -294,7 +270,7 @@ export namespace Debug {
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;
export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
node !== undefined && (test === undefined || test(node)),
message || "Unexpected node.",
Expand All @@ -307,7 +283,7 @@ export namespace Debug {
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>;
export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
node === undefined || test === undefined || !test(node),
message || "Unexpected node.",
Expand All @@ -321,7 +297,7 @@ export namespace Debug {
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;
export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
test === undefined || node === undefined || test(node),
message || "Unexpected node.",
Expand All @@ -335,7 +311,7 @@ export namespace Debug {
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;
export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
kind === undefined || node === undefined || node.kind === kind,
message || "Unexpected node.",
Expand All @@ -347,7 +323,7 @@ export namespace Debug {

export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined;
export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) {
if (shouldAssert(AssertionLevel.Normal)) {
assert(
node === undefined,
message || "Unexpected node.",
Expand Down Expand Up @@ -417,8 +393,6 @@ export namespace Debug {
return value.toString();
}

const enumMemberCache = new Map<any, SortedReadonlyArray<[number, string]>>();

function getEnumMembers(enumObject: any) {
// Assuming enum objects do not change at runtime, we can cache the enum members list
// to reuse later. This saves us from reconstructing this each and every time we call
Expand Down Expand Up @@ -509,10 +483,6 @@ export namespace Debug {
return formatEnum(facts, (ts as any).TypeFacts, /*isFlags*/ true);
}

let isDebugInfoEnabled = false;

let flowNodeProto: FlowNode | undefined;

function attachFlowNodeDebugInfoWorker(flowNode: FlowNode) {
if (!("__debugFlowFlags" in flowNode)) { // eslint-disable-line local/no-in-operator
Object.defineProperties(flowNode, {
Expand Down Expand Up @@ -568,8 +538,6 @@ export namespace Debug {
return flowNode;
}

let nodeArrayProto: NodeArray<Node> | undefined;

function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) {
if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator
Object.defineProperties(array, {
Expand Down