Skip to content

Remove Debug namespace runtime modification #53228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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