mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-03 02:21:05 +08:00
https://typescript-eslint.io/rules/only-throw-error/ Bug: 397260638 Change-Id: I2dbdd6006ce21a99818fd6de2e4d727894194e35 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6277290 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Nikolay Vitkov <nvitkov@chromium.org>
367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
// Copyright 2021 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 * as SDK from '../core/sdk/sdk.js';
|
|
|
|
const base64Digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
|
|
export function encodeVlq(n: number): string {
|
|
// Set the sign bit as the least significant bit.
|
|
n = n >= 0 ? 2 * n : 1 - 2 * n;
|
|
// Encode into a base64 run.
|
|
let result = '';
|
|
while (true) {
|
|
// Extract the lowest 5 bits and remove them from the number.
|
|
const digit = n & 0x1f;
|
|
n >>= 5;
|
|
// Is there anything more left to encode?
|
|
if (n === 0) {
|
|
// We are done encoding, finish the run.
|
|
result += base64Digits[digit];
|
|
break;
|
|
} else {
|
|
// There is still more encode, so add the digit and the continuation bit.
|
|
result += base64Digits[0x20 + digit];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function encodeVlqList(list: number[]) {
|
|
return list.map(encodeVlq).join('');
|
|
}
|
|
|
|
// Encode array mappings of the form "compiledLine:compiledColumn => srcFile:srcLine:srcColumn@name"
|
|
// as a source map.
|
|
export function encodeSourceMap(textMap: string[], sourceRoot?: string): SDK.SourceMap.SourceMapV3Object {
|
|
let mappings = '';
|
|
const sources: string[] = [];
|
|
const names: string[] = [];
|
|
let sourcesContent: Array<null|string>|undefined;
|
|
|
|
const state = {
|
|
line: -1,
|
|
column: 0,
|
|
srcFile: 0,
|
|
srcLine: 0,
|
|
srcColumn: 0,
|
|
srcName: 0,
|
|
};
|
|
|
|
for (const mapping of textMap) {
|
|
let match = mapping.match(/^(\d+):(\d+)(?:\s*=>\s*([^:]+):(\d+):(\d+)(?:@(\S+))?)?$/);
|
|
if (!match) {
|
|
match = mapping.match(/^([^:]+):\s*(.+)$/);
|
|
if (!match) {
|
|
throw new Error(`Cannot parse mapping "${mapping}"`);
|
|
}
|
|
(sourcesContent = sourcesContent ?? [])[getOrAddString(sources, match[1])] = match[2];
|
|
continue;
|
|
}
|
|
|
|
const lastState = Object.assign({}, state);
|
|
state.line = Number(match[1]);
|
|
state.column = Number(match[2]);
|
|
const hasSource = match[3] !== undefined;
|
|
const hasName = hasSource && (match[6] !== undefined);
|
|
if (hasSource) {
|
|
state.srcFile = getOrAddString(sources, match[3]);
|
|
state.srcLine = Number(match[4]);
|
|
state.srcColumn = Number(match[5]);
|
|
if (hasName) {
|
|
state.srcName = getOrAddString(names, match[6]);
|
|
}
|
|
}
|
|
|
|
if (state.line < lastState.line) {
|
|
throw new Error('Line numbers must be increasing');
|
|
}
|
|
|
|
const isNewLine = state.line !== lastState.line;
|
|
|
|
if (isNewLine) {
|
|
// Fixup for the first line mapping.
|
|
if (lastState.line === -1) {
|
|
lastState.line = 0;
|
|
}
|
|
// Insert semicolons for all the new lines.
|
|
mappings += ';'.repeat(state.line - lastState.line);
|
|
// Reset the compiled code column counter.
|
|
lastState.column = 0;
|
|
} else {
|
|
mappings += ',';
|
|
}
|
|
|
|
// Encode the mapping and add it to the list of mappings.
|
|
const toEncode = [state.column - lastState.column];
|
|
if (hasSource) {
|
|
toEncode.push(
|
|
state.srcFile - lastState.srcFile, state.srcLine - lastState.srcLine, state.srcColumn - lastState.srcColumn);
|
|
if (hasName) {
|
|
toEncode.push(state.srcName - lastState.srcName);
|
|
}
|
|
}
|
|
mappings += encodeVlqList(toEncode);
|
|
}
|
|
|
|
const sourceMapV3: SDK.SourceMap.SourceMapV3 = {version: 3, mappings, sources, names};
|
|
if (sourceRoot !== undefined) {
|
|
sourceMapV3.sourceRoot = sourceRoot;
|
|
}
|
|
if (sourcesContent !== undefined) {
|
|
for (let i = 0; i < sources.length; ++i) {
|
|
if (typeof sourcesContent[i] !== 'string') {
|
|
sourcesContent[i] = null;
|
|
}
|
|
}
|
|
sourceMapV3.sourcesContent = sourcesContent;
|
|
}
|
|
return sourceMapV3;
|
|
|
|
function getOrAddString(array: string[], s: string) {
|
|
const index = array.indexOf(s);
|
|
if (index >= 0) {
|
|
return index;
|
|
}
|
|
array.push(s);
|
|
return array.length - 1;
|
|
}
|
|
}
|
|
|
|
export class OriginalScopeBuilder {
|
|
#encodedScope = '';
|
|
#lastLine = 0;
|
|
#lastKind = 0;
|
|
|
|
readonly #names: string[];
|
|
|
|
/** The 'names' field of the SourceMap. The builder will modify it. */
|
|
constructor(names: string[]) {
|
|
this.#names = names;
|
|
}
|
|
|
|
start(
|
|
line: number, column: number,
|
|
options?: {name?: string, kind?: string, isStackFrame?: boolean, variables?: string[]}): this {
|
|
if (this.#encodedScope !== '') {
|
|
this.#encodedScope += ',';
|
|
}
|
|
|
|
const lineDiff = line - this.#lastLine;
|
|
this.#lastLine = line;
|
|
let flags = 0;
|
|
const nameIdxAndKindIdx: number[] = [];
|
|
|
|
if (options?.name) {
|
|
flags |= SDK.SourceMapScopes.EncodedOriginalScopeFlag.HAS_NAME;
|
|
nameIdxAndKindIdx.push(this.#nameIdx(options.name));
|
|
}
|
|
if (options?.kind) {
|
|
flags |= SDK.SourceMapScopes.EncodedOriginalScopeFlag.HAS_KIND;
|
|
nameIdxAndKindIdx.push(this.#encodeKind(options?.kind));
|
|
}
|
|
if (options?.isStackFrame) {
|
|
flags |= SDK.SourceMapScopes.EncodedOriginalScopeFlag.IS_STACK_FRAME;
|
|
}
|
|
|
|
this.#encodedScope += encodeVlqList([lineDiff, column, flags, ...nameIdxAndKindIdx]);
|
|
|
|
if (options?.variables) {
|
|
this.#encodedScope += encodeVlqList(options.variables.map(variable => this.#nameIdx(variable)));
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
end(line: number, column: number): this {
|
|
if (this.#encodedScope !== '') {
|
|
this.#encodedScope += ',';
|
|
}
|
|
|
|
const lineDiff = line - this.#lastLine;
|
|
this.#lastLine = line;
|
|
this.#encodedScope += encodeVlqList([lineDiff, column]);
|
|
|
|
return this;
|
|
}
|
|
|
|
build(): string {
|
|
const result = this.#encodedScope;
|
|
this.#lastLine = 0;
|
|
this.#encodedScope = '';
|
|
return result;
|
|
}
|
|
|
|
#encodeKind(kind: string): number {
|
|
const kindIdx = this.#nameIdx(kind);
|
|
const encodedIdx = kindIdx - this.#lastKind;
|
|
this.#lastKind = kindIdx;
|
|
return encodedIdx;
|
|
}
|
|
|
|
#nameIdx(name: string): number {
|
|
let idx = this.#names.indexOf(name);
|
|
if (idx < 0) {
|
|
idx = this.#names.length;
|
|
this.#names.push(name);
|
|
}
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
export class GeneratedRangeBuilder {
|
|
#encodedRange = '';
|
|
#state = {
|
|
line: 0,
|
|
column: 0,
|
|
defSourceIdx: 0,
|
|
defScopeIdx: 0,
|
|
callsiteSourceIdx: 0,
|
|
callsiteLine: 0,
|
|
callsiteColumn: 0,
|
|
};
|
|
|
|
readonly #names: string[];
|
|
|
|
/** The 'names' field of the SourceMap. The builder will modify it. */
|
|
constructor(names: string[]) {
|
|
this.#names = names;
|
|
}
|
|
|
|
start(line: number, column: number, options?: {
|
|
isStackFrame?: boolean,
|
|
isHidden?: boolean,
|
|
definition?: {sourceIdx: number, scopeIdx: number},
|
|
callsite?: {sourceIdx: number, line: number, column: number},
|
|
bindings?: Array<string|undefined|Array<{line: number, column: number, name: string|undefined}>>,
|
|
}): this {
|
|
this.#emitLineSeparator(line);
|
|
this.#emitItemSepratorIfRequired();
|
|
|
|
const emittedColumn = column - (this.#state.line === line ? this.#state.column : 0);
|
|
this.#encodedRange += encodeVlq(emittedColumn);
|
|
|
|
this.#state.line = line;
|
|
this.#state.column = column;
|
|
|
|
let flags = 0;
|
|
if (options?.definition) {
|
|
flags |= SDK.SourceMapScopes.EncodedGeneratedRangeFlag.HAS_DEFINITION;
|
|
}
|
|
if (options?.callsite) {
|
|
flags |= SDK.SourceMapScopes.EncodedGeneratedRangeFlag.HAS_CALLSITE;
|
|
}
|
|
if (options?.isStackFrame) {
|
|
flags |= SDK.SourceMapScopes.EncodedGeneratedRangeFlag.IS_STACK_FRAME;
|
|
}
|
|
if (options?.isHidden) {
|
|
flags |= SDK.SourceMapScopes.EncodedGeneratedRangeFlag.IS_HIDDEN;
|
|
}
|
|
this.#encodedRange += encodeVlq(flags);
|
|
|
|
if (options?.definition) {
|
|
const {sourceIdx, scopeIdx} = options.definition;
|
|
this.#encodedRange += encodeVlq(sourceIdx - this.#state.defSourceIdx);
|
|
|
|
const emittedScopeIdx = scopeIdx - (this.#state.defSourceIdx === sourceIdx ? this.#state.defScopeIdx : 0);
|
|
this.#encodedRange += encodeVlq(emittedScopeIdx);
|
|
|
|
this.#state.defSourceIdx = sourceIdx;
|
|
this.#state.defScopeIdx = scopeIdx;
|
|
}
|
|
|
|
if (options?.callsite) {
|
|
const {sourceIdx, line, column} = options.callsite;
|
|
this.#encodedRange += encodeVlq(sourceIdx - this.#state.callsiteSourceIdx);
|
|
|
|
const emittedLine = line - (this.#state.callsiteSourceIdx === sourceIdx ? this.#state.callsiteLine : 0);
|
|
this.#encodedRange += encodeVlq(emittedLine);
|
|
|
|
const emittedColumn = column - (this.#state.callsiteLine === line ? this.#state.callsiteColumn : 0);
|
|
this.#encodedRange += encodeVlq(emittedColumn);
|
|
|
|
this.#state.callsiteSourceIdx = sourceIdx;
|
|
this.#state.callsiteLine = line;
|
|
this.#state.callsiteColumn = column;
|
|
}
|
|
|
|
for (const bindings of options?.bindings ?? []) {
|
|
if (bindings === undefined || typeof bindings === 'string') {
|
|
this.#encodedRange += encodeVlq(this.#nameIdx(bindings));
|
|
continue;
|
|
}
|
|
|
|
this.#encodedRange += encodeVlq(-bindings.length);
|
|
this.#encodedRange += encodeVlq(this.#nameIdx(bindings[0].name));
|
|
if (bindings[0].line !== line || bindings[0].column !== column) {
|
|
throw new Error('First binding line/column must match the range start line/column');
|
|
}
|
|
|
|
for (let i = 1; i < bindings.length; ++i) {
|
|
const {line, column, name} = bindings[i];
|
|
const emittedLine = line - bindings[i - 1].line;
|
|
const emittedColumn = column - (line === bindings[i - 1].line ? bindings[i - 1].column : 0);
|
|
this.#encodedRange += encodeVlq(emittedLine);
|
|
this.#encodedRange += encodeVlq(emittedColumn);
|
|
this.#encodedRange += encodeVlq(this.#nameIdx(name));
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
end(line: number, column: number): this {
|
|
this.#emitLineSeparator(line);
|
|
this.#emitItemSepratorIfRequired();
|
|
|
|
const emittedColumn = column - (this.#state.line === line ? this.#state.column : 0);
|
|
this.#encodedRange += encodeVlq(emittedColumn);
|
|
|
|
this.#state.line = line;
|
|
this.#state.column = column;
|
|
|
|
return this;
|
|
}
|
|
|
|
#emitLineSeparator(line: number): void {
|
|
for (let i = this.#state.line; i < line; ++i) {
|
|
this.#encodedRange += ';';
|
|
}
|
|
}
|
|
|
|
#emitItemSepratorIfRequired(): void {
|
|
if (this.#encodedRange !== '' && this.#encodedRange[this.#encodedRange.length - 1] !== ';') {
|
|
this.#encodedRange += ',';
|
|
}
|
|
}
|
|
|
|
#nameIdx(name?: string): number {
|
|
if (name === undefined) {
|
|
return -1;
|
|
}
|
|
|
|
let idx = this.#names.indexOf(name);
|
|
if (idx < 0) {
|
|
idx = this.#names.length;
|
|
this.#names.push(name);
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
build(): string {
|
|
const result = this.#encodedRange;
|
|
this.#state = {
|
|
line: 0,
|
|
column: 0,
|
|
defSourceIdx: 0,
|
|
defScopeIdx: 0,
|
|
callsiteSourceIdx: 0,
|
|
callsiteLine: 0,
|
|
callsiteColumn: 0,
|
|
};
|
|
this.#encodedRange = '';
|
|
return result;
|
|
}
|
|
}
|