remapping.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import { traceSegment, decodedMappings, presortedDecodedMap, TraceMap, encodedMappings } from '@jridgewell/trace-mapping';
  2. /**
  3. * A "leaf" node in the sourcemap tree, representing an original, unmodified
  4. * source file. Recursive segment tracing ends at the `OriginalSource`.
  5. */
  6. class OriginalSource {
  7. constructor(source, content) {
  8. this.source = source;
  9. this.content = content;
  10. }
  11. /**
  12. * Tracing a `SourceMapSegment` ends when we get to an `OriginalSource`,
  13. * meaning this line/column location originated from this source file.
  14. */
  15. originalPositionFor(line, column, name) {
  16. return { column, line, name, source: this.source, content: this.content };
  17. }
  18. }
  19. /**
  20. * Puts `key` into the backing array, if it is not already present. Returns
  21. * the index of the `key` in the backing array.
  22. */
  23. let put;
  24. /**
  25. * FastStringArray acts like a `Set` (allowing only one occurrence of a string
  26. * `key`), but provides the index of the `key` in the backing array.
  27. *
  28. * This is designed to allow synchronizing a second array with the contents of
  29. * the backing array, like how `sourcesContent[i]` is the source content
  30. * associated with `source[i]`, and there are never duplicates.
  31. */
  32. class FastStringArray {
  33. constructor() {
  34. this.indexes = Object.create(null);
  35. this.array = [];
  36. }
  37. }
  38. (() => {
  39. put = (strarr, key) => {
  40. const { array, indexes } = strarr;
  41. // The key may or may not be present. If it is present, it's a number.
  42. let index = indexes[key];
  43. // If it's not yet present, we need to insert it and track the index in the
  44. // indexes.
  45. if (index === undefined) {
  46. index = indexes[key] = array.length;
  47. array.push(key);
  48. }
  49. return index;
  50. };
  51. })();
  52. const INVALID_MAPPING = undefined;
  53. const SOURCELESS_MAPPING = null;
  54. /**
  55. * traceMappings is only called on the root level SourceMapTree, and begins the process of
  56. * resolving each mapping in terms of the original source files.
  57. */
  58. let traceMappings;
  59. /**
  60. * SourceMapTree represents a single sourcemap, with the ability to trace
  61. * mappings into its child nodes (which may themselves be SourceMapTrees).
  62. */
  63. class SourceMapTree {
  64. constructor(map, sources) {
  65. this.map = map;
  66. this.sources = sources;
  67. }
  68. /**
  69. * originalPositionFor is only called on children SourceMapTrees. It recurses down
  70. * into its own child SourceMapTrees, until we find the original source map.
  71. */
  72. originalPositionFor(line, column, name) {
  73. const segment = traceSegment(this.map, line, column);
  74. // If we couldn't find a segment, then this doesn't exist in the sourcemap.
  75. if (segment == null)
  76. return INVALID_MAPPING;
  77. // 1-length segments only move the current generated column, there's no source information
  78. // to gather from it.
  79. if (segment.length === 1)
  80. return SOURCELESS_MAPPING;
  81. const source = this.sources[segment[1]];
  82. return source.originalPositionFor(segment[2], segment[3], segment.length === 5 ? this.map.names[segment[4]] : name);
  83. }
  84. }
  85. (() => {
  86. traceMappings = (tree) => {
  87. const mappings = [];
  88. const names = new FastStringArray();
  89. const sources = new FastStringArray();
  90. const sourcesContent = [];
  91. const { sources: rootSources, map } = tree;
  92. const rootNames = map.names;
  93. const rootMappings = decodedMappings(map);
  94. let lastLineWithSegment = -1;
  95. for (let i = 0; i < rootMappings.length; i++) {
  96. const segments = rootMappings[i];
  97. const tracedSegments = [];
  98. let lastSourcesIndex = -1;
  99. let lastSourceLine = -1;
  100. let lastSourceColumn = -1;
  101. for (let j = 0; j < segments.length; j++) {
  102. const segment = segments[j];
  103. let traced = SOURCELESS_MAPPING;
  104. // 1-length segments only move the current generated column, there's no source information
  105. // to gather from it.
  106. if (segment.length !== 1) {
  107. const source = rootSources[segment[1]];
  108. traced = source.originalPositionFor(segment[2], segment[3], segment.length === 5 ? rootNames[segment[4]] : '');
  109. // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
  110. // respective segment into an original source.
  111. if (traced === INVALID_MAPPING)
  112. continue;
  113. }
  114. const genCol = segment[0];
  115. if (traced === SOURCELESS_MAPPING) {
  116. if (lastSourcesIndex === -1) {
  117. // This is a consecutive source-less segment, which doesn't carry any new information.
  118. continue;
  119. }
  120. lastSourcesIndex = lastSourceLine = lastSourceColumn = -1;
  121. tracedSegments.push([genCol]);
  122. continue;
  123. }
  124. // So we traced a segment down into its original source file. Now push a
  125. // new segment pointing to this location.
  126. const { column, line, name, content, source } = traced;
  127. // Store the source location, and ensure we keep sourcesContent up to
  128. // date with the sources array.
  129. const sourcesIndex = put(sources, source);
  130. sourcesContent[sourcesIndex] = content;
  131. if (lastSourcesIndex === sourcesIndex &&
  132. lastSourceLine === line &&
  133. lastSourceColumn === column) {
  134. // This is a duplicate mapping pointing at the exact same starting point in the source
  135. // file. It doesn't carry any new information, and only bloats the sourcemap.
  136. continue;
  137. }
  138. lastLineWithSegment = i;
  139. lastSourcesIndex = sourcesIndex;
  140. lastSourceLine = line;
  141. lastSourceColumn = column;
  142. // This looks like unnecessary duplication, but it noticeably increases performance. If we
  143. // were to push the nameIndex onto length-4 array, v8 would internally allocate 22 slots!
  144. // That's 68 wasted bytes! Array literals have the same capacity as their length, saving
  145. // memory.
  146. tracedSegments.push(name
  147. ? [genCol, sourcesIndex, line, column, put(names, name)]
  148. : [genCol, sourcesIndex, line, column]);
  149. }
  150. mappings.push(tracedSegments);
  151. }
  152. if (mappings.length > lastLineWithSegment + 1) {
  153. mappings.length = lastLineWithSegment + 1;
  154. }
  155. return presortedDecodedMap(Object.assign({}, tree.map, {
  156. mappings,
  157. // TODO: Make all sources relative to the sourceRoot.
  158. sourceRoot: undefined,
  159. names: names.array,
  160. sources: sources.array,
  161. sourcesContent,
  162. }));
  163. };
  164. })();
  165. function asArray(value) {
  166. if (Array.isArray(value))
  167. return value;
  168. return [value];
  169. }
  170. /**
  171. * Recursively builds a tree structure out of sourcemap files, with each node
  172. * being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
  173. * `OriginalSource`s and `SourceMapTree`s.
  174. *
  175. * Every sourcemap is composed of a collection of source files and mappings
  176. * into locations of those source files. When we generate a `SourceMapTree` for
  177. * the sourcemap, we attempt to load each source file's own sourcemap. If it
  178. * does not have an associated sourcemap, it is considered an original,
  179. * unmodified source file.
  180. */
  181. function buildSourceMapTree(input, loader) {
  182. const maps = asArray(input).map((m) => new TraceMap(m, ''));
  183. const map = maps.pop();
  184. for (let i = 0; i < maps.length; i++) {
  185. if (maps[i].sources.length > 1) {
  186. throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
  187. 'Did you specify these with the most recent transformation maps first?');
  188. }
  189. }
  190. let tree = build(map, loader, '', 0);
  191. for (let i = maps.length - 1; i >= 0; i--) {
  192. tree = new SourceMapTree(maps[i], [tree]);
  193. }
  194. return tree;
  195. }
  196. function build(map, loader, importer, importerDepth) {
  197. const { resolvedSources, sourcesContent } = map;
  198. const depth = importerDepth + 1;
  199. const children = resolvedSources.map((sourceFile, i) => {
  200. // The loading context gives the loader more information about why this file is being loaded
  201. // (eg, from which importer). It also allows the loader to override the location of the loaded
  202. // sourcemap/original source, or to override the content in the sourcesContent field if it's
  203. // an unmodified source file.
  204. const ctx = {
  205. importer,
  206. depth,
  207. source: sourceFile || '',
  208. content: undefined,
  209. };
  210. // Use the provided loader callback to retrieve the file's sourcemap.
  211. // TODO: We should eventually support async loading of sourcemap files.
  212. const sourceMap = loader(ctx.source, ctx);
  213. const { source, content } = ctx;
  214. // If there is no sourcemap, then it is an unmodified source file.
  215. if (!sourceMap) {
  216. // The contents of this unmodified source file can be overridden via the loader context,
  217. // allowing it to be explicitly null or a string. If it remains undefined, we fall back to
  218. // the importing sourcemap's `sourcesContent` field.
  219. const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
  220. return new OriginalSource(source, sourceContent);
  221. }
  222. // Else, it's a real sourcemap, and we need to recurse into it to load its
  223. // source files.
  224. return build(new TraceMap(sourceMap, source), loader, source, depth);
  225. });
  226. return new SourceMapTree(map, children);
  227. }
  228. /**
  229. * A SourceMap v3 compatible sourcemap, which only includes fields that were
  230. * provided to it.
  231. */
  232. class SourceMap {
  233. constructor(map, options) {
  234. this.version = 3; // SourceMap spec says this should be first.
  235. this.file = map.file;
  236. this.mappings = options.decodedMappings ? decodedMappings(map) : encodedMappings(map);
  237. this.names = map.names;
  238. this.sourceRoot = map.sourceRoot;
  239. this.sources = map.sources;
  240. if (!options.excludeContent && 'sourcesContent' in map) {
  241. this.sourcesContent = map.sourcesContent;
  242. }
  243. }
  244. toString() {
  245. return JSON.stringify(this);
  246. }
  247. }
  248. /**
  249. * Traces through all the mappings in the root sourcemap, through the sources
  250. * (and their sourcemaps), all the way back to the original source location.
  251. *
  252. * `loader` will be called every time we encounter a source file. If it returns
  253. * a sourcemap, we will recurse into that sourcemap to continue the trace. If
  254. * it returns a falsey value, that source file is treated as an original,
  255. * unmodified source file.
  256. *
  257. * Pass `excludeContent` to exclude any self-containing source file content
  258. * from the output sourcemap.
  259. *
  260. * Pass `decodedMappings` to receive a SourceMap with decoded (instead of
  261. * VLQ encoded) mappings.
  262. */
  263. function remapping(input, loader, options) {
  264. const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
  265. const tree = buildSourceMapTree(input, loader);
  266. return new SourceMap(traceMappings(tree), opts);
  267. }
  268. export { remapping as default };
  269. //# sourceMappingURL=remapping.mjs.map