OriginalSource.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Source = require("./Source");
  7. const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
  8. const getGeneratedSourceInfo = require("./helpers/getGeneratedSourceInfo");
  9. const { eachPotentialToken } = require("./helpers/splitIntoPotentialTokens");
  10. const {
  11. isDualStringBufferCachingEnabled,
  12. } = require("./helpers/stringBufferUtils");
  13. /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
  14. /** @typedef {import("./Source").HashLike} HashLike */
  15. /** @typedef {import("./Source").MapOptions} MapOptions */
  16. /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
  17. /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
  18. /** @typedef {import("./Source").SourceValue} SourceValue */
  19. /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  20. /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
  21. /** @typedef {import("./helpers/streamChunks").OnName} OnName */
  22. /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
  23. /** @typedef {import("./helpers/streamChunks").Options} Options */
  24. class OriginalSource extends Source {
  25. /**
  26. * @param {string | Buffer} value value
  27. * @param {string} name name
  28. */
  29. constructor(value, name) {
  30. super();
  31. const isBuffer = Buffer.isBuffer(value);
  32. /**
  33. * @private
  34. * @type {undefined | string}
  35. */
  36. this._value = isBuffer ? undefined : value;
  37. /**
  38. * @private
  39. * @type {undefined | Buffer}
  40. */
  41. this._valueAsBuffer = isBuffer ? value : undefined;
  42. /**
  43. * @private
  44. * @type {string}
  45. */
  46. this._name = name;
  47. }
  48. getName() {
  49. return this._name;
  50. }
  51. /**
  52. * @returns {SourceValue} source
  53. */
  54. source() {
  55. if (this._value === undefined) {
  56. const value =
  57. /** @type {Buffer} */
  58. (this._valueAsBuffer).toString("utf8");
  59. if (isDualStringBufferCachingEnabled()) {
  60. this._value = value;
  61. }
  62. return value;
  63. }
  64. return this._value;
  65. }
  66. /**
  67. * @returns {Buffer} buffer
  68. */
  69. buffer() {
  70. if (this._valueAsBuffer === undefined) {
  71. const value = Buffer.from(/** @type {string} */ (this._value), "utf8");
  72. if (isDualStringBufferCachingEnabled()) {
  73. this._valueAsBuffer = value;
  74. }
  75. return value;
  76. }
  77. return this._valueAsBuffer;
  78. }
  79. /**
  80. * @returns {number} size
  81. */
  82. size() {
  83. if (this._cachedSize !== undefined) return this._cachedSize;
  84. if (this._valueAsBuffer !== undefined) {
  85. return (this._cachedSize = this._valueAsBuffer.length);
  86. }
  87. return (this._cachedSize = Buffer.byteLength(
  88. /** @type {string} */ (this._value),
  89. "utf8",
  90. ));
  91. }
  92. /**
  93. * @param {MapOptions=} options map options
  94. * @returns {RawSourceMap | null} map
  95. */
  96. map(options) {
  97. return getMap(this, options);
  98. }
  99. /**
  100. * @param {MapOptions=} options map options
  101. * @returns {SourceAndMap} source and map
  102. */
  103. sourceAndMap(options) {
  104. return getSourceAndMap(this, options);
  105. }
  106. /**
  107. * @param {Options} options options
  108. * @param {OnChunk} onChunk called for each chunk of code
  109. * @param {OnSource} onSource called for each source
  110. * @param {OnName} _onName called for each name
  111. * @returns {GeneratedSourceInfo} generated source info
  112. */
  113. streamChunks(options, onChunk, onSource, _onName) {
  114. if (this._value === undefined) {
  115. this._value =
  116. /** @type {Buffer} */
  117. (this._valueAsBuffer).toString("utf8");
  118. }
  119. onSource(0, this._name, this._value);
  120. const finalSource = Boolean(options && options.finalSource);
  121. if (!options || options.columns !== false) {
  122. // With column info we need to walk every potential token. The
  123. // scan is streamed by offset (see `eachPotentialToken`) so we
  124. // only allocate a chunk substring when one is actually emitted —
  125. // and `map()` / `sourceAndMap()` set `finalSource`, in which case
  126. // the chunk is dropped and no substring is allocated at all.
  127. const value = this._value;
  128. let line = 1;
  129. let column = 0;
  130. eachPotentialToken(value, (start, end, newline) => {
  131. const length = end - start;
  132. if (newline && length === 1) {
  133. if (!finalSource) onChunk("\n", line, column, -1, -1, -1, -1);
  134. } else {
  135. const chunk = finalSource ? undefined : value.slice(start, end);
  136. onChunk(chunk, line, column, 0, line, column, -1);
  137. }
  138. if (newline) {
  139. line++;
  140. column = 0;
  141. } else {
  142. column += length;
  143. }
  144. });
  145. return {
  146. generatedLine: line,
  147. generatedColumn: column,
  148. source: finalSource ? value : undefined,
  149. };
  150. } else if (finalSource) {
  151. // Without column info and with final source we only
  152. // need meta info to generate mapping
  153. const result = getGeneratedSourceInfo(this._value);
  154. const { generatedLine, generatedColumn } = result;
  155. if (generatedColumn === 0) {
  156. for (
  157. let line = 1;
  158. line < /** @type {number} */ (generatedLine);
  159. line++
  160. ) {
  161. onChunk(undefined, line, 0, 0, line, 0, -1);
  162. }
  163. } else {
  164. for (
  165. let line = 1;
  166. line <= /** @type {number} */ (generatedLine);
  167. line++
  168. ) {
  169. onChunk(undefined, line, 0, 0, line, 0, -1);
  170. }
  171. }
  172. return result;
  173. }
  174. // Without column info, but also without final source.
  175. // We only get here when (options.columns === false && !finalSource),
  176. // so the source field is always undefined and the chunk arg is always
  177. // the line text. Single-pass scan over newlines avoids the
  178. // splitIntoLines array allocation.
  179. const value = this._value;
  180. const len = value.length;
  181. if (len === 0) {
  182. return { generatedLine: 1, generatedColumn: 0, source: undefined };
  183. }
  184. let line = 1;
  185. let i = 0;
  186. while (i < len) {
  187. const n = value.indexOf("\n", i);
  188. if (n === -1) {
  189. const lastLine = i === 0 ? value : value.slice(i);
  190. onChunk(lastLine, line, 0, 0, line, 0, -1);
  191. return {
  192. generatedLine: line,
  193. generatedColumn: lastLine.length,
  194. source: undefined,
  195. };
  196. }
  197. const chunk = n === i ? "\n" : value.slice(i, n + 1);
  198. onChunk(chunk, line, 0, 0, line, 0, -1);
  199. line++;
  200. i = n + 1;
  201. }
  202. // Source ended with a newline.
  203. return { generatedLine: line, generatedColumn: 0, source: undefined };
  204. }
  205. /**
  206. * Release cached data held by this source. clearCache is a memory
  207. * hint: it never affects correctness or output, only how expensive
  208. * the next read is. Subclasses override; the base is a no-op so
  209. * every Source supports the call. Composite sources always recurse
  210. * into wrapped sources. When the same child is reachable via several
  211. * parents (e.g. modules shared across webpack chunks), pass a shared
  212. * `visited` WeakSet so each subtree is walked at most once.
  213. * Not safe to call concurrently with source/map/sourceAndMap/
  214. * streamChunks/updateHash on the same instance.
  215. * @param {ClearCacheOptions=} options selectors
  216. * @param {WeakSet<Source>=} visited de-duplication set shared across calls
  217. * @returns {void}
  218. */
  219. clearCache(options, visited) {
  220. if (visited !== undefined) {
  221. if (visited.has(this)) return;
  222. visited.add(this);
  223. }
  224. if (options !== undefined && options.source === false) return;
  225. if (this._value !== undefined && this._valueAsBuffer !== undefined) {
  226. // When both forms are cached, drop the string (UTF-16 in V8 is
  227. // ~2 bytes/char) and keep the more compact buffer. source() will
  228. // rehydrate the string on demand.
  229. this._value = undefined;
  230. }
  231. }
  232. /**
  233. * @param {HashLike} hash hash
  234. * @returns {void}
  235. */
  236. updateHash(hash) {
  237. hash.update("OriginalSource");
  238. hash.update(this.buffer());
  239. hash.update(this._name || "");
  240. }
  241. }
  242. module.exports = OriginalSource;