source-map-tree.ts•4.82 kB
import { GenMapping, maybeAddSegment, setIgnore, setSourceContent } from '@jridgewell/gen-mapping';
import { traceSegment, decodedMappings } from '@jridgewell/trace-mapping';
import type { TraceMap } from '@jridgewell/trace-mapping';
export type SourceMapSegmentObject = {
column: number;
line: number;
name: string;
source: string;
content: string | null;
ignore: boolean;
};
export type OriginalSource = {
map: null;
sources: Sources[];
source: string;
content: string | null;
ignore: boolean;
};
export type MapSource = {
map: TraceMap;
sources: Sources[];
source: string;
content: null;
ignore: false;
};
export type Sources = OriginalSource | MapSource;
const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false);
const EMPTY_SOURCES: Sources[] = [];
function SegmentObject(
source: string,
line: number,
column: number,
name: string,
content: string | null,
ignore: boolean,
): SourceMapSegmentObject {
return { source, line, column, name, content, ignore };
}
function Source(
map: TraceMap,
sources: Sources[],
source: '',
content: null,
ignore: false,
): MapSource;
function Source(
map: null,
sources: Sources[],
source: string,
content: string | null,
ignore: boolean,
): OriginalSource;
function Source(
map: TraceMap | null,
sources: Sources[],
source: string | '',
content: string | null,
ignore: boolean,
): Sources {
return {
map,
sources,
source,
content,
ignore,
} as any;
}
/**
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
* (which may themselves be SourceMapTrees).
*/
export function MapSource(map: TraceMap, sources: Sources[]): MapSource {
return Source(map, sources, '', null, false);
}
/**
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
* segment tracing ends at the `OriginalSource`.
*/
export function OriginalSource(
source: string,
content: string | null,
ignore: boolean,
): OriginalSource {
return Source(null, EMPTY_SOURCES, source, content, ignore);
}
/**
* traceMappings is only called on the root level SourceMapTree, and begins the process of
* resolving each mapping in terms of the original source files.
*/
export function traceMappings(tree: MapSource): GenMapping {
// TODO: Eventually support sourceRoot, which has to be removed because the sources are already
// fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
const gen = new GenMapping({ file: tree.map.file });
const { sources: rootSources, map } = tree;
const rootNames = map.names;
const rootMappings = decodedMappings(map);
for (let i = 0; i < rootMappings.length; i++) {
const segments = rootMappings[i];
for (let j = 0; j < segments.length; j++) {
const segment = segments[j];
const genCol = segment[0];
let traced: SourceMapSegmentObject | null = SOURCELESS_MAPPING;
// 1-length segments only move the current generated column, there's no source information
// to gather from it.
if (segment.length !== 1) {
const source = rootSources[segment[1]];
traced = originalPositionFor(
source,
segment[2],
segment[3],
segment.length === 5 ? rootNames[segment[4]] : '',
);
// If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
// respective segment into an original source.
if (traced == null) continue;
}
const { column, line, name, content, source, ignore } = traced;
maybeAddSegment(gen, i, genCol, source, line, column, name);
if (source && content != null) setSourceContent(gen, source, content);
if (ignore) setIgnore(gen, source, true);
}
}
return gen;
}
/**
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
* child SourceMapTrees, until we find the original source map.
*/
export function originalPositionFor(
source: Sources,
line: number,
column: number,
name: string,
): SourceMapSegmentObject | null {
if (!source.map) {
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
}
const segment = traceSegment(source.map, line, column);
// If we couldn't find a segment, then this doesn't exist in the sourcemap.
if (segment == null) return null;
// 1-length segments only move the current generated column, there's no source information
// to gather from it.
if (segment.length === 1) return SOURCELESS_MAPPING;
return originalPositionFor(
source.sources[segment[1]],
segment[2],
segment[3],
segment.length === 5 ? source.map.names[segment[4]] : name,
);
}