getFromStreamChunks.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { createMappingsWriter } = require("./createMappingsSerializer");
  7. /** @typedef {import("../Source").RawSourceMap} RawSourceMap */
  8. /** @typedef {import("../Source").SourceAndMap} SourceAndMap */
  9. /** @typedef {import("./streamChunks").Options} Options */
  10. /** @typedef {import("./streamChunks").StreamChunksFunction} StreamChunksFunction */
  11. /** @typedef {{ streamChunks: StreamChunksFunction }} SourceLikeWithStreamChunks */
  12. /**
  13. * Assign `value` at index `i` of `arr`, padding earlier slots with `null`.
  14. * Hoisted helper so `getMap` and `getSourceAndMap` share one
  15. * implementation. Uses `push` rather than `arr.length = i + 1` because the
  16. * latter forces V8's HOLEY_ELEMENTS kind permanently — push keeps the
  17. * backing store PACKED, which the downstream source-map serializer iterates
  18. * over far more efficiently.
  19. * @template T
  20. * @param {(T | null)[]} arr destination array
  21. * @param {number} i index to assign
  22. * @param {T} value value to assign
  23. * @returns {void}
  24. */
  25. const setAtIndex = (arr, i, value) => {
  26. while (arr.length < i) arr.push(null);
  27. // `arr[i] = value` where `i === arr.length` extends the array exactly
  28. // like `push` (V8 keeps PACKED_ELEMENTS); after the while-loop
  29. // `i <= arr.length` always, so the hole case that would force HOLEY is
  30. // impossible here.
  31. arr[i] = value;
  32. };
  33. /**
  34. * @param {SourceLikeWithStreamChunks} source source
  35. * @param {Options=} options options
  36. * @returns {RawSourceMap | null} map
  37. */
  38. module.exports.getMap = (source, options) => {
  39. /** @type {(string | null)[]} */
  40. const potentialSources = [];
  41. /** @type {(string | null)[]} */
  42. const potentialSourcesContent = [];
  43. /** @type {(string | null)[]} */
  44. const potentialNames = [];
  45. const mappingsWriter = createMappingsWriter(options);
  46. const addMapping = mappingsWriter.add;
  47. source.streamChunks(
  48. { ...options, source: false, finalSource: true },
  49. (
  50. chunk,
  51. generatedLine,
  52. generatedColumn,
  53. sourceIndex,
  54. originalLine,
  55. originalColumn,
  56. nameIndex,
  57. ) => {
  58. addMapping(
  59. generatedLine,
  60. generatedColumn,
  61. sourceIndex,
  62. originalLine,
  63. originalColumn,
  64. nameIndex,
  65. );
  66. },
  67. (sourceIndex, source, sourceContent) => {
  68. setAtIndex(potentialSources, sourceIndex, source);
  69. if (sourceContent !== undefined) {
  70. setAtIndex(potentialSourcesContent, sourceIndex, sourceContent);
  71. }
  72. },
  73. (nameIndex, name) => {
  74. setAtIndex(potentialNames, nameIndex, name);
  75. },
  76. );
  77. const mappings = mappingsWriter.finish();
  78. return mappings.length > 0
  79. ? {
  80. version: 3,
  81. file: "x",
  82. mappings,
  83. // We handle broken sources as `null`, in spec this field should be string, but no information what we should do in such cases if we change type it will be breaking change
  84. sources: /** @type {string[]} */ (potentialSources),
  85. sourcesContent:
  86. potentialSourcesContent.length > 0
  87. ? /** @type {string[]} */ (potentialSourcesContent)
  88. : undefined,
  89. names: /** @type {string[]} */ (potentialNames),
  90. }
  91. : null;
  92. };
  93. /**
  94. * @param {SourceLikeWithStreamChunks} inputSource input source
  95. * @param {Options=} options options
  96. * @returns {SourceAndMap} map
  97. */
  98. module.exports.getSourceAndMap = (inputSource, options) => {
  99. let code = "";
  100. /** @type {(string | null)[]} */
  101. const potentialSources = [];
  102. /** @type {(string | null)[]} */
  103. const potentialSourcesContent = [];
  104. /** @type {(string | null)[]} */
  105. const potentialNames = [];
  106. const mappingsWriter = createMappingsWriter(options);
  107. const addMapping = mappingsWriter.add;
  108. const { source } = inputSource.streamChunks(
  109. // `source: true` overrides any `source: false` a caller forwarded
  110. // (e.g. the streamChunks fallback passing getMap options through
  111. // sourceAndMap) — this helper always consumes the source.
  112. { ...options, finalSource: true, source: true },
  113. (
  114. chunk,
  115. generatedLine,
  116. generatedColumn,
  117. sourceIndex,
  118. originalLine,
  119. originalColumn,
  120. nameIndex,
  121. ) => {
  122. if (chunk !== undefined) code += chunk;
  123. addMapping(
  124. generatedLine,
  125. generatedColumn,
  126. sourceIndex,
  127. originalLine,
  128. originalColumn,
  129. nameIndex,
  130. );
  131. },
  132. (sourceIndex, source, sourceContent) => {
  133. while (potentialSources.length < sourceIndex) {
  134. potentialSources.push(null);
  135. }
  136. potentialSources[sourceIndex] = source;
  137. if (sourceContent !== undefined) {
  138. while (potentialSourcesContent.length < sourceIndex) {
  139. potentialSourcesContent.push(null);
  140. }
  141. potentialSourcesContent[sourceIndex] = sourceContent;
  142. }
  143. },
  144. (nameIndex, name) => {
  145. while (potentialNames.length < nameIndex) {
  146. potentialNames.push(null);
  147. }
  148. potentialNames[nameIndex] = name;
  149. },
  150. );
  151. const mappings = mappingsWriter.finish();
  152. return {
  153. source: source !== undefined ? source : code,
  154. map:
  155. mappings.length > 0
  156. ? {
  157. version: 3,
  158. file: "x",
  159. mappings,
  160. // We handle broken sources as `null`, in spec this field should be string, but no information what we should do in such cases if we change type it will be breaking change
  161. sources: /** @type {string[]} */ (potentialSources),
  162. sourcesContent:
  163. potentialSourcesContent.length > 0
  164. ? /** @type {string[]} */ (potentialSourcesContent)
  165. : undefined,
  166. names: /** @type {string[]} */ (potentialNames),
  167. }
  168. : null,
  169. };
  170. };