123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- import { SetArray, put, remove } from './set-array';
- import {
- encode,
- // encodeGeneratedRanges,
- // encodeOriginalScopes
- } from '@jridgewell/sourcemap-codec';
- import { TraceMap, decodedMappings } from '@jridgewell/trace-mapping';
- import {
- COLUMN,
- SOURCES_INDEX,
- SOURCE_LINE,
- SOURCE_COLUMN,
- NAMES_INDEX,
- } from './sourcemap-segment';
- import type { SourceMapInput } from '@jridgewell/trace-mapping';
- // import type { OriginalScope, GeneratedRange } from '@jridgewell/sourcemap-codec';
- import type { SourceMapSegment } from './sourcemap-segment';
- import type {
- DecodedSourceMap,
- EncodedSourceMap,
- Pos,
- Mapping,
- // BindingExpressionRange,
- // OriginalPos,
- // OriginalScopeInfo,
- // GeneratedRangeInfo,
- } from './types';
- export type { DecodedSourceMap, EncodedSourceMap, Mapping };
- export type Options = {
- file?: string | null;
- sourceRoot?: string | null;
- };
- const NO_NAME = -1;
- /**
- * Provides the state to generate a sourcemap.
- */
- export class GenMapping {
- declare private _names: SetArray<string>;
- declare private _sources: SetArray<string>;
- declare private _sourcesContent: (string | null)[];
- declare private _mappings: SourceMapSegment[][];
- // private declare _originalScopes: OriginalScope[][];
- // private declare _generatedRanges: GeneratedRange[];
- declare private _ignoreList: SetArray<number>;
- declare file: string | null | undefined;
- declare sourceRoot: string | null | undefined;
- constructor({ file, sourceRoot }: Options = {}) {
- this._names = new SetArray();
- this._sources = new SetArray();
- this._sourcesContent = [];
- this._mappings = [];
- // this._originalScopes = [];
- // this._generatedRanges = [];
- this.file = file;
- this.sourceRoot = sourceRoot;
- this._ignoreList = new SetArray();
- }
- }
- interface PublicMap {
- _names: GenMapping['_names'];
- _sources: GenMapping['_sources'];
- _sourcesContent: GenMapping['_sourcesContent'];
- _mappings: GenMapping['_mappings'];
- // _originalScopes: GenMapping['_originalScopes'];
- // _generatedRanges: GenMapping['_generatedRanges'];
- _ignoreList: GenMapping['_ignoreList'];
- }
- /**
- * Typescript doesn't allow friend access to private fields, so this just casts the map into a type
- * with public access modifiers.
- */
- function cast(map: unknown): PublicMap {
- return map as any;
- }
- /**
- * A low-level API to associate a generated position with an original source position. Line and
- * column here are 0-based, unlike `addMapping`.
- */
- export function addSegment(
- map: GenMapping,
- genLine: number,
- genColumn: number,
- source?: null,
- sourceLine?: null,
- sourceColumn?: null,
- name?: null,
- content?: null,
- ): void;
- export function addSegment(
- map: GenMapping,
- genLine: number,
- genColumn: number,
- source: string,
- sourceLine: number,
- sourceColumn: number,
- name?: null,
- content?: string | null,
- ): void;
- export function addSegment(
- map: GenMapping,
- genLine: number,
- genColumn: number,
- source: string,
- sourceLine: number,
- sourceColumn: number,
- name: string,
- content?: string | null,
- ): void;
- export function addSegment(
- map: GenMapping,
- genLine: number,
- genColumn: number,
- source?: string | null,
- sourceLine?: number | null,
- sourceColumn?: number | null,
- name?: string | null,
- content?: string | null,
- ): void {
- return addSegmentInternal(
- false,
- map,
- genLine,
- genColumn,
- source,
- sourceLine,
- sourceColumn,
- name,
- content,
- );
- }
- /**
- * A high-level API to associate a generated position with an original source position. Line is
- * 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
- */
- export function addMapping(
- map: GenMapping,
- mapping: {
- generated: Pos;
- source?: null;
- original?: null;
- name?: null;
- content?: null;
- },
- ): void;
- export function addMapping(
- map: GenMapping,
- mapping: {
- generated: Pos;
- source: string;
- original: Pos;
- name?: null;
- content?: string | null;
- },
- ): void;
- export function addMapping(
- map: GenMapping,
- mapping: {
- generated: Pos;
- source: string;
- original: Pos;
- name: string;
- content?: string | null;
- },
- ): void;
- export function addMapping(
- map: GenMapping,
- mapping: {
- generated: Pos;
- source?: string | null;
- original?: Pos | null;
- name?: string | null;
- content?: string | null;
- },
- ): void {
- return addMappingInternal(false, map, mapping as Parameters<typeof addMappingInternal>[2]);
- }
- /**
- * Same as `addSegment`, but will only add the segment if it generates useful information in the
- * resulting map. This only works correctly if segments are added **in order**, meaning you should
- * not add a segment with a lower generated line/column than one that came before.
- */
- export const maybeAddSegment: typeof addSegment = (
- map,
- genLine,
- genColumn,
- source,
- sourceLine,
- sourceColumn,
- name,
- content,
- ) => {
- return addSegmentInternal(
- true,
- map,
- genLine,
- genColumn,
- source,
- sourceLine,
- sourceColumn,
- name,
- content,
- );
- };
- /**
- * Same as `addMapping`, but will only add the mapping if it generates useful information in the
- * resulting map. This only works correctly if mappings are added **in order**, meaning you should
- * not add a mapping with a lower generated line/column than one that came before.
- */
- export const maybeAddMapping: typeof addMapping = (map, mapping) => {
- return addMappingInternal(true, map, mapping as Parameters<typeof addMappingInternal>[2]);
- };
- /**
- * Adds/removes the content of the source file to the source map.
- */
- export function setSourceContent(map: GenMapping, source: string, content: string | null): void {
- const {
- _sources: sources,
- _sourcesContent: sourcesContent,
- // _originalScopes: originalScopes,
- } = cast(map);
- const index = put(sources, source);
- sourcesContent[index] = content;
- // if (index === originalScopes.length) originalScopes[index] = [];
- }
- export function setIgnore(map: GenMapping, source: string, ignore = true) {
- const {
- _sources: sources,
- _sourcesContent: sourcesContent,
- _ignoreList: ignoreList,
- // _originalScopes: originalScopes,
- } = cast(map);
- const index = put(sources, source);
- if (index === sourcesContent.length) sourcesContent[index] = null;
- // if (index === originalScopes.length) originalScopes[index] = [];
- if (ignore) put(ignoreList, index);
- else remove(ignoreList, index);
- }
- /**
- * Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
- * a sourcemap, or to JSON.stringify.
- */
- export function toDecodedMap(map: GenMapping): DecodedSourceMap {
- const {
- _mappings: mappings,
- _sources: sources,
- _sourcesContent: sourcesContent,
- _names: names,
- _ignoreList: ignoreList,
- // _originalScopes: originalScopes,
- // _generatedRanges: generatedRanges,
- } = cast(map);
- removeEmptyFinalLines(mappings);
- return {
- version: 3,
- file: map.file || undefined,
- names: names.array,
- sourceRoot: map.sourceRoot || undefined,
- sources: sources.array,
- sourcesContent,
- mappings,
- // originalScopes,
- // generatedRanges,
- ignoreList: ignoreList.array,
- };
- }
- /**
- * Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
- * a sourcemap, or to JSON.stringify.
- */
- export function toEncodedMap(map: GenMapping): EncodedSourceMap {
- const decoded = toDecodedMap(map);
- return Object.assign({}, decoded, {
- // originalScopes: decoded.originalScopes.map((os) => encodeOriginalScopes(os)),
- // generatedRanges: encodeGeneratedRanges(decoded.generatedRanges as GeneratedRange[]),
- mappings: encode(decoded.mappings as SourceMapSegment[][]),
- });
- }
- /**
- * Constructs a new GenMapping, using the already present mappings of the input.
- */
- export function fromMap(input: SourceMapInput): GenMapping {
- const map = new TraceMap(input);
- const gen = new GenMapping({ file: map.file, sourceRoot: map.sourceRoot });
- putAll(cast(gen)._names, map.names);
- putAll(cast(gen)._sources, map.sources as string[]);
- cast(gen)._sourcesContent = map.sourcesContent || map.sources.map(() => null);
- cast(gen)._mappings = decodedMappings(map) as GenMapping['_mappings'];
- // TODO: implement originalScopes/generatedRanges
- if (map.ignoreList) putAll(cast(gen)._ignoreList, map.ignoreList);
- return gen;
- }
- /**
- * Returns an array of high-level mapping objects for every recorded segment, which could then be
- * passed to the `source-map` library.
- */
- export function allMappings(map: GenMapping): Mapping[] {
- const out: Mapping[] = [];
- const { _mappings: mappings, _sources: sources, _names: names } = cast(map);
- for (let i = 0; i < mappings.length; i++) {
- const line = mappings[i];
- for (let j = 0; j < line.length; j++) {
- const seg = line[j];
- const generated = { line: i + 1, column: seg[COLUMN] };
- let source: string | undefined = undefined;
- let original: Pos | undefined = undefined;
- let name: string | undefined = undefined;
- if (seg.length !== 1) {
- source = sources.array[seg[SOURCES_INDEX]];
- original = { line: seg[SOURCE_LINE] + 1, column: seg[SOURCE_COLUMN] };
- if (seg.length === 5) name = names.array[seg[NAMES_INDEX]];
- }
- out.push({ generated, source, original, name } as Mapping);
- }
- }
- return out;
- }
- // This split declaration is only so that terser can elminiate the static initialization block.
- function addSegmentInternal<S extends string | null | undefined>(
- skipable: boolean,
- map: GenMapping,
- genLine: number,
- genColumn: number,
- source: S,
- sourceLine: S extends string ? number : null | undefined,
- sourceColumn: S extends string ? number : null | undefined,
- name: S extends string ? string | null | undefined : null | undefined,
- content: S extends string ? string | null | undefined : null | undefined,
- ): void {
- const {
- _mappings: mappings,
- _sources: sources,
- _sourcesContent: sourcesContent,
- _names: names,
- // _originalScopes: originalScopes,
- } = cast(map);
- const line = getIndex(mappings, genLine);
- const index = getColumnIndex(line, genColumn);
- if (!source) {
- if (skipable && skipSourceless(line, index)) return;
- return insert(line, index, [genColumn]);
- }
- // Sigh, TypeScript can't figure out sourceLine and sourceColumn aren't nullish if source
- // isn't nullish.
- assert<number>(sourceLine);
- assert<number>(sourceColumn);
- const sourcesIndex = put(sources, source);
- const namesIndex = name ? put(names, name) : NO_NAME;
- if (sourcesIndex === sourcesContent.length) sourcesContent[sourcesIndex] = content ?? null;
- // if (sourcesIndex === originalScopes.length) originalScopes[sourcesIndex] = [];
- if (skipable && skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex)) {
- return;
- }
- return insert(
- line,
- index,
- name
- ? [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]
- : [genColumn, sourcesIndex, sourceLine, sourceColumn],
- );
- }
- function assert<T>(_val: unknown): asserts _val is T {
- // noop.
- }
- function getIndex<T>(arr: T[][], index: number): T[] {
- for (let i = arr.length; i <= index; i++) {
- arr[i] = [];
- }
- return arr[index];
- }
- function getColumnIndex(line: SourceMapSegment[], genColumn: number): number {
- let index = line.length;
- for (let i = index - 1; i >= 0; index = i--) {
- const current = line[i];
- if (genColumn >= current[COLUMN]) break;
- }
- return index;
- }
- function insert<T>(array: T[], index: number, value: T) {
- for (let i = array.length; i > index; i--) {
- array[i] = array[i - 1];
- }
- array[index] = value;
- }
- function removeEmptyFinalLines(mappings: SourceMapSegment[][]) {
- const { length } = mappings;
- let len = length;
- for (let i = len - 1; i >= 0; len = i, i--) {
- if (mappings[i].length > 0) break;
- }
- if (len < length) mappings.length = len;
- }
- function putAll<T extends string | number>(setarr: SetArray<T>, array: T[]) {
- for (let i = 0; i < array.length; i++) put(setarr, array[i]);
- }
- function skipSourceless(line: SourceMapSegment[], index: number): boolean {
- // The start of a line is already sourceless, so adding a sourceless segment to the beginning
- // doesn't generate any useful information.
- if (index === 0) return true;
- const prev = line[index - 1];
- // If the previous segment is also sourceless, then adding another sourceless segment doesn't
- // genrate any new information. Else, this segment will end the source/named segment and point to
- // a sourceless position, which is useful.
- return prev.length === 1;
- }
- function skipSource(
- line: SourceMapSegment[],
- index: number,
- sourcesIndex: number,
- sourceLine: number,
- sourceColumn: number,
- namesIndex: number,
- ): boolean {
- // A source/named segment at the start of a line gives position at that genColumn
- if (index === 0) return false;
- const prev = line[index - 1];
- // If the previous segment is sourceless, then we're transitioning to a source.
- if (prev.length === 1) return false;
- // If the previous segment maps to the exact same source position, then this segment doesn't
- // provide any new position information.
- return (
- sourcesIndex === prev[SOURCES_INDEX] &&
- sourceLine === prev[SOURCE_LINE] &&
- sourceColumn === prev[SOURCE_COLUMN] &&
- namesIndex === (prev.length === 5 ? prev[NAMES_INDEX] : NO_NAME)
- );
- }
- function addMappingInternal<S extends string | null | undefined>(
- skipable: boolean,
- map: GenMapping,
- mapping: {
- generated: Pos;
- source: S;
- original: S extends string ? Pos : null | undefined;
- name: S extends string ? string | null | undefined : null | undefined;
- content: S extends string ? string | null | undefined : null | undefined;
- },
- ) {
- const { generated, source, original, name, content } = mapping;
- if (!source) {
- return addSegmentInternal(
- skipable,
- map,
- generated.line - 1,
- generated.column,
- null,
- null,
- null,
- null,
- null,
- );
- }
- assert<Pos>(original);
- return addSegmentInternal(
- skipable,
- map,
- generated.line - 1,
- generated.column,
- source as string,
- original.line - 1,
- original.column,
- name,
- content,
- );
- }
- /*
- export function addOriginalScope(
- map: GenMapping,
- data: {
- start: Pos;
- end: Pos;
- source: string;
- kind: string;
- name?: string;
- variables?: string[];
- },
- ): OriginalScopeInfo {
- const { start, end, source, kind, name, variables } = data;
- const {
- _sources: sources,
- _sourcesContent: sourcesContent,
- _originalScopes: originalScopes,
- _names: names,
- } = cast(map);
- const index = put(sources, source);
- if (index === sourcesContent.length) sourcesContent[index] = null;
- if (index === originalScopes.length) originalScopes[index] = [];
- const kindIndex = put(names, kind);
- const scope: OriginalScope = name
- ? [start.line - 1, start.column, end.line - 1, end.column, kindIndex, put(names, name)]
- : [start.line - 1, start.column, end.line - 1, end.column, kindIndex];
- if (variables) {
- scope.vars = variables.map((v) => put(names, v));
- }
- const len = originalScopes[index].push(scope);
- return [index, len - 1, variables];
- }
- */
- // Generated Ranges
- /*
- export function addGeneratedRange(
- map: GenMapping,
- data: {
- start: Pos;
- isScope: boolean;
- originalScope?: OriginalScopeInfo;
- callsite?: OriginalPos;
- },
- ): GeneratedRangeInfo {
- const { start, isScope, originalScope, callsite } = data;
- const {
- _originalScopes: originalScopes,
- _sources: sources,
- _sourcesContent: sourcesContent,
- _generatedRanges: generatedRanges,
- } = cast(map);
- const range: GeneratedRange = [
- start.line - 1,
- start.column,
- 0,
- 0,
- originalScope ? originalScope[0] : -1,
- originalScope ? originalScope[1] : -1,
- ];
- if (originalScope?.[2]) {
- range.bindings = originalScope[2].map(() => [[-1]]);
- }
- if (callsite) {
- const index = put(sources, callsite.source);
- if (index === sourcesContent.length) sourcesContent[index] = null;
- if (index === originalScopes.length) originalScopes[index] = [];
- range.callsite = [index, callsite.line - 1, callsite.column];
- }
- if (isScope) range.isScope = true;
- generatedRanges.push(range);
- return [range, originalScope?.[2]];
- }
- export function setEndPosition(range: GeneratedRangeInfo, pos: Pos) {
- range[0][2] = pos.line - 1;
- range[0][3] = pos.column;
- }
- export function addBinding(
- map: GenMapping,
- range: GeneratedRangeInfo,
- variable: string,
- expression: string | BindingExpressionRange,
- ) {
- const { _names: names } = cast(map);
- const bindings = (range[0].bindings ||= []);
- const vars = range[1];
- const index = vars!.indexOf(variable);
- const binding = getIndex(bindings, index);
- if (typeof expression === 'string') binding[0] = [put(names, expression)];
- else {
- const { start } = expression;
- binding.push([put(names, expression.expression), start.line - 1, start.column]);
- }
- }
- */
|