remapping.mjs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { decodedMappings, traceSegment, TraceMap } from '@jridgewell/trace-mapping';
  2. import { GenMapping, maybeAddSegment, setSourceContent, toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping';
  3. const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null);
  4. const EMPTY_SOURCES = [];
  5. function SegmentObject(source, line, column, name, content) {
  6. return { source, line, column, name, content };
  7. }
  8. function Source(map, sources, source, content) {
  9. return {
  10. map,
  11. sources,
  12. source,
  13. content,
  14. };
  15. }
  16. /**
  17. * MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
  18. * (which may themselves be SourceMapTrees).
  19. */
  20. function MapSource(map, sources) {
  21. return Source(map, sources, '', null);
  22. }
  23. /**
  24. * A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
  25. * segment tracing ends at the `OriginalSource`.
  26. */
  27. function OriginalSource(source, content) {
  28. return Source(null, EMPTY_SOURCES, source, content);
  29. }
  30. /**
  31. * traceMappings is only called on the root level SourceMapTree, and begins the process of
  32. * resolving each mapping in terms of the original source files.
  33. */
  34. function traceMappings(tree) {
  35. // TODO: Eventually support sourceRoot, which has to be removed because the sources are already
  36. // fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
  37. const gen = new GenMapping({ file: tree.map.file });
  38. const { sources: rootSources, map } = tree;
  39. const rootNames = map.names;
  40. const rootMappings = decodedMappings(map);
  41. for (let i = 0; i < rootMappings.length; i++) {
  42. const segments = rootMappings[i];
  43. for (let j = 0; j < segments.length; j++) {
  44. const segment = segments[j];
  45. const genCol = segment[0];
  46. let traced = SOURCELESS_MAPPING;
  47. // 1-length segments only move the current generated column, there's no source information
  48. // to gather from it.
  49. if (segment.length !== 1) {
  50. const source = rootSources[segment[1]];
  51. traced = originalPositionFor(source, segment[2], segment[3], segment.length === 5 ? rootNames[segment[4]] : '');
  52. // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
  53. // respective segment into an original source.
  54. if (traced == null)
  55. continue;
  56. }
  57. const { column, line, name, content, source } = traced;
  58. maybeAddSegment(gen, i, genCol, source, line, column, name);
  59. if (source && content != null)
  60. setSourceContent(gen, source, content);
  61. }
  62. }
  63. return gen;
  64. }
  65. /**
  66. * originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
  67. * child SourceMapTrees, until we find the original source map.
  68. */
  69. function originalPositionFor(source, line, column, name) {
  70. if (!source.map) {
  71. return SegmentObject(source.source, line, column, name, source.content);
  72. }
  73. const segment = traceSegment(source.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 null;
  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. return originalPositionFor(source.sources[segment[1]], segment[2], segment[3], segment.length === 5 ? source.map.names[segment[4]] : name);
  82. }
  83. function asArray(value) {
  84. if (Array.isArray(value))
  85. return value;
  86. return [value];
  87. }
  88. /**
  89. * Recursively builds a tree structure out of sourcemap files, with each node
  90. * being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
  91. * `OriginalSource`s and `SourceMapTree`s.
  92. *
  93. * Every sourcemap is composed of a collection of source files and mappings
  94. * into locations of those source files. When we generate a `SourceMapTree` for
  95. * the sourcemap, we attempt to load each source file's own sourcemap. If it
  96. * does not have an associated sourcemap, it is considered an original,
  97. * unmodified source file.
  98. */
  99. function buildSourceMapTree(input, loader) {
  100. const maps = asArray(input).map((m) => new TraceMap(m, ''));
  101. const map = maps.pop();
  102. for (let i = 0; i < maps.length; i++) {
  103. if (maps[i].sources.length > 1) {
  104. throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
  105. 'Did you specify these with the most recent transformation maps first?');
  106. }
  107. }
  108. let tree = build(map, loader, '', 0);
  109. for (let i = maps.length - 1; i >= 0; i--) {
  110. tree = MapSource(maps[i], [tree]);
  111. }
  112. return tree;
  113. }
  114. function build(map, loader, importer, importerDepth) {
  115. const { resolvedSources, sourcesContent } = map;
  116. const depth = importerDepth + 1;
  117. const children = resolvedSources.map((sourceFile, i) => {
  118. // The loading context gives the loader more information about why this file is being loaded
  119. // (eg, from which importer). It also allows the loader to override the location of the loaded
  120. // sourcemap/original source, or to override the content in the sourcesContent field if it's
  121. // an unmodified source file.
  122. const ctx = {
  123. importer,
  124. depth,
  125. source: sourceFile || '',
  126. content: undefined,
  127. };
  128. // Use the provided loader callback to retrieve the file's sourcemap.
  129. // TODO: We should eventually support async loading of sourcemap files.
  130. const sourceMap = loader(ctx.source, ctx);
  131. const { source, content } = ctx;
  132. // If there is a sourcemap, then we need to recurse into it to load its source files.
  133. if (sourceMap)
  134. return build(new TraceMap(sourceMap, source), loader, source, depth);
  135. // Else, it's an an unmodified source file.
  136. // The contents of this unmodified source file can be overridden via the loader context,
  137. // allowing it to be explicitly null or a string. If it remains undefined, we fall back to
  138. // the importing sourcemap's `sourcesContent` field.
  139. const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
  140. return OriginalSource(source, sourceContent);
  141. });
  142. return MapSource(map, children);
  143. }
  144. /**
  145. * A SourceMap v3 compatible sourcemap, which only includes fields that were
  146. * provided to it.
  147. */
  148. class SourceMap {
  149. constructor(map, options) {
  150. const out = options.decodedMappings ? toDecodedMap(map) : toEncodedMap(map);
  151. this.version = out.version; // SourceMap spec says this should be first.
  152. this.file = out.file;
  153. this.mappings = out.mappings;
  154. this.names = out.names;
  155. this.sourceRoot = out.sourceRoot;
  156. this.sources = out.sources;
  157. if (!options.excludeContent) {
  158. this.sourcesContent = out.sourcesContent;
  159. }
  160. }
  161. toString() {
  162. return JSON.stringify(this);
  163. }
  164. }
  165. /**
  166. * Traces through all the mappings in the root sourcemap, through the sources
  167. * (and their sourcemaps), all the way back to the original source location.
  168. *
  169. * `loader` will be called every time we encounter a source file. If it returns
  170. * a sourcemap, we will recurse into that sourcemap to continue the trace. If
  171. * it returns a falsey value, that source file is treated as an original,
  172. * unmodified source file.
  173. *
  174. * Pass `excludeContent` to exclude any self-containing source file content
  175. * from the output sourcemap.
  176. *
  177. * Pass `decodedMappings` to receive a SourceMap with decoded (instead of
  178. * VLQ encoded) mappings.
  179. */
  180. function remapping(input, loader, options) {
  181. const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
  182. const tree = buildSourceMapTree(input, loader);
  183. return new SourceMap(traceMappings(tree), opts);
  184. }
  185. export { remapping as default };
  186. //# sourceMappingURL=remapping.mjs.map