source-map-tree.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import { GenMapping, maybeAddSegment, setIgnore, setSourceContent } from '@jridgewell/gen-mapping';
  2. import { traceSegment, decodedMappings } from '@jridgewell/trace-mapping';
  3. import type { TraceMap } from '@jridgewell/trace-mapping';
  4. export type SourceMapSegmentObject = {
  5. column: number;
  6. line: number;
  7. name: string;
  8. source: string;
  9. content: string | null;
  10. ignore: boolean;
  11. };
  12. export type OriginalSource = {
  13. map: null;
  14. sources: Sources[];
  15. source: string;
  16. content: string | null;
  17. ignore: boolean;
  18. };
  19. export type MapSource = {
  20. map: TraceMap;
  21. sources: Sources[];
  22. source: string;
  23. content: null;
  24. ignore: false;
  25. };
  26. export type Sources = OriginalSource | MapSource;
  27. const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false);
  28. const EMPTY_SOURCES: Sources[] = [];
  29. function SegmentObject(
  30. source: string,
  31. line: number,
  32. column: number,
  33. name: string,
  34. content: string | null,
  35. ignore: boolean,
  36. ): SourceMapSegmentObject {
  37. return { source, line, column, name, content, ignore };
  38. }
  39. function Source(
  40. map: TraceMap,
  41. sources: Sources[],
  42. source: '',
  43. content: null,
  44. ignore: false,
  45. ): MapSource;
  46. function Source(
  47. map: null,
  48. sources: Sources[],
  49. source: string,
  50. content: string | null,
  51. ignore: boolean,
  52. ): OriginalSource;
  53. function Source(
  54. map: TraceMap | null,
  55. sources: Sources[],
  56. source: string | '',
  57. content: string | null,
  58. ignore: boolean,
  59. ): Sources {
  60. return {
  61. map,
  62. sources,
  63. source,
  64. content,
  65. ignore,
  66. } as any;
  67. }
  68. /**
  69. * MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
  70. * (which may themselves be SourceMapTrees).
  71. */
  72. export function MapSource(map: TraceMap, sources: Sources[]): MapSource {
  73. return Source(map, sources, '', null, false);
  74. }
  75. /**
  76. * A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
  77. * segment tracing ends at the `OriginalSource`.
  78. */
  79. export function OriginalSource(
  80. source: string,
  81. content: string | null,
  82. ignore: boolean,
  83. ): OriginalSource {
  84. return Source(null, EMPTY_SOURCES, source, content, ignore);
  85. }
  86. /**
  87. * traceMappings is only called on the root level SourceMapTree, and begins the process of
  88. * resolving each mapping in terms of the original source files.
  89. */
  90. export function traceMappings(tree: MapSource): GenMapping {
  91. // TODO: Eventually support sourceRoot, which has to be removed because the sources are already
  92. // fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
  93. const gen = new GenMapping({ file: tree.map.file });
  94. const { sources: rootSources, map } = tree;
  95. const rootNames = map.names;
  96. const rootMappings = decodedMappings(map);
  97. for (let i = 0; i < rootMappings.length; i++) {
  98. const segments = rootMappings[i];
  99. for (let j = 0; j < segments.length; j++) {
  100. const segment = segments[j];
  101. const genCol = segment[0];
  102. let traced: SourceMapSegmentObject | null = SOURCELESS_MAPPING;
  103. // 1-length segments only move the current generated column, there's no source information
  104. // to gather from it.
  105. if (segment.length !== 1) {
  106. const source = rootSources[segment[1]];
  107. traced = originalPositionFor(
  108. source,
  109. segment[2],
  110. segment[3],
  111. segment.length === 5 ? rootNames[segment[4]] : '',
  112. );
  113. // If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
  114. // respective segment into an original source.
  115. if (traced == null) continue;
  116. }
  117. const { column, line, name, content, source, ignore } = traced;
  118. maybeAddSegment(gen, i, genCol, source, line, column, name);
  119. if (source && content != null) setSourceContent(gen, source, content);
  120. if (ignore) setIgnore(gen, source, true);
  121. }
  122. }
  123. return gen;
  124. }
  125. /**
  126. * originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
  127. * child SourceMapTrees, until we find the original source map.
  128. */
  129. export function originalPositionFor(
  130. source: Sources,
  131. line: number,
  132. column: number,
  133. name: string,
  134. ): SourceMapSegmentObject | null {
  135. if (!source.map) {
  136. return SegmentObject(source.source, line, column, name, source.content, source.ignore);
  137. }
  138. const segment = traceSegment(source.map, line, column);
  139. // If we couldn't find a segment, then this doesn't exist in the sourcemap.
  140. if (segment == null) return null;
  141. // 1-length segments only move the current generated column, there's no source information
  142. // to gather from it.
  143. if (segment.length === 1) return SOURCELESS_MAPPING;
  144. return originalPositionFor(
  145. source.sources[segment[1]],
  146. segment[2],
  147. segment[3],
  148. segment.length === 5 ? source.map.names[segment[4]] : name,
  149. );
  150. }