streamChunksOfCombinedSourceMap.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const splitIntoLines = require("./splitIntoLines");
  7. const streamChunksOfSourceMap = require("./streamChunksOfSourceMap");
  8. /** @typedef {import("../Source").RawSourceMap} RawSourceMap */
  9. /** @typedef {import("./getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  10. /** @typedef {import("./streamChunks").OnChunk} onChunk */
  11. /** @typedef {import("./streamChunks").OnName} OnName */
  12. /** @typedef {import("./streamChunks").OnSource} OnSource */
  13. /**
  14. * @param {string} source source
  15. * @param {RawSourceMap} sourceMap source map
  16. * @param {string} innerSourceName inner source name
  17. * @param {string} innerSource inner source
  18. * @param {RawSourceMap} innerSourceMap inner source map
  19. * @param {boolean | undefined} removeInnerSource do remove inner source
  20. * @param {onChunk} onChunk on chunk
  21. * @param {OnSource} onSource on source
  22. * @param {OnName} onName on name
  23. * @param {boolean} finalSource finalSource
  24. * @param {boolean} columns columns
  25. * @returns {GeneratedSourceInfo} generated source info
  26. */
  27. const streamChunksOfCombinedSourceMap = (
  28. source,
  29. sourceMap,
  30. innerSourceName,
  31. innerSource,
  32. innerSourceMap,
  33. removeInnerSource,
  34. onChunk,
  35. onSource,
  36. onName,
  37. finalSource,
  38. columns,
  39. ) => {
  40. /** @type {Map<string | null, number>} */
  41. const sourceMapping = new Map();
  42. /** @type {Map<string, number>} */
  43. const nameMapping = new Map();
  44. /** @type {number[]} */
  45. const sourceIndexMapping = [];
  46. /** @type {number[]} */
  47. const nameIndexMapping = [];
  48. /** @type {string[]} */
  49. const nameIndexValueMapping = [];
  50. let outerSourceIndex = -2;
  51. /** @type {number[]} */
  52. const innerSourceIndexMapping = [];
  53. /** @type {[string | null, string | undefined][]} */
  54. const innerSourceIndexValueMapping = [];
  55. /** @type {(string | undefined)[]} */
  56. const innerSourceContents = [];
  57. /** @type {(null | undefined | string[])[]} */
  58. const innerSourceContentLines = [];
  59. /** @type {number[]} */
  60. const innerNameIndexMapping = [];
  61. /** @type {string[]} */
  62. const innerNameIndexValueMapping = [];
  63. /** @typedef {[number, number, number, number, number] | number[]} MappingsData */
  64. /** @type {{ chunks: string[], mappingsData: MappingsData }[]} */
  65. const innerSourceMapLineData = [];
  66. /**
  67. * @param {number} line line
  68. * @param {number} column column
  69. * @returns {number} result
  70. */
  71. const findInnerMapping = (line, column) => {
  72. if (line > innerSourceMapLineData.length) return -1;
  73. const { mappingsData } = innerSourceMapLineData[line - 1];
  74. let l = 0;
  75. // `mappingsData.length` is always a multiple of 5 (five values pushed
  76. // per mapping), so dividing is exact. Coerce the bound to an int32 so
  77. // the binary-search loop stays on V8's fast small-int path instead of
  78. // comparing an int against a float.
  79. let r = (mappingsData.length / 5) | 0;
  80. while (l < r) {
  81. const m = (l + r) >> 1;
  82. if (mappingsData[m * 5] <= column) {
  83. l = m + 1;
  84. } else {
  85. r = m;
  86. }
  87. }
  88. if (l === 0) return -1;
  89. return l - 1;
  90. };
  91. return streamChunksOfSourceMap(
  92. source,
  93. sourceMap,
  94. (
  95. chunk,
  96. generatedLine,
  97. generatedColumn,
  98. sourceIndex,
  99. originalLine,
  100. originalColumn,
  101. nameIndex,
  102. ) => {
  103. // Check if this is a mapping to the inner source
  104. if (sourceIndex === outerSourceIndex) {
  105. // Check if there is a mapping in the inner source
  106. const idx = findInnerMapping(originalLine, originalColumn);
  107. if (idx !== -1) {
  108. const { chunks, mappingsData } =
  109. innerSourceMapLineData[originalLine - 1];
  110. const mi = idx * 5;
  111. const innerSourceIndex = mappingsData[mi + 1];
  112. const innerOriginalLine = mappingsData[mi + 2];
  113. let innerOriginalColumn = mappingsData[mi + 3];
  114. let innerNameIndex = mappingsData[mi + 4];
  115. if (innerSourceIndex >= 0) {
  116. // Check for an identity mapping
  117. // where we are allowed to adjust the original column
  118. const innerChunk = chunks[idx];
  119. const innerGeneratedColumn = mappingsData[mi];
  120. const locationInChunk = originalColumn - innerGeneratedColumn;
  121. if (locationInChunk > 0) {
  122. let originalSourceLines =
  123. innerSourceIndex < innerSourceContentLines.length
  124. ? innerSourceContentLines[innerSourceIndex]
  125. : null;
  126. if (originalSourceLines === undefined) {
  127. const originalSource = innerSourceContents[innerSourceIndex];
  128. originalSourceLines = originalSource
  129. ? splitIntoLines(originalSource)
  130. : null;
  131. innerSourceContentLines[innerSourceIndex] = originalSourceLines;
  132. }
  133. if (originalSourceLines !== null) {
  134. const originalChunk =
  135. innerOriginalLine <= originalSourceLines.length
  136. ? originalSourceLines[innerOriginalLine - 1].slice(
  137. innerOriginalColumn,
  138. innerOriginalColumn + locationInChunk,
  139. )
  140. : "";
  141. if (innerChunk.slice(0, locationInChunk) === originalChunk) {
  142. innerOriginalColumn += locationInChunk;
  143. innerNameIndex = -1;
  144. }
  145. }
  146. }
  147. // We have a inner mapping to original source
  148. // emit source when needed and compute global source index
  149. let sourceIndex =
  150. innerSourceIndex < innerSourceIndexMapping.length
  151. ? innerSourceIndexMapping[innerSourceIndex]
  152. : -2;
  153. if (sourceIndex === -2) {
  154. const [source, sourceContent] =
  155. innerSourceIndex < innerSourceIndexValueMapping.length
  156. ? innerSourceIndexValueMapping[innerSourceIndex]
  157. : [null, undefined];
  158. let globalIndex = sourceMapping.get(source);
  159. if (globalIndex === undefined) {
  160. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  161. onSource(globalIndex, source, sourceContent);
  162. }
  163. sourceIndex = globalIndex;
  164. innerSourceIndexMapping[innerSourceIndex] = sourceIndex;
  165. }
  166. // emit name when needed and compute global name index
  167. let finalNameIndex = -1;
  168. if (innerNameIndex >= 0) {
  169. // when we have a inner name
  170. finalNameIndex =
  171. innerNameIndex < innerNameIndexMapping.length
  172. ? innerNameIndexMapping[innerNameIndex]
  173. : -2;
  174. if (finalNameIndex === -2) {
  175. const name =
  176. innerNameIndex < innerNameIndexValueMapping.length
  177. ? innerNameIndexValueMapping[innerNameIndex]
  178. : undefined;
  179. if (name) {
  180. let globalIndex = nameMapping.get(name);
  181. if (globalIndex === undefined) {
  182. nameMapping.set(name, (globalIndex = nameMapping.size));
  183. onName(globalIndex, name);
  184. }
  185. finalNameIndex = globalIndex;
  186. } else {
  187. finalNameIndex = -1;
  188. }
  189. innerNameIndexMapping[innerNameIndex] = finalNameIndex;
  190. }
  191. } else if (nameIndex >= 0) {
  192. // when we don't have an inner name,
  193. // but we have an outer name
  194. // it can be used when inner original code equals to the name
  195. let originalSourceLines =
  196. innerSourceContentLines[innerSourceIndex];
  197. if (originalSourceLines === undefined) {
  198. const originalSource = innerSourceContents[innerSourceIndex];
  199. originalSourceLines = originalSource
  200. ? splitIntoLines(originalSource)
  201. : null;
  202. innerSourceContentLines[innerSourceIndex] = originalSourceLines;
  203. }
  204. if (originalSourceLines !== null) {
  205. const name = nameIndexValueMapping[nameIndex];
  206. const originalName =
  207. innerOriginalLine <= originalSourceLines.length
  208. ? originalSourceLines[innerOriginalLine - 1].slice(
  209. innerOriginalColumn,
  210. innerOriginalColumn + name.length,
  211. )
  212. : "";
  213. if (name === originalName) {
  214. finalNameIndex =
  215. nameIndex < nameIndexMapping.length
  216. ? nameIndexMapping[nameIndex]
  217. : -2;
  218. if (finalNameIndex === -2) {
  219. const name = nameIndexValueMapping[nameIndex];
  220. if (name) {
  221. let globalIndex = nameMapping.get(name);
  222. if (globalIndex === undefined) {
  223. nameMapping.set(name, (globalIndex = nameMapping.size));
  224. onName(globalIndex, name);
  225. }
  226. finalNameIndex = globalIndex;
  227. } else {
  228. finalNameIndex = -1;
  229. }
  230. nameIndexMapping[nameIndex] = finalNameIndex;
  231. }
  232. }
  233. }
  234. }
  235. onChunk(
  236. chunk,
  237. generatedLine,
  238. generatedColumn,
  239. sourceIndex,
  240. innerOriginalLine,
  241. innerOriginalColumn,
  242. finalNameIndex,
  243. );
  244. return;
  245. }
  246. }
  247. // We have a mapping to the inner source, but no inner mapping
  248. if (removeInnerSource) {
  249. onChunk(chunk, generatedLine, generatedColumn, -1, -1, -1, -1);
  250. return;
  251. }
  252. if (sourceIndexMapping[sourceIndex] === -2) {
  253. let globalIndex = sourceMapping.get(innerSourceName);
  254. if (globalIndex === undefined) {
  255. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  256. onSource(globalIndex, innerSourceName, innerSource);
  257. }
  258. sourceIndexMapping[sourceIndex] = globalIndex;
  259. }
  260. }
  261. const finalSourceIndex =
  262. sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
  263. ? -1
  264. : sourceIndexMapping[sourceIndex];
  265. if (finalSourceIndex < 0) {
  266. // no source, so we make it a generated chunk
  267. onChunk(chunk, generatedLine, generatedColumn, -1, -1, -1, -1);
  268. } else {
  269. // Pass through the chunk with mapping
  270. let finalNameIndex = -1;
  271. if (nameIndex >= 0 && nameIndex < nameIndexMapping.length) {
  272. finalNameIndex = nameIndexMapping[nameIndex];
  273. if (finalNameIndex === -2) {
  274. const name = nameIndexValueMapping[nameIndex];
  275. let globalIndex = nameMapping.get(name);
  276. if (globalIndex === undefined) {
  277. nameMapping.set(name, (globalIndex = nameMapping.size));
  278. onName(globalIndex, name);
  279. }
  280. finalNameIndex = globalIndex;
  281. nameIndexMapping[nameIndex] = finalNameIndex;
  282. }
  283. }
  284. onChunk(
  285. chunk,
  286. generatedLine,
  287. generatedColumn,
  288. finalSourceIndex,
  289. originalLine,
  290. originalColumn,
  291. finalNameIndex,
  292. );
  293. }
  294. },
  295. (i, source, sourceContent) => {
  296. if (source === innerSourceName) {
  297. outerSourceIndex = i;
  298. if (innerSource !== undefined) sourceContent = innerSource;
  299. else innerSource = /** @type {string} */ (sourceContent);
  300. sourceIndexMapping[i] = -2;
  301. streamChunksOfSourceMap(
  302. /** @type {string} */
  303. (sourceContent),
  304. innerSourceMap,
  305. (
  306. chunk,
  307. generatedLine,
  308. generatedColumn,
  309. sourceIndex,
  310. originalLine,
  311. originalColumn,
  312. nameIndex,
  313. ) => {
  314. while (innerSourceMapLineData.length < generatedLine) {
  315. innerSourceMapLineData.push({
  316. mappingsData: [],
  317. chunks: [],
  318. });
  319. }
  320. const data = innerSourceMapLineData[generatedLine - 1];
  321. data.mappingsData.push(
  322. generatedColumn,
  323. sourceIndex,
  324. originalLine,
  325. originalColumn,
  326. nameIndex,
  327. );
  328. data.chunks.push(/** @type {string} */ (chunk));
  329. },
  330. (i, source, sourceContent) => {
  331. innerSourceContents[i] = sourceContent;
  332. innerSourceContentLines[i] = undefined;
  333. innerSourceIndexMapping[i] = -2;
  334. innerSourceIndexValueMapping[i] = [source, sourceContent];
  335. },
  336. (i, name) => {
  337. innerNameIndexMapping[i] = -2;
  338. innerNameIndexValueMapping[i] = name;
  339. },
  340. false,
  341. columns,
  342. );
  343. } else {
  344. let globalIndex = sourceMapping.get(source);
  345. if (globalIndex === undefined) {
  346. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  347. onSource(globalIndex, source, sourceContent);
  348. }
  349. sourceIndexMapping[i] = globalIndex;
  350. }
  351. },
  352. (i, name) => {
  353. nameIndexMapping[i] = -2;
  354. nameIndexValueMapping[i] = name;
  355. },
  356. finalSource,
  357. columns,
  358. );
  359. };
  360. module.exports = streamChunksOfCombinedSourceMap;