streamChunksOfCombinedSourceMap.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. let r = mappingsData.length / 5;
  76. while (l < r) {
  77. const m = (l + r) >> 1;
  78. if (mappingsData[m * 5] <= column) {
  79. l = m + 1;
  80. } else {
  81. r = m;
  82. }
  83. }
  84. if (l === 0) return -1;
  85. return l - 1;
  86. };
  87. return streamChunksOfSourceMap(
  88. source,
  89. sourceMap,
  90. (
  91. chunk,
  92. generatedLine,
  93. generatedColumn,
  94. sourceIndex,
  95. originalLine,
  96. originalColumn,
  97. nameIndex,
  98. ) => {
  99. // Check if this is a mapping to the inner source
  100. if (sourceIndex === outerSourceIndex) {
  101. // Check if there is a mapping in the inner source
  102. const idx = findInnerMapping(originalLine, originalColumn);
  103. if (idx !== -1) {
  104. const { chunks, mappingsData } =
  105. innerSourceMapLineData[originalLine - 1];
  106. const mi = idx * 5;
  107. const innerSourceIndex = mappingsData[mi + 1];
  108. const innerOriginalLine = mappingsData[mi + 2];
  109. let innerOriginalColumn = mappingsData[mi + 3];
  110. let innerNameIndex = mappingsData[mi + 4];
  111. if (innerSourceIndex >= 0) {
  112. // Check for an identity mapping
  113. // where we are allowed to adjust the original column
  114. const innerChunk = chunks[idx];
  115. const innerGeneratedColumn = mappingsData[mi];
  116. const locationInChunk = originalColumn - innerGeneratedColumn;
  117. if (locationInChunk > 0) {
  118. let originalSourceLines =
  119. innerSourceIndex < innerSourceContentLines.length
  120. ? innerSourceContentLines[innerSourceIndex]
  121. : null;
  122. if (originalSourceLines === undefined) {
  123. const originalSource = innerSourceContents[innerSourceIndex];
  124. originalSourceLines = originalSource
  125. ? splitIntoLines(originalSource)
  126. : null;
  127. innerSourceContentLines[innerSourceIndex] = originalSourceLines;
  128. }
  129. if (originalSourceLines !== null) {
  130. const originalChunk =
  131. innerOriginalLine <= originalSourceLines.length
  132. ? originalSourceLines[innerOriginalLine - 1].slice(
  133. innerOriginalColumn,
  134. innerOriginalColumn + locationInChunk,
  135. )
  136. : "";
  137. if (innerChunk.slice(0, locationInChunk) === originalChunk) {
  138. innerOriginalColumn += locationInChunk;
  139. innerNameIndex = -1;
  140. }
  141. }
  142. }
  143. // We have a inner mapping to original source
  144. // emit source when needed and compute global source index
  145. let sourceIndex =
  146. innerSourceIndex < innerSourceIndexMapping.length
  147. ? innerSourceIndexMapping[innerSourceIndex]
  148. : -2;
  149. if (sourceIndex === -2) {
  150. const [source, sourceContent] =
  151. innerSourceIndex < innerSourceIndexValueMapping.length
  152. ? innerSourceIndexValueMapping[innerSourceIndex]
  153. : [null, undefined];
  154. let globalIndex = sourceMapping.get(source);
  155. if (globalIndex === undefined) {
  156. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  157. onSource(globalIndex, source, sourceContent);
  158. }
  159. sourceIndex = globalIndex;
  160. innerSourceIndexMapping[innerSourceIndex] = sourceIndex;
  161. }
  162. // emit name when needed and compute global name index
  163. let finalNameIndex = -1;
  164. if (innerNameIndex >= 0) {
  165. // when we have a inner name
  166. finalNameIndex =
  167. innerNameIndex < innerNameIndexMapping.length
  168. ? innerNameIndexMapping[innerNameIndex]
  169. : -2;
  170. if (finalNameIndex === -2) {
  171. const name =
  172. innerNameIndex < innerNameIndexValueMapping.length
  173. ? innerNameIndexValueMapping[innerNameIndex]
  174. : undefined;
  175. if (name) {
  176. let globalIndex = nameMapping.get(name);
  177. if (globalIndex === undefined) {
  178. nameMapping.set(name, (globalIndex = nameMapping.size));
  179. onName(globalIndex, name);
  180. }
  181. finalNameIndex = globalIndex;
  182. } else {
  183. finalNameIndex = -1;
  184. }
  185. innerNameIndexMapping[innerNameIndex] = finalNameIndex;
  186. }
  187. } else if (nameIndex >= 0) {
  188. // when we don't have an inner name,
  189. // but we have an outer name
  190. // it can be used when inner original code equals to the name
  191. let originalSourceLines =
  192. innerSourceContentLines[innerSourceIndex];
  193. if (originalSourceLines === undefined) {
  194. const originalSource = innerSourceContents[innerSourceIndex];
  195. originalSourceLines = originalSource
  196. ? splitIntoLines(originalSource)
  197. : null;
  198. innerSourceContentLines[innerSourceIndex] = originalSourceLines;
  199. }
  200. if (originalSourceLines !== null) {
  201. const name = nameIndexValueMapping[nameIndex];
  202. const originalName =
  203. innerOriginalLine <= originalSourceLines.length
  204. ? originalSourceLines[innerOriginalLine - 1].slice(
  205. innerOriginalColumn,
  206. innerOriginalColumn + name.length,
  207. )
  208. : "";
  209. if (name === originalName) {
  210. finalNameIndex =
  211. nameIndex < nameIndexMapping.length
  212. ? nameIndexMapping[nameIndex]
  213. : -2;
  214. if (finalNameIndex === -2) {
  215. const name = nameIndexValueMapping[nameIndex];
  216. if (name) {
  217. let globalIndex = nameMapping.get(name);
  218. if (globalIndex === undefined) {
  219. nameMapping.set(name, (globalIndex = nameMapping.size));
  220. onName(globalIndex, name);
  221. }
  222. finalNameIndex = globalIndex;
  223. } else {
  224. finalNameIndex = -1;
  225. }
  226. nameIndexMapping[nameIndex] = finalNameIndex;
  227. }
  228. }
  229. }
  230. }
  231. onChunk(
  232. chunk,
  233. generatedLine,
  234. generatedColumn,
  235. sourceIndex,
  236. innerOriginalLine,
  237. innerOriginalColumn,
  238. finalNameIndex,
  239. );
  240. return;
  241. }
  242. }
  243. // We have a mapping to the inner source, but no inner mapping
  244. if (removeInnerSource) {
  245. onChunk(chunk, generatedLine, generatedColumn, -1, -1, -1, -1);
  246. return;
  247. }
  248. if (sourceIndexMapping[sourceIndex] === -2) {
  249. let globalIndex = sourceMapping.get(innerSourceName);
  250. if (globalIndex === undefined) {
  251. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  252. onSource(globalIndex, innerSourceName, innerSource);
  253. }
  254. sourceIndexMapping[sourceIndex] = globalIndex;
  255. }
  256. }
  257. const finalSourceIndex =
  258. sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
  259. ? -1
  260. : sourceIndexMapping[sourceIndex];
  261. if (finalSourceIndex < 0) {
  262. // no source, so we make it a generated chunk
  263. onChunk(chunk, generatedLine, generatedColumn, -1, -1, -1, -1);
  264. } else {
  265. // Pass through the chunk with mapping
  266. let finalNameIndex = -1;
  267. if (nameIndex >= 0 && nameIndex < nameIndexMapping.length) {
  268. finalNameIndex = nameIndexMapping[nameIndex];
  269. if (finalNameIndex === -2) {
  270. const name = nameIndexValueMapping[nameIndex];
  271. let globalIndex = nameMapping.get(name);
  272. if (globalIndex === undefined) {
  273. nameMapping.set(name, (globalIndex = nameMapping.size));
  274. onName(globalIndex, name);
  275. }
  276. finalNameIndex = globalIndex;
  277. nameIndexMapping[nameIndex] = finalNameIndex;
  278. }
  279. }
  280. onChunk(
  281. chunk,
  282. generatedLine,
  283. generatedColumn,
  284. finalSourceIndex,
  285. originalLine,
  286. originalColumn,
  287. finalNameIndex,
  288. );
  289. }
  290. },
  291. (i, source, sourceContent) => {
  292. if (source === innerSourceName) {
  293. outerSourceIndex = i;
  294. if (innerSource !== undefined) sourceContent = innerSource;
  295. else innerSource = /** @type {string} */ (sourceContent);
  296. sourceIndexMapping[i] = -2;
  297. streamChunksOfSourceMap(
  298. /** @type {string} */
  299. (sourceContent),
  300. innerSourceMap,
  301. (
  302. chunk,
  303. generatedLine,
  304. generatedColumn,
  305. sourceIndex,
  306. originalLine,
  307. originalColumn,
  308. nameIndex,
  309. ) => {
  310. while (innerSourceMapLineData.length < generatedLine) {
  311. innerSourceMapLineData.push({
  312. mappingsData: [],
  313. chunks: [],
  314. });
  315. }
  316. const data = innerSourceMapLineData[generatedLine - 1];
  317. data.mappingsData.push(
  318. generatedColumn,
  319. sourceIndex,
  320. originalLine,
  321. originalColumn,
  322. nameIndex,
  323. );
  324. data.chunks.push(/** @type {string} */ (chunk));
  325. },
  326. (i, source, sourceContent) => {
  327. innerSourceContents[i] = sourceContent;
  328. innerSourceContentLines[i] = undefined;
  329. innerSourceIndexMapping[i] = -2;
  330. innerSourceIndexValueMapping[i] = [source, sourceContent];
  331. },
  332. (i, name) => {
  333. innerNameIndexMapping[i] = -2;
  334. innerNameIndexValueMapping[i] = name;
  335. },
  336. false,
  337. columns,
  338. );
  339. } else {
  340. let globalIndex = sourceMapping.get(source);
  341. if (globalIndex === undefined) {
  342. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  343. onSource(globalIndex, source, sourceContent);
  344. }
  345. sourceIndexMapping[i] = globalIndex;
  346. }
  347. },
  348. (i, name) => {
  349. nameIndexMapping[i] = -2;
  350. nameIndexValueMapping[i] = name;
  351. },
  352. finalSource,
  353. columns,
  354. );
  355. };
  356. module.exports = streamChunksOfCombinedSourceMap;