UNPKG

chrome-devtools-frontend

Version:
438 lines • 14 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 { GeneratedRangeFlags, OriginalScopeFlags, Tag } from "../codec.js"; import { TokenIterator } from "../vlq.js"; /** * The mode decides how well-formed the encoded scopes have to be, to be accepted by the decoder. * * LAX is the default and is much more lenient. It's still best effort though and the decoder doesn't * implement any error recovery: e.g. superfluous "start" items can lead to whole trees being omitted. * * STRICT mode will throw in the following situations: * * - Encountering ORIGINAL_SCOPE_END, or GENERATED_RANGE_END items that don't have matching *_START items. * - Encountering ORIGINAL_SCOPE_VARIABLES items outside a surrounding scope START/END. * - Encountering GENERATED_RANGE_BINDINGS items outside a surrounding range START/END. * - Miss-matches between the number of variables in a scope vs the number of value expressions in the ranges. * - Out-of-bound indices into the "names" array. */ export var DecodeMode = /*#__PURE__*/ function(DecodeMode) { DecodeMode[DecodeMode["STRICT"] = 1] = "STRICT"; DecodeMode[DecodeMode["LAX"] = 2] = "LAX"; return DecodeMode; }({}); export const DEFAULT_DECODE_OPTIONS = { mode: 2, generatedOffset: { line: 0, column: 0 } }; export function decode(sourceMap, options = DEFAULT_DECODE_OPTIONS) { const opts = { ...DEFAULT_DECODE_OPTIONS, ...options }; if ("sections" in sourceMap) { return decodeIndexMap(sourceMap, { ...opts, generatedOffset: { line: 0, column: 0 } }); } return decodeMap(sourceMap, opts); } function decodeMap(sourceMap, options) { if (!sourceMap.scopes || !sourceMap.names) return { scopes: [], ranges: [] }; return new Decoder(sourceMap.scopes, sourceMap.names, options).decode(); } function decodeIndexMap(sourceMap, options) { const scopeInfo = { scopes: [], ranges: [] }; for (const section of sourceMap.sections){ const { scopes, ranges } = decode(section.map, { ...options, generatedOffset: section.offset }); for (const scope of scopes)scopeInfo.scopes.push(scope); for (const range of ranges)scopeInfo.ranges.push(range); } return scopeInfo; } const DEFAULT_SCOPE_STATE = { line: 0, column: 0, name: 0, kind: 0, variable: 0 }; const DEFAULT_RANGE_STATE = { line: 0, column: 0, defScopeIdx: 0 }; class Decoder { #encodedScopes; #names; #mode; #scopes = []; #ranges = []; #scopeState = { ...DEFAULT_SCOPE_STATE }; #rangeState = { ...DEFAULT_RANGE_STATE }; #scopeStack = []; #rangeStack = []; #flatOriginalScopes = []; #subRangeBindingsForRange = new Map(); constructor(scopes, names, options){ this.#encodedScopes = scopes; this.#names = names; this.#mode = options.mode; this.#rangeState.line = options.generatedOffset.line; this.#rangeState.column = options.generatedOffset.column; } decode() { const iter = new TokenIterator(this.#encodedScopes); while(iter.hasNext()){ if (iter.peek() === ",") { iter.nextChar(); // Consume ",". this.#scopes.push(null); // Add an EmptyItem; continue; } const tag = iter.nextUnsignedVLQ(); switch(tag){ case Tag.ORIGINAL_SCOPE_START: { const item = { flags: iter.nextUnsignedVLQ(), line: iter.nextUnsignedVLQ(), column: iter.nextUnsignedVLQ() }; if (item.flags & OriginalScopeFlags.HAS_NAME) { item.nameIdx = iter.nextSignedVLQ(); } if (item.flags & OriginalScopeFlags.HAS_KIND) { item.kindIdx = iter.nextSignedVLQ(); } this.#handleOriginalScopeStartItem(item); break; } case Tag.ORIGINAL_SCOPE_VARIABLES: { const variableIdxs = []; while(iter.hasNext() && iter.peek() !== ","){ variableIdxs.push(iter.nextSignedVLQ()); } this.#handleOriginalScopeVariablesItem(variableIdxs); break; } case Tag.ORIGINAL_SCOPE_END: { this.#handleOriginalScopeEndItem(iter.nextUnsignedVLQ(), iter.nextUnsignedVLQ()); break; } case Tag.GENERATED_RANGE_START: { const flags = iter.nextUnsignedVLQ(); const line = flags & GeneratedRangeFlags.HAS_LINE ? iter.nextUnsignedVLQ() : undefined; const column = iter.nextUnsignedVLQ(); const definitionIdx = flags & GeneratedRangeFlags.HAS_DEFINITION ? iter.nextSignedVLQ() : undefined; this.#handleGeneratedRangeStartItem({ flags, line, column, definitionIdx }); break; } case Tag.GENERATED_RANGE_END: { const lineOrColumn = iter.nextUnsignedVLQ(); const maybeColumn = iter.hasNext() && iter.peek() !== "," ? iter.nextUnsignedVLQ() : undefined; if (maybeColumn !== undefined) { this.#handleGeneratedRangeEndItem(lineOrColumn, maybeColumn); } else { this.#handleGeneratedRangeEndItem(0, lineOrColumn); } break; } case Tag.GENERATED_RANGE_BINDINGS: { const valueIdxs = []; while(iter.hasNext() && iter.peek() !== ","){ valueIdxs.push(iter.nextUnsignedVLQ()); } this.#handleGeneratedRangeBindingsItem(valueIdxs); break; } case Tag.GENERATED_RANGE_SUBRANGE_BINDING: { const variableIndex = iter.nextUnsignedVLQ(); const bindings = []; while(iter.hasNext() && iter.peek() !== ","){ bindings.push([ iter.nextUnsignedVLQ(), iter.nextUnsignedVLQ(), iter.nextUnsignedVLQ() ]); } this.#recordGeneratedSubRangeBindingItem(variableIndex, bindings); break; } case Tag.GENERATED_RANGE_CALL_SITE: { this.#handleGeneratedRangeCallSite(iter.nextUnsignedVLQ(), iter.nextUnsignedVLQ(), iter.nextUnsignedVLQ()); break; } } // Consume any trailing VLQ and the the "," while(iter.hasNext() && iter.peek() !== ",")iter.nextUnsignedVLQ(); if (iter.hasNext()) iter.nextChar(); } if (iter.currentChar() === ",") { // Handle trailing EmptyItem. this.#scopes.push(null); } if (this.#scopeStack.length > 0) { this.#throwInStrictMode("Encountered ORIGINAL_SCOPE_START without matching END!"); } if (this.#rangeStack.length > 0) { this.#throwInStrictMode("Encountered GENERATED_RANGE_START without matching END!"); } const info = { scopes: this.#scopes, ranges: this.#ranges }; this.#scopes = []; this.#ranges = []; this.#flatOriginalScopes = []; return info; } #throwInStrictMode(message) { if (this.#mode === 1) throw new Error(message); } #handleOriginalScopeStartItem(item) { this.#scopeState.line += item.line; if (item.line === 0) { this.#scopeState.column += item.column; } else { this.#scopeState.column = item.column; } const scope = { start: { line: this.#scopeState.line, column: this.#scopeState.column }, end: { line: this.#scopeState.line, column: this.#scopeState.column }, isStackFrame: false, variables: [], children: [] }; if (item.nameIdx !== undefined) { this.#scopeState.name += item.nameIdx; scope.name = this.#resolveName(this.#scopeState.name); } if (item.kindIdx !== undefined) { this.#scopeState.kind += item.kindIdx; scope.kind = this.#resolveName(this.#scopeState.kind); } scope.isStackFrame = Boolean(item.flags & OriginalScopeFlags.IS_STACK_FRAME); this.#scopeStack.push(scope); this.#flatOriginalScopes.push(scope); } #handleOriginalScopeVariablesItem(variableIdxs) { const scope = this.#scopeStack.at(-1); if (!scope) { this.#throwInStrictMode("Encountered ORIGINAL_SCOPE_VARIABLES without surrounding ORIGINAL_SCOPE_START"); return; } for (const variableIdx of variableIdxs){ this.#scopeState.variable += variableIdx; scope.variables.push(this.#resolveName(this.#scopeState.variable)); } } #handleOriginalScopeEndItem(line, column) { this.#scopeState.line += line; if (line === 0) { this.#scopeState.column += column; } else { this.#scopeState.column = column; } const scope = this.#scopeStack.pop(); if (!scope) { this.#throwInStrictMode("Encountered ORIGINAL_SCOPE_END without matching ORIGINAL_SCOPE_START!"); return; } scope.end = { line: this.#scopeState.line, column: this.#scopeState.column }; if (this.#scopeStack.length > 0) { const parent = this.#scopeStack.at(-1); scope.parent = parent; parent.children.push(scope); } else { this.#scopes.push(scope); this.#scopeState.line = 0; this.#scopeState.column = 0; } } #handleGeneratedRangeStartItem(item) { if (item.line !== undefined) { this.#rangeState.line += item.line; this.#rangeState.column = item.column; } else { this.#rangeState.column += item.column; } const range = { start: { line: this.#rangeState.line, column: this.#rangeState.column }, end: { line: this.#rangeState.line, column: this.#rangeState.column }, isStackFrame: Boolean(item.flags & GeneratedRangeFlags.IS_STACK_FRAME), isHidden: Boolean(item.flags & GeneratedRangeFlags.IS_HIDDEN), values: [], children: [] }; if (item.definitionIdx !== undefined) { this.#rangeState.defScopeIdx += item.definitionIdx; if (this.#rangeState.defScopeIdx < 0 || this.#rangeState.defScopeIdx >= this.#flatOriginalScopes.length) { this.#throwInStrictMode("Invalid definition scope index"); } else { range.originalScope = this.#flatOriginalScopes[this.#rangeState.defScopeIdx]; } } this.#rangeStack.push(range); this.#subRangeBindingsForRange.clear(); } #handleGeneratedRangeBindingsItem(valueIdxs) { const range = this.#rangeStack.at(-1); if (!range) { this.#throwInStrictMode("Encountered GENERATED_RANGE_BINDINGS without surrounding GENERATED_RANGE_START"); return; } for (const valueIdx of valueIdxs){ if (valueIdx === 0) { range.values.push(null); } else { range.values.push(this.#resolveName(valueIdx - 1)); } } } #recordGeneratedSubRangeBindingItem(variableIndex, bindings) { if (this.#subRangeBindingsForRange.has(variableIndex)) { this.#throwInStrictMode("Encountered multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable"); return; } this.#subRangeBindingsForRange.set(variableIndex, bindings); } #handleGeneratedRangeCallSite(sourceIndex, line, column) { const range = this.#rangeStack.at(-1); if (!range) { this.#throwInStrictMode("Encountered GENERATED_RANGE_CALL_SITE without surrounding GENERATED_RANGE_START"); return; } range.callSite = { sourceIndex, line, column }; } #handleGeneratedRangeEndItem(line, column) { if (line !== 0) { this.#rangeState.line += line; this.#rangeState.column = column; } else { this.#rangeState.column += column; } const range = this.#rangeStack.pop(); if (!range) { this.#throwInStrictMode("Encountered GENERATED_RANGE_END without matching GENERATED_RANGE_START!"); return; } range.end = { line: this.#rangeState.line, column: this.#rangeState.column }; this.#handleGeneratedRangeSubRangeBindings(range); if (this.#rangeStack.length > 0) { const parent = this.#rangeStack.at(-1); range.parent = parent; parent.children.push(range); } else { this.#ranges.push(range); } } #handleGeneratedRangeSubRangeBindings(range) { for (const [variableIndex, bindings] of this.#subRangeBindingsForRange){ const value = range.values[variableIndex]; const subRanges = []; range.values[variableIndex] = subRanges; let lastLine = range.start.line; let lastColumn = range.start.column; subRanges.push({ from: { line: lastLine, column: lastColumn }, to: { line: 0, column: 0 }, value: value }); for (const [binding, line, column] of bindings){ lastLine += line; if (line === 0) { lastColumn += column; } else { lastColumn = column; } subRanges.push({ from: { line: lastLine, column: lastColumn }, to: { line: 0, column: 0 }, value: binding === 0 ? undefined : this.#resolveName(binding - 1) }); } } for (const value of range.values){ if (Array.isArray(value)) { const subRanges = value; for(let i = 0; i < subRanges.length - 1; ++i){ subRanges[i].to = subRanges[i + 1].from; } subRanges[subRanges.length - 1].to = range.end; } } } #resolveName(index) { if (index < 0 || index >= this.#names.length) { this.#throwInStrictMode("Illegal index into the 'names' array"); } return this.#names[index] ?? ""; } } //# sourceMappingURL=decode.js.map