createMappingsSerializer.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /**
  7. * @callback MappingsSerializer
  8. * @param {number} generatedLine generated line
  9. * @param {number} generatedColumn generated column
  10. * @param {number} sourceIndex source index
  11. * @param {number} originalLine original line
  12. * @param {number} originalColumn generated line
  13. * @param {number} nameIndex generated line
  14. * @returns {string} result
  15. */
  16. /**
  17. * A push-based serializer: `add()` appends one mapping to an internal
  18. * byte buffer, `finish()` materialises the whole `mappings` string in a
  19. * single allocation. Compared to the string-returning
  20. * {@link MappingsSerializer} this avoids every per-mapping intermediate
  21. * string (each VLQ digit concatenation) plus the caller-side
  22. * `mappings += str` cons chain — together the dominant allocation site
  23. * of `map()` / `sourceAndMap()`.
  24. * @typedef {object} MappingsWriter
  25. * @property {(generatedLine: number, generatedColumn: number, sourceIndex: number, originalLine: number, originalColumn: number, nameIndex: number) => void} add append one mapping
  26. * @property {() => string} finish materialise the mappings string
  27. */
  28. const ALPHABET = [
  29. ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
  30. ];
  31. // Char codes of ALPHABET for the buffer-writing path.
  32. const ALPHABET_CODES = new Uint8Array(64);
  33. for (let i = 0; i < 64; i++) ALPHABET_CODES[i] = ALPHABET[i].charCodeAt(0);
  34. const CH_SEMICOLON = 59; // ;
  35. const CH_COMMA = 44; // ,
  36. const CH_A = 65; // A
  37. const CONTINUATION_BIT = 0x20;
  38. /**
  39. * Append a VLQ-encoded signed integer to `str`. Hoisted to module scope so
  40. * that both serializers share a single function object and avoid allocating
  41. * a new closure on every call.
  42. * @param {string} str current string buffer
  43. * @param {number} value signed integer to encode
  44. * @returns {string} updated string buffer
  45. */
  46. const writeValue = (str, value) => {
  47. const sign = (value >>> 31) & 1;
  48. const mask = value >> 31;
  49. const absValue = (value + mask) ^ mask;
  50. let data = (absValue << 1) | sign;
  51. for (;;) {
  52. const sextet = data & 0x1f;
  53. data >>= 5;
  54. if (data === 0) {
  55. return str + ALPHABET[sextet];
  56. }
  57. str += ALPHABET[sextet | CONTINUATION_BIT];
  58. }
  59. };
  60. /**
  61. * Byte-buffer state shared by the writer variants. A plain object (not a
  62. * class) so the hot `add` closures capture it directly.
  63. * @returns {{ buf: Uint8Array, pos: number }} state
  64. */
  65. const createBufferState = () => ({ buf: new Uint8Array(1024), pos: 0 });
  66. /**
  67. * Ensure space for `n` more bytes.
  68. * @param {{ buf: Uint8Array, pos: number }} state state
  69. * @param {number} n bytes needed
  70. * @returns {Uint8Array} the (possibly grown) buffer
  71. */
  72. const ensure = (state, n) => {
  73. const { buf, pos } = state;
  74. if (pos + n <= buf.length) return buf;
  75. let nextLength = buf.length * 2;
  76. while (nextLength < pos + n) nextLength *= 2;
  77. const next = new Uint8Array(nextLength);
  78. next.set(buf);
  79. state.buf = next;
  80. return next;
  81. };
  82. /**
  83. * Append a VLQ-encoded signed integer to the byte buffer. The caller must
  84. * have reserved space already (a 32-bit value needs at most 7 sextets).
  85. * @param {{ buf: Uint8Array, pos: number }} state state
  86. * @param {number} value signed integer to encode
  87. * @returns {void}
  88. */
  89. const writeValueBytes = (state, value) => {
  90. const { buf } = state;
  91. let { pos } = state;
  92. const sign = (value >>> 31) & 1;
  93. const mask = value >> 31;
  94. const absValue = (value + mask) ^ mask;
  95. let data = (absValue << 1) | sign;
  96. for (;;) {
  97. const sextet = data & 0x1f;
  98. data >>= 5;
  99. if (data === 0) {
  100. buf[pos++] = ALPHABET_CODES[sextet];
  101. break;
  102. }
  103. buf[pos++] = ALPHABET_CODES[sextet | CONTINUATION_BIT];
  104. }
  105. state.pos = pos;
  106. };
  107. /**
  108. * @param {{ buf: Uint8Array, pos: number }} state state
  109. * @returns {string} the mappings accumulated so far
  110. */
  111. const bufferToString = (state) => {
  112. if (state.pos === 0) return "";
  113. // The mappings alphabet is pure ASCII, so a latin1 decode is exact and
  114. // needs no re-encoding pass.
  115. return Buffer.from(state.buf.buffer, 0, state.pos).toString("latin1");
  116. };
  117. /**
  118. * @returns {MappingsWriter} writer
  119. */
  120. const createFullMappingsWriter = () => {
  121. const state = createBufferState();
  122. let currentLine = 1;
  123. let currentColumn = 0;
  124. let currentSourceIndex = 0;
  125. let currentOriginalLine = 1;
  126. let currentOriginalColumn = 0;
  127. let currentNameIndex = 0;
  128. let activeMapping = false;
  129. let activeName = false;
  130. let initial = true;
  131. return {
  132. add(
  133. generatedLine,
  134. generatedColumn,
  135. sourceIndex,
  136. originalLine,
  137. originalColumn,
  138. nameIndex,
  139. ) {
  140. if (activeMapping && currentLine === generatedLine) {
  141. // A mapping is still active
  142. if (
  143. sourceIndex === currentSourceIndex &&
  144. originalLine === currentOriginalLine &&
  145. originalColumn === currentOriginalColumn &&
  146. !activeName &&
  147. nameIndex < 0
  148. ) {
  149. // avoid repeating the same original mapping
  150. return;
  151. }
  152. }
  153. // No mapping is active
  154. else if (sourceIndex < 0) {
  155. // avoid writing unneccessary generated mappings
  156. return;
  157. }
  158. // Reserve the worst case for one mapping in a single check:
  159. // line separators + 5 VLQ values of up to 7 sextets each.
  160. const lineDiff = generatedLine - currentLine;
  161. const buf = ensure(state, (lineDiff > 0 ? lineDiff : 1) + 35);
  162. if (lineDiff > 0) {
  163. // Consecutive lines (diff === 1) are the dominant case.
  164. buf[state.pos++] = CH_SEMICOLON;
  165. for (let i = 1; i < lineDiff; i++) buf[state.pos++] = CH_SEMICOLON;
  166. currentLine = generatedLine;
  167. currentColumn = 0;
  168. initial = false;
  169. } else if (initial) {
  170. initial = false;
  171. } else {
  172. buf[state.pos++] = CH_COMMA;
  173. }
  174. writeValueBytes(state, generatedColumn - currentColumn);
  175. currentColumn = generatedColumn;
  176. if (sourceIndex >= 0) {
  177. activeMapping = true;
  178. if (sourceIndex === currentSourceIndex) {
  179. buf[state.pos++] = CH_A;
  180. } else {
  181. writeValueBytes(state, sourceIndex - currentSourceIndex);
  182. currentSourceIndex = sourceIndex;
  183. }
  184. writeValueBytes(state, originalLine - currentOriginalLine);
  185. currentOriginalLine = originalLine;
  186. if (originalColumn === currentOriginalColumn) {
  187. buf[state.pos++] = CH_A;
  188. } else {
  189. writeValueBytes(state, originalColumn - currentOriginalColumn);
  190. currentOriginalColumn = originalColumn;
  191. }
  192. if (nameIndex >= 0) {
  193. writeValueBytes(state, nameIndex - currentNameIndex);
  194. currentNameIndex = nameIndex;
  195. activeName = true;
  196. } else {
  197. activeName = false;
  198. }
  199. } else {
  200. activeMapping = false;
  201. }
  202. },
  203. finish: () => bufferToString(state),
  204. };
  205. };
  206. const createFullMappingsSerializer = () => {
  207. let currentLine = 1;
  208. let currentColumn = 0;
  209. let currentSourceIndex = 0;
  210. let currentOriginalLine = 1;
  211. let currentOriginalColumn = 0;
  212. let currentNameIndex = 0;
  213. let activeMapping = false;
  214. let activeName = false;
  215. let initial = true;
  216. /** @type {MappingsSerializer} */
  217. return (
  218. generatedLine,
  219. generatedColumn,
  220. sourceIndex,
  221. originalLine,
  222. originalColumn,
  223. nameIndex,
  224. ) => {
  225. if (activeMapping && currentLine === generatedLine) {
  226. // A mapping is still active
  227. if (
  228. sourceIndex === currentSourceIndex &&
  229. originalLine === currentOriginalLine &&
  230. originalColumn === currentOriginalColumn &&
  231. !activeName &&
  232. nameIndex < 0
  233. ) {
  234. // avoid repeating the same original mapping
  235. return "";
  236. }
  237. }
  238. // No mapping is active
  239. else if (sourceIndex < 0) {
  240. // avoid writing unneccessary generated mappings
  241. return "";
  242. }
  243. let str;
  244. if (currentLine < generatedLine) {
  245. // Consecutive lines (diff === 1) are the dominant case; avoid the
  246. // `.repeat()` call entirely for them.
  247. str =
  248. generatedLine === currentLine + 1
  249. ? ";"
  250. : ";".repeat(generatedLine - currentLine);
  251. currentLine = generatedLine;
  252. currentColumn = 0;
  253. initial = false;
  254. } else if (initial) {
  255. str = "";
  256. initial = false;
  257. } else {
  258. str = ",";
  259. }
  260. str = writeValue(str, generatedColumn - currentColumn);
  261. currentColumn = generatedColumn;
  262. if (sourceIndex >= 0) {
  263. activeMapping = true;
  264. if (sourceIndex === currentSourceIndex) {
  265. str += "A";
  266. } else {
  267. str = writeValue(str, sourceIndex - currentSourceIndex);
  268. currentSourceIndex = sourceIndex;
  269. }
  270. str = writeValue(str, originalLine - currentOriginalLine);
  271. currentOriginalLine = originalLine;
  272. if (originalColumn === currentOriginalColumn) {
  273. str += "A";
  274. } else {
  275. str = writeValue(str, originalColumn - currentOriginalColumn);
  276. currentOriginalColumn = originalColumn;
  277. }
  278. if (nameIndex >= 0) {
  279. str = writeValue(str, nameIndex - currentNameIndex);
  280. currentNameIndex = nameIndex;
  281. activeName = true;
  282. } else {
  283. activeName = false;
  284. }
  285. } else {
  286. activeMapping = false;
  287. }
  288. return str;
  289. };
  290. };
  291. const createLinesOnlyMappingsSerializer = () => {
  292. let lastWrittenLine = 0;
  293. let currentLine = 1;
  294. let currentSourceIndex = 0;
  295. let currentOriginalLine = 1;
  296. /** @type {MappingsSerializer} */
  297. return (
  298. generatedLine,
  299. _generatedColumn,
  300. sourceIndex,
  301. originalLine,
  302. _originalColumn,
  303. _nameIndex,
  304. ) => {
  305. if (sourceIndex < 0) {
  306. // avoid writing generated mappings at all
  307. return "";
  308. }
  309. if (lastWrittenLine === generatedLine) {
  310. // avoid writing multiple original mappings per line
  311. return "";
  312. }
  313. let str;
  314. lastWrittenLine = generatedLine;
  315. if (generatedLine === currentLine + 1) {
  316. currentLine = generatedLine;
  317. if (sourceIndex === currentSourceIndex) {
  318. if (originalLine === currentOriginalLine + 1) {
  319. currentOriginalLine = originalLine;
  320. return ";AACA";
  321. }
  322. str = ";AA";
  323. str = writeValue(str, originalLine - currentOriginalLine);
  324. currentOriginalLine = originalLine;
  325. return `${str}A`;
  326. }
  327. str = ";A";
  328. str = writeValue(str, sourceIndex - currentSourceIndex);
  329. currentSourceIndex = sourceIndex;
  330. str = writeValue(str, originalLine - currentOriginalLine);
  331. currentOriginalLine = originalLine;
  332. return `${str}A`;
  333. }
  334. str = ";".repeat(generatedLine - currentLine);
  335. currentLine = generatedLine;
  336. if (sourceIndex === currentSourceIndex) {
  337. if (originalLine === currentOriginalLine + 1) {
  338. currentOriginalLine = originalLine;
  339. return `${str}AACA`;
  340. }
  341. str += "AA";
  342. str = writeValue(str, originalLine - currentOriginalLine);
  343. currentOriginalLine = originalLine;
  344. return `${str}A`;
  345. }
  346. str += "A";
  347. str = writeValue(str, sourceIndex - currentSourceIndex);
  348. currentSourceIndex = sourceIndex;
  349. str = writeValue(str, originalLine - currentOriginalLine);
  350. currentOriginalLine = originalLine;
  351. return `${str}A`;
  352. };
  353. };
  354. /**
  355. * Lines-only mappings emit at most one short — usually constant — segment
  356. * per generated line, so the classic string encoding plus cons-string
  357. * append is already optimal there (measurably faster than byte-writing).
  358. * Only the full serializer, which emits per token, benefits from the byte
  359. * buffer. The encoding below mirrors `createLinesOnlyMappingsSerializer`,
  360. * inlined so `add` costs a single call.
  361. * @returns {MappingsWriter} writer
  362. */
  363. const createLinesOnlyMappingsWriter = () => {
  364. let mappings = "";
  365. let lastWrittenLine = 0;
  366. let currentLine = 1;
  367. let currentSourceIndex = 0;
  368. let currentOriginalLine = 1;
  369. return {
  370. add(
  371. generatedLine,
  372. _generatedColumn,
  373. sourceIndex,
  374. originalLine,
  375. _originalColumn,
  376. _nameIndex,
  377. ) {
  378. if (sourceIndex < 0) {
  379. // avoid writing generated mappings at all
  380. return;
  381. }
  382. if (lastWrittenLine === generatedLine) {
  383. // avoid writing multiple original mappings per line
  384. return;
  385. }
  386. let str;
  387. lastWrittenLine = generatedLine;
  388. if (generatedLine === currentLine + 1) {
  389. currentLine = generatedLine;
  390. if (sourceIndex === currentSourceIndex) {
  391. if (originalLine === currentOriginalLine + 1) {
  392. currentOriginalLine = originalLine;
  393. mappings += ";AACA";
  394. return;
  395. }
  396. str = ";AA";
  397. str = writeValue(str, originalLine - currentOriginalLine);
  398. currentOriginalLine = originalLine;
  399. mappings += `${str}A`;
  400. return;
  401. }
  402. str = ";A";
  403. str = writeValue(str, sourceIndex - currentSourceIndex);
  404. currentSourceIndex = sourceIndex;
  405. str = writeValue(str, originalLine - currentOriginalLine);
  406. currentOriginalLine = originalLine;
  407. mappings += `${str}A`;
  408. return;
  409. }
  410. str = ";".repeat(generatedLine - currentLine);
  411. currentLine = generatedLine;
  412. if (sourceIndex === currentSourceIndex) {
  413. if (originalLine === currentOriginalLine + 1) {
  414. currentOriginalLine = originalLine;
  415. mappings += `${str}AACA`;
  416. return;
  417. }
  418. str += "AA";
  419. str = writeValue(str, originalLine - currentOriginalLine);
  420. currentOriginalLine = originalLine;
  421. mappings += `${str}A`;
  422. return;
  423. }
  424. str += "A";
  425. str = writeValue(str, sourceIndex - currentSourceIndex);
  426. currentSourceIndex = sourceIndex;
  427. str = writeValue(str, originalLine - currentOriginalLine);
  428. currentOriginalLine = originalLine;
  429. mappings += `${str}A`;
  430. },
  431. finish: () => mappings,
  432. };
  433. };
  434. /**
  435. * @param {{ columns?: boolean }=} options options
  436. * @returns {MappingsSerializer} mappings serializer
  437. */
  438. const createMappingsSerializer = (options) => {
  439. const linesOnly = options && options.columns === false;
  440. return linesOnly
  441. ? createLinesOnlyMappingsSerializer()
  442. : createFullMappingsSerializer();
  443. };
  444. /**
  445. * @param {{ columns?: boolean }=} options options
  446. * @returns {MappingsWriter} push-based mappings writer
  447. */
  448. const createMappingsWriter = (options) => {
  449. const linesOnly = options && options.columns === false;
  450. return linesOnly
  451. ? createLinesOnlyMappingsWriter()
  452. : createFullMappingsWriter();
  453. };
  454. module.exports = createMappingsSerializer;
  455. module.exports.createMappingsWriter = createMappingsWriter;