205 lines
8.3 KiB
JavaScript
205 lines
8.3 KiB
JavaScript
|
import { decodedMappings, traceSegment, TraceMap } from '@jridgewell/trace-mapping';
|
||
|
import { GenMapping, addSegment, setSourceContent, decodedMap, encodedMap } from '@jridgewell/gen-mapping';
|
||
|
|
||
|
const SOURCELESS_MAPPING = {
|
||
|
source: null,
|
||
|
column: null,
|
||
|
line: null,
|
||
|
name: null,
|
||
|
content: null,
|
||
|
};
|
||
|
const EMPTY_SOURCES = [];
|
||
|
function Source(map, sources, source, content) {
|
||
|
return {
|
||
|
map,
|
||
|
sources,
|
||
|
source,
|
||
|
content,
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
|
||
|
* (which may themselves be SourceMapTrees).
|
||
|
*/
|
||
|
function MapSource(map, sources) {
|
||
|
return Source(map, sources, '', null);
|
||
|
}
|
||
|
/**
|
||
|
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
|
||
|
* segment tracing ends at the `OriginalSource`.
|
||
|
*/
|
||
|
function OriginalSource(source, content) {
|
||
|
return Source(null, EMPTY_SOURCES, source, content);
|
||
|
}
|
||
|
/**
|
||
|
* traceMappings is only called on the root level SourceMapTree, and begins the process of
|
||
|
* resolving each mapping in terms of the original source files.
|
||
|
*/
|
||
|
function traceMappings(tree) {
|
||
|
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];
|
||
|
let lastSource = null;
|
||
|
let lastSourceLine = null;
|
||
|
let lastSourceColumn = null;
|
||
|
for (let j = 0; j < segments.length; j++) {
|
||
|
const segment = segments[j];
|
||
|
const genCol = segment[0];
|
||
|
let traced = 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;
|
||
|
}
|
||
|
// So we traced a segment down into its original source file. Now push a
|
||
|
// new segment pointing to this location.
|
||
|
const { column, line, name, content, source } = traced;
|
||
|
if (line === lastSourceLine && column === lastSourceColumn && source === lastSource) {
|
||
|
continue;
|
||
|
}
|
||
|
lastSourceLine = line;
|
||
|
lastSourceColumn = column;
|
||
|
lastSource = source;
|
||
|
// Sigh, TypeScript can't figure out source/line/column are either all null, or all non-null...
|
||
|
addSegment(gen, i, genCol, source, line, column, name);
|
||
|
if (content != null)
|
||
|
setSourceContent(gen, source, content);
|
||
|
}
|
||
|
}
|
||
|
return gen;
|
||
|
}
|
||
|
/**
|
||
|
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
|
||
|
* child SourceMapTrees, until we find the original source map.
|
||
|
*/
|
||
|
function originalPositionFor(source, line, column, name) {
|
||
|
if (!source.map) {
|
||
|
return { column, line, name, source: source.source, content: source.content };
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
function asArray(value) {
|
||
|
if (Array.isArray(value))
|
||
|
return value;
|
||
|
return [value];
|
||
|
}
|
||
|
/**
|
||
|
* Recursively builds a tree structure out of sourcemap files, with each node
|
||
|
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
||
|
* `OriginalSource`s and `SourceMapTree`s.
|
||
|
*
|
||
|
* Every sourcemap is composed of a collection of source files and mappings
|
||
|
* into locations of those source files. When we generate a `SourceMapTree` for
|
||
|
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
||
|
* does not have an associated sourcemap, it is considered an original,
|
||
|
* unmodified source file.
|
||
|
*/
|
||
|
function buildSourceMapTree(input, loader) {
|
||
|
const maps = asArray(input).map((m) => new TraceMap(m, ''));
|
||
|
const map = maps.pop();
|
||
|
for (let i = 0; i < maps.length; i++) {
|
||
|
if (maps[i].sources.length > 1) {
|
||
|
throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
|
||
|
'Did you specify these with the most recent transformation maps first?');
|
||
|
}
|
||
|
}
|
||
|
let tree = build(map, loader, '', 0);
|
||
|
for (let i = maps.length - 1; i >= 0; i--) {
|
||
|
tree = MapSource(maps[i], [tree]);
|
||
|
}
|
||
|
return tree;
|
||
|
}
|
||
|
function build(map, loader, importer, importerDepth) {
|
||
|
const { resolvedSources, sourcesContent } = map;
|
||
|
const depth = importerDepth + 1;
|
||
|
const children = resolvedSources.map((sourceFile, i) => {
|
||
|
// The loading context gives the loader more information about why this file is being loaded
|
||
|
// (eg, from which importer). It also allows the loader to override the location of the loaded
|
||
|
// sourcemap/original source, or to override the content in the sourcesContent field if it's
|
||
|
// an unmodified source file.
|
||
|
const ctx = {
|
||
|
importer,
|
||
|
depth,
|
||
|
source: sourceFile || '',
|
||
|
content: undefined,
|
||
|
};
|
||
|
// Use the provided loader callback to retrieve the file's sourcemap.
|
||
|
// TODO: We should eventually support async loading of sourcemap files.
|
||
|
const sourceMap = loader(ctx.source, ctx);
|
||
|
const { source, content } = ctx;
|
||
|
// If there is a sourcemap, then we need to recurse into it to load its source files.
|
||
|
if (sourceMap)
|
||
|
return build(new TraceMap(sourceMap, source), loader, source, depth);
|
||
|
// Else, it's an an unmodified source file.
|
||
|
// The contents of this unmodified source file can be overridden via the loader context,
|
||
|
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
|
||
|
// the importing sourcemap's `sourcesContent` field.
|
||
|
const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
|
||
|
return OriginalSource(source, sourceContent);
|
||
|
});
|
||
|
return MapSource(map, children);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A SourceMap v3 compatible sourcemap, which only includes fields that were
|
||
|
* provided to it.
|
||
|
*/
|
||
|
class SourceMap {
|
||
|
constructor(map, options) {
|
||
|
const out = options.decodedMappings ? decodedMap(map) : encodedMap(map);
|
||
|
this.version = out.version; // SourceMap spec says this should be first.
|
||
|
this.file = out.file;
|
||
|
this.mappings = out.mappings;
|
||
|
this.names = out.names;
|
||
|
this.sourceRoot = out.sourceRoot;
|
||
|
this.sources = out.sources;
|
||
|
if (!options.excludeContent) {
|
||
|
this.sourcesContent = out.sourcesContent;
|
||
|
}
|
||
|
}
|
||
|
toString() {
|
||
|
return JSON.stringify(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Traces through all the mappings in the root sourcemap, through the sources
|
||
|
* (and their sourcemaps), all the way back to the original source location.
|
||
|
*
|
||
|
* `loader` will be called every time we encounter a source file. If it returns
|
||
|
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
|
||
|
* it returns a falsey value, that source file is treated as an original,
|
||
|
* unmodified source file.
|
||
|
*
|
||
|
* Pass `excludeContent` to exclude any self-containing source file content
|
||
|
* from the output sourcemap.
|
||
|
*
|
||
|
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
|
||
|
* VLQ encoded) mappings.
|
||
|
*/
|
||
|
function remapping(input, loader, options) {
|
||
|
const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
|
||
|
const tree = buildSourceMapTree(input, loader);
|
||
|
return new SourceMap(traceMappings(tree), opts);
|
||
|
}
|
||
|
|
||
|
export { remapping as default };
|
||
|
//# sourceMappingURL=remapping.mjs.map
|