remapping.mjs 8.27 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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