OriginalSource.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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 splitIntoPotentialTokens = require("./helpers/splitIntoPotentialTokens");
  10. const {
  11. isDualStringBufferCachingEnabled,
  12. } = require("./helpers/stringBufferUtils");
  13. /** @typedef {import("./Source").HashLike} HashLike */
  14. /** @typedef {import("./Source").MapOptions} MapOptions */
  15. /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
  16. /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
  17. /** @typedef {import("./Source").SourceValue} SourceValue */
  18. /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  19. /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
  20. /** @typedef {import("./helpers/streamChunks").OnName} OnName */
  21. /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
  22. /** @typedef {import("./helpers/streamChunks").Options} Options */
  23. class OriginalSource extends Source {
  24. /**
  25. * @param {string | Buffer} value value
  26. * @param {string} name name
  27. */
  28. constructor(value, name) {
  29. super();
  30. const isBuffer = Buffer.isBuffer(value);
  31. /**
  32. * @private
  33. * @type {undefined | string}
  34. */
  35. this._value = isBuffer ? undefined : value;
  36. /**
  37. * @private
  38. * @type {undefined | Buffer}
  39. */
  40. this._valueAsBuffer = isBuffer ? value : undefined;
  41. /**
  42. * @private
  43. * @type {string}
  44. */
  45. this._name = name;
  46. }
  47. getName() {
  48. return this._name;
  49. }
  50. /**
  51. * @returns {SourceValue} source
  52. */
  53. source() {
  54. if (this._value === undefined) {
  55. const value =
  56. /** @type {Buffer} */
  57. (this._valueAsBuffer).toString("utf8");
  58. if (isDualStringBufferCachingEnabled()) {
  59. this._value = value;
  60. }
  61. return value;
  62. }
  63. return this._value;
  64. }
  65. /**
  66. * @returns {Buffer} buffer
  67. */
  68. buffer() {
  69. if (this._valueAsBuffer === undefined) {
  70. const value = Buffer.from(/** @type {string} */ (this._value), "utf8");
  71. if (isDualStringBufferCachingEnabled()) {
  72. this._valueAsBuffer = value;
  73. }
  74. return value;
  75. }
  76. return this._valueAsBuffer;
  77. }
  78. /**
  79. * @returns {number} size
  80. */
  81. size() {
  82. if (this._cachedSize !== undefined) return this._cachedSize;
  83. if (this._valueAsBuffer !== undefined) {
  84. return (this._cachedSize = this._valueAsBuffer.length);
  85. }
  86. return (this._cachedSize = Buffer.byteLength(
  87. /** @type {string} */ (this._value),
  88. "utf8",
  89. ));
  90. }
  91. /**
  92. * @param {MapOptions=} options map options
  93. * @returns {RawSourceMap | null} map
  94. */
  95. map(options) {
  96. return getMap(this, options);
  97. }
  98. /**
  99. * @param {MapOptions=} options map options
  100. * @returns {SourceAndMap} source and map
  101. */
  102. sourceAndMap(options) {
  103. return getSourceAndMap(this, options);
  104. }
  105. /**
  106. * @param {Options} options options
  107. * @param {OnChunk} onChunk called for each chunk of code
  108. * @param {OnSource} onSource called for each source
  109. * @param {OnName} _onName called for each name
  110. * @returns {GeneratedSourceInfo} generated source info
  111. */
  112. streamChunks(options, onChunk, onSource, _onName) {
  113. if (this._value === undefined) {
  114. this._value =
  115. /** @type {Buffer} */
  116. (this._valueAsBuffer).toString("utf8");
  117. }
  118. onSource(0, this._name, this._value);
  119. const finalSource = Boolean(options && options.finalSource);
  120. if (!options || options.columns !== false) {
  121. // With column info we need to read all lines and split them
  122. const matches = splitIntoPotentialTokens(this._value);
  123. let line = 1;
  124. let column = 0;
  125. if (matches !== null) {
  126. for (const match of matches) {
  127. const isEndOfLine = match.endsWith("\n");
  128. if (isEndOfLine && match.length === 1) {
  129. if (!finalSource) onChunk(match, line, column, -1, -1, -1, -1);
  130. } else {
  131. const chunk = finalSource ? undefined : match;
  132. onChunk(chunk, line, column, 0, line, column, -1);
  133. }
  134. if (isEndOfLine) {
  135. line++;
  136. column = 0;
  137. } else {
  138. column += match.length;
  139. }
  140. }
  141. }
  142. return {
  143. generatedLine: line,
  144. generatedColumn: column,
  145. source: finalSource ? this._value : undefined,
  146. };
  147. } else if (finalSource) {
  148. // Without column info and with final source we only
  149. // need meta info to generate mapping
  150. const result = getGeneratedSourceInfo(this._value);
  151. const { generatedLine, generatedColumn } = result;
  152. if (generatedColumn === 0) {
  153. for (
  154. let line = 1;
  155. line < /** @type {number} */ (generatedLine);
  156. line++
  157. ) {
  158. onChunk(undefined, line, 0, 0, line, 0, -1);
  159. }
  160. } else {
  161. for (
  162. let line = 1;
  163. line <= /** @type {number} */ (generatedLine);
  164. line++
  165. ) {
  166. onChunk(undefined, line, 0, 0, line, 0, -1);
  167. }
  168. }
  169. return result;
  170. }
  171. // Without column info, but also without final source.
  172. // We only get here when (options.columns === false && !finalSource),
  173. // so the source field is always undefined and the chunk arg is always
  174. // the line text. Single-pass scan over newlines avoids the
  175. // splitIntoLines array allocation.
  176. const value = this._value;
  177. const len = value.length;
  178. if (len === 0) {
  179. return { generatedLine: 1, generatedColumn: 0, source: undefined };
  180. }
  181. let line = 1;
  182. let i = 0;
  183. while (i < len) {
  184. const n = value.indexOf("\n", i);
  185. if (n === -1) {
  186. const lastLine = i === 0 ? value : value.slice(i);
  187. onChunk(lastLine, line, 0, 0, line, 0, -1);
  188. return {
  189. generatedLine: line,
  190. generatedColumn: lastLine.length,
  191. source: undefined,
  192. };
  193. }
  194. const chunk = n === i ? "\n" : value.slice(i, n + 1);
  195. onChunk(chunk, line, 0, 0, line, 0, -1);
  196. line++;
  197. i = n + 1;
  198. }
  199. // Source ended with a newline.
  200. return { generatedLine: line, generatedColumn: 0, source: undefined };
  201. }
  202. /**
  203. * @param {HashLike} hash hash
  204. * @returns {void}
  205. */
  206. updateHash(hash) {
  207. hash.update("OriginalSource");
  208. hash.update(this.buffer());
  209. hash.update(this._name || "");
  210. }
  211. }
  212. module.exports = OriginalSource;