chrome-devtools-frontend
Version:
Chrome DevTools UI
196 lines • 5.37 kB
JavaScript
// Copyright 2025 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Small utility class to build scope and range trees.
*
* This class allows construction of scope/range trees that will be rejected by the encoder.
* Use this class if you guarantee proper nesting yourself and don't want to pay for the
* checks, otherwise use the `SafeScopeInfoBuilder`.
*
* This class will also silently ignore calls that would fail otherwise. E.g. calling
* `end*` without a matching `start*`.
*/ export class ScopeInfoBuilder {
#scopes = [];
#ranges = [];
#scopeStack = [];
#rangeStack = [];
#knownScopes = new Set();
#keyToScope = new Map();
#lastScope = null;
addNullScope() {
this.#scopes.push(null);
return this;
}
startScope(line, column, options) {
const scope = {
start: {
line,
column
},
end: {
line,
column
},
variables: options?.variables?.slice(0) ?? [],
children: [],
isStackFrame: Boolean(options?.isStackFrame)
};
if (options?.name !== undefined) scope.name = options.name;
if (options?.kind !== undefined) scope.kind = options.kind;
if (this.#scopeStack.length > 0) {
scope.parent = this.#scopeStack.at(-1);
}
this.#scopeStack.push(scope);
this.#knownScopes.add(scope);
if (options?.key !== undefined) this.#keyToScope.set(options.key, scope);
return this;
}
setScopeName(name) {
const scope = this.#scopeStack.at(-1);
if (scope) scope.name = name;
return this;
}
setScopeKind(kind) {
const scope = this.#scopeStack.at(-1);
if (scope) scope.kind = kind;
return this;
}
setScopeStackFrame(isStackFrame) {
const scope = this.#scopeStack.at(-1);
if (scope) scope.isStackFrame = isStackFrame;
return this;
}
setScopeVariables(variables) {
const scope = this.#scopeStack.at(-1);
if (scope) scope.variables = variables.slice(0);
return this;
}
endScope(line, column) {
const scope = this.#scopeStack.pop();
if (!scope) return this;
scope.end = {
line,
column
};
if (this.#scopeStack.length === 0) {
this.#scopes.push(scope);
} else {
this.#scopeStack.at(-1).children.push(scope);
}
this.#lastScope = scope;
return this;
}
/**
* @returns The OriginalScope opened with the most recent `startScope` call, but not yet closed.
*/ currentScope() {
return this.#scopeStack.at(-1) ?? null;
}
/**
* @returns The most recent OriginalScope closed with `endScope`.
*/ lastScope() {
return this.#lastScope;
}
/**
* @param option The definition 'scope' of this range can either be the "OriginalScope" directly
* (produced by this builder) or the scope's key set while building the scope.
*/ startRange(line, column, options) {
const range = {
start: {
line,
column
},
end: {
line,
column
},
isStackFrame: Boolean(options?.isStackFrame),
isHidden: Boolean(options?.isHidden),
values: options?.values ?? [],
children: []
};
if (this.#rangeStack.length > 0) {
range.parent = this.#rangeStack.at(-1);
}
if (options?.scope !== undefined) {
range.originalScope = options.scope;
} else if (options?.scopeKey !== undefined) {
range.originalScope = this.#keyToScope.get(options.scopeKey);
}
if (options?.callSite) {
range.callSite = options.callSite;
}
this.#rangeStack.push(range);
return this;
}
setRangeDefinitionScope(scope) {
const range = this.#rangeStack.at(-1);
if (range) range.originalScope = scope;
return this;
}
setRangeDefinitionScopeKey(scopeKey) {
const range = this.#rangeStack.at(-1);
if (range) range.originalScope = this.#keyToScope.get(scopeKey);
return this;
}
setRangeStackFrame(isStackFrame) {
const range = this.#rangeStack.at(-1);
if (range) range.isStackFrame = isStackFrame;
return this;
}
setRangeHidden(isHidden) {
const range = this.#rangeStack.at(-1);
if (range) range.isHidden = isHidden;
return this;
}
setRangeValues(values) {
const range = this.#rangeStack.at(-1);
if (range) range.values = values;
return this;
}
setRangeCallSite(callSite) {
const range = this.#rangeStack.at(-1);
if (range) range.callSite = callSite;
return this;
}
endRange(line, column) {
const range = this.#rangeStack.pop();
if (!range) return this;
range.end = {
line,
column
};
if (this.#rangeStack.length === 0) {
this.#ranges.push(range);
} else {
this.#rangeStack.at(-1).children.push(range);
}
return this;
}
build() {
const info = {
scopes: this.#scopes,
ranges: this.#ranges
};
this.#scopes = [];
this.#ranges = [];
this.#knownScopes.clear();
return info;
}
get scopeStack() {
return this.#scopeStack;
}
get rangeStack() {
return this.#rangeStack;
}
isKnownScope(scope) {
return this.#knownScopes.has(scope);
}
isValidScopeKey(key) {
return this.#keyToScope.has(key);
}
getScopeByValidKey(key) {
return this.#keyToScope.get(key);
}
}
//# sourceMappingURL=builder.js.map