UNPKG

chrome-devtools-frontend

Version:
257 lines • 10 kB
// 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. import { EncodedTag, GeneratedRangeFlags, OriginalScopeFlags } from "../codec.js"; import { encodeSigned, encodeUnsigned } from "../vlq.js"; import { comparePositions } from "../util.js"; const DEFAULT_SCOPE_STATE = { line: 0, column: 0, name: 0, kind: 0, variable: 0 }; const DEFAULT_RANGE_STATE = { line: 0, column: 0, defScopeIdx: 0 }; export class Encoder { #info; #names; // Hash map to resolve indices of strings in the "names" array. Otherwise we'd have // to use 'indexOf' for every name we want to encode. #namesToIndex = new Map(); #scopeState = { ...DEFAULT_SCOPE_STATE }; #rangeState = { ...DEFAULT_RANGE_STATE }; #encodedItems = []; #currentItem = ""; #scopeToCount = new Map(); #scopeCounter = 0; constructor(info, names){ this.#info = info; this.#names = names; for(let i = 0; i < names.length; ++i){ this.#namesToIndex.set(names[i], i); } } encode() { this.#encodedItems = []; this.#info.scopes.forEach((scope)=>{ this.#scopeState.line = 0; this.#scopeState.column = 0; this.#encodeOriginalScope(scope); }); this.#info.ranges.forEach((range)=>{ this.#encodeGeneratedRange(range); }); return this.#encodedItems.join(","); } #encodeOriginalScope(scope) { if (scope === null) { this.#encodedItems.push(""); return; } this.#encodeOriginalScopeStart(scope); this.#encodeOriginalScopeVariables(scope); scope.children.forEach((child)=>this.#encodeOriginalScope(child)); this.#encodeOriginalScopeEnd(scope); } #encodeOriginalScopeStart(scope) { const { line, column } = scope.start; this.#verifyPositionWithScopeState(line, column); let flags = 0; const encodedLine = line - this.#scopeState.line; const encodedColumn = encodedLine === 0 ? column - this.#scopeState.column : column; this.#scopeState.line = line; this.#scopeState.column = column; let encodedName; if (scope.name !== undefined) { flags |= OriginalScopeFlags.HAS_NAME; const nameIdx = this.#resolveNamesIdx(scope.name); encodedName = nameIdx - this.#scopeState.name; this.#scopeState.name = nameIdx; } let encodedKind; if (scope.kind !== undefined) { flags |= OriginalScopeFlags.HAS_KIND; const kindIdx = this.#resolveNamesIdx(scope.kind); encodedKind = kindIdx - this.#scopeState.kind; this.#scopeState.kind = kindIdx; } if (scope.isStackFrame) flags |= OriginalScopeFlags.IS_STACK_FRAME; this.#encodeTag(EncodedTag.ORIGINAL_SCOPE_START).#encodeUnsigned(flags).#encodeUnsigned(encodedLine).#encodeUnsigned(encodedColumn); if (encodedName !== undefined) this.#encodeSigned(encodedName); if (encodedKind !== undefined) this.#encodeSigned(encodedKind); this.#finishItem(); this.#scopeToCount.set(scope, this.#scopeCounter++); } #encodeOriginalScopeVariables(scope) { if (scope.variables.length === 0) return; this.#encodeTag(EncodedTag.ORIGINAL_SCOPE_VARIABLES); for (const variable of scope.variables){ const idx = this.#resolveNamesIdx(variable); this.#encodeSigned(idx - this.#scopeState.variable); this.#scopeState.variable = idx; } this.#finishItem(); } #encodeOriginalScopeEnd(scope) { const { line, column } = scope.end; this.#verifyPositionWithScopeState(line, column); const encodedLine = line - this.#scopeState.line; const encodedColumn = encodedLine === 0 ? column - this.#scopeState.column : column; this.#scopeState.line = line; this.#scopeState.column = column; this.#encodeTag(EncodedTag.ORIGINAL_SCOPE_END).#encodeUnsigned(encodedLine).#encodeUnsigned(encodedColumn).#finishItem(); } #encodeGeneratedRange(range) { this.#encodeGeneratedRangeStart(range); this.#encodeGeneratedRangeBindings(range); this.#encodeGeneratedRangeSubRangeBindings(range); this.#encodeGeneratedRangeCallSite(range); range.children.forEach((child)=>this.#encodeGeneratedRange(child)); this.#encodeGeneratedRangeEnd(range); } #encodeGeneratedRangeStart(range) { const { line, column } = range.start; this.#verifyPositionWithRangeState(line, column); let flags = 0; const encodedLine = line - this.#rangeState.line; let encodedColumn = column - this.#rangeState.column; if (encodedLine > 0) { flags |= GeneratedRangeFlags.HAS_LINE; encodedColumn = column; } this.#rangeState.line = line; this.#rangeState.column = column; let encodedDefinition; if (range.originalScope) { const definitionIdx = this.#scopeToCount.get(range.originalScope); if (definitionIdx === undefined) { throw new Error("Unknown OriginalScope for definition!"); } flags |= GeneratedRangeFlags.HAS_DEFINITION; encodedDefinition = definitionIdx - this.#rangeState.defScopeIdx; this.#rangeState.defScopeIdx = definitionIdx; } if (range.isStackFrame) flags |= GeneratedRangeFlags.IS_STACK_FRAME; if (range.isHidden) flags |= GeneratedRangeFlags.IS_HIDDEN; this.#encodeTag(EncodedTag.GENERATED_RANGE_START).#encodeUnsigned(flags); if (encodedLine > 0) this.#encodeUnsigned(encodedLine); this.#encodeUnsigned(encodedColumn); if (encodedDefinition !== undefined) this.#encodeSigned(encodedDefinition); this.#finishItem(); } #encodeGeneratedRangeSubRangeBindings(range) { if (range.values.length === 0) return; for(let i = 0; i < range.values.length; ++i){ const value = range.values[i]; if (!Array.isArray(value) || value.length <= 1) { continue; } this.#encodeTag(EncodedTag.GENERATED_RANGE_SUBRANGE_BINDING).#encodeUnsigned(i); let lastLine = range.start.line; let lastColumn = range.start.column; for(let j = 1; j < value.length; ++j){ const subRange = value[j]; const prevSubRange = value[j - 1]; if (comparePositions(prevSubRange.to, subRange.from) !== 0) { throw new Error("Sub-range bindings must not have gaps"); } const encodedLine = subRange.from.line - lastLine; const encodedColumn = encodedLine === 0 ? subRange.from.column - lastColumn : subRange.from.column; if (encodedLine < 0 || encodedColumn < 0) { throw new Error("Sub-range bindings must be sorted"); } lastLine = subRange.from.line; lastColumn = subRange.from.column; const binding = subRange.value === undefined ? 0 : this.#resolveNamesIdx(subRange.value) + 1; this.#encodeUnsigned(binding).#encodeUnsigned(encodedLine).#encodeUnsigned(encodedColumn); } this.#finishItem(); } } #encodeGeneratedRangeBindings(range) { if (range.values.length === 0) return; if (!range.originalScope) { throw new Error("Range has binding expressions but no OriginalScope"); } else if (range.originalScope.variables.length !== range.values.length) { throw new Error("Range's binding expressions don't match OriginalScopes' variables"); } this.#encodeTag(EncodedTag.GENERATED_RANGE_BINDINGS); for (const val of range.values){ if (val === null || val === undefined) { this.#encodeUnsigned(0); } else if (typeof val === "string") { this.#encodeUnsigned(this.#resolveNamesIdx(val) + 1); } else { const initialValue = val[0]; const binding = initialValue.value === undefined ? 0 : this.#resolveNamesIdx(initialValue.value) + 1; this.#encodeUnsigned(binding); } } this.#finishItem(); } #encodeGeneratedRangeCallSite(range) { if (!range.callSite) return; const { sourceIndex, line, column } = range.callSite; // TODO: Throw if stackFrame flag is set or OriginalScope index is invalid or no generated range is here. this.#encodeTag(EncodedTag.GENERATED_RANGE_CALL_SITE).#encodeUnsigned(sourceIndex).#encodeUnsigned(line).#encodeUnsigned(column).#finishItem(); } #encodeGeneratedRangeEnd(range) { const { line, column } = range.end; this.#verifyPositionWithRangeState(line, column); let flags = 0; const encodedLine = line - this.#rangeState.line; let encodedColumn = column - this.#rangeState.column; if (encodedLine > 0) { flags |= GeneratedRangeFlags.HAS_LINE; encodedColumn = column; } this.#rangeState.line = line; this.#rangeState.column = column; this.#encodeTag(EncodedTag.GENERATED_RANGE_END); if (encodedLine > 0) this.#encodeUnsigned(encodedLine); this.#encodeUnsigned(encodedColumn).#finishItem(); } #resolveNamesIdx(name) { const index = this.#namesToIndex.get(name); if (index !== undefined) return index; const addedIndex = this.#names.length; this.#names.push(name); this.#namesToIndex.set(name, addedIndex); return addedIndex; } #verifyPositionWithScopeState(line, column) { if (this.#scopeState.line > line || this.#scopeState.line === line && this.#scopeState.column > column) { throw new Error(`Attempting to encode scope item (${line}, ${column}) that precedes the last encoded scope item (${this.#scopeState.line}, ${this.#scopeState.column})`); } } #verifyPositionWithRangeState(line, column) { if (this.#rangeState.line > line || this.#rangeState.line === line && this.#rangeState.column > column) { throw new Error(`Attempting to encode range item that precedes the last encoded range item (${line}, ${column})`); } } #encodeTag(tag) { this.#currentItem += tag; return this; } #encodeSigned(n) { this.#currentItem += encodeSigned(n); return this; } #encodeUnsigned(n) { this.#currentItem += encodeUnsigned(n); return this; } #finishItem() { this.#encodedItems.push(this.#currentItem); this.#currentItem = ""; } } //# sourceMappingURL=encoder.js.map