PrefixSource.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const RawSource = require("./RawSource");
  7. const Source = require("./Source");
  8. const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
  9. const streamChunks = require("./helpers/streamChunks");
  10. /** @typedef {import("./Source").ClearCacheOptions} ClearCacheOptions */
  11. /** @typedef {import("./Source").HashLike} HashLike */
  12. /** @typedef {import("./Source").MapOptions} MapOptions */
  13. /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
  14. /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
  15. /** @typedef {import("./Source").SourceValue} SourceValue */
  16. /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  17. /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
  18. /** @typedef {import("./helpers/streamChunks").OnName} OnName */
  19. /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
  20. /** @typedef {import("./helpers/streamChunks").Options} Options */
  21. // `/\n/g` (no lookahead) lets V8's regex compiler take its
  22. // literal-character fast path; the previous `/\n(?=.|\s)/g` form
  23. // disabled it. Output stays identical because `buildPrefixed` strips
  24. // the spurious trailing prefix when the input ended with a newline.
  25. const NEWLINE_REGEX = /\n/g;
  26. /**
  27. * Prepend `prefix` and insert `prefix` after every newline that has
  28. * content following — the original `/\n(?=.|\s)/g` semantics, but
  29. * implemented as a fast-path regex + tail-strip.
  30. * @param {string} prefix prefix
  31. * @param {string} node underlying source string
  32. * @returns {string} prefixed string
  33. */
  34. const buildPrefixed = (prefix, node) => {
  35. if (prefix.length === 0) return node;
  36. const replaced = node.replace(NEWLINE_REGEX, `\n${prefix}`);
  37. const len = node.length;
  38. // `/\n/g` matches the trailing newline too, so the replace appended
  39. // a spurious prefix at the end. Trim it.
  40. if (len > 0 && node.charCodeAt(len - 1) === 10) {
  41. return prefix + replaced.slice(0, replaced.length - prefix.length);
  42. }
  43. return prefix + replaced;
  44. };
  45. class PrefixSource extends Source {
  46. /**
  47. * @param {string} prefix prefix
  48. * @param {string | Buffer | Source} source source
  49. */
  50. constructor(prefix, source) {
  51. super();
  52. /**
  53. * @private
  54. * @type {string}
  55. */
  56. this._prefix = prefix;
  57. /**
  58. * @private
  59. * @type {Source}
  60. */
  61. this._source =
  62. typeof source === "string" || Buffer.isBuffer(source)
  63. ? new RawSource(source, true)
  64. : source;
  65. }
  66. getPrefix() {
  67. return this._prefix;
  68. }
  69. original() {
  70. return this._source;
  71. }
  72. /**
  73. * @returns {SourceValue} source
  74. */
  75. source() {
  76. return buildPrefixed(
  77. this._prefix,
  78. /** @type {string} */ (this._source.source()),
  79. );
  80. }
  81. // buffer() / buffers() / size() inherit from Source.prototype.
  82. // Source.buffer() does Buffer.from(this.source(), "utf8") — cheaper
  83. // in CodSpeed instruction count than any safe override we tried
  84. // (caching is unsafe with mutable child; a JS-side splice loop
  85. // regressed ~5x in instruction count). Speeding up source() via the
  86. // simpler regex above lifts buffer(), buffers(), size(), and any
  87. // other `this.source()`-using path with no override needed.
  88. /**
  89. * @param {MapOptions=} options map options
  90. * @returns {RawSourceMap | null} map
  91. */
  92. map(options) {
  93. return getMap(this, options);
  94. }
  95. /**
  96. * @param {MapOptions=} options map options
  97. * @returns {SourceAndMap} source and map
  98. */
  99. sourceAndMap(options) {
  100. return getSourceAndMap(this, options);
  101. }
  102. /**
  103. * @param {Options} options options
  104. * @param {OnChunk} onChunk called for each chunk of code
  105. * @param {OnSource} onSource called for each source
  106. * @param {OnName} onName called for each name
  107. * @returns {GeneratedSourceInfo} generated source info
  108. */
  109. streamChunks(options, onChunk, onSource, onName) {
  110. const prefix = this._prefix;
  111. const prefixOffset = prefix.length;
  112. const linesOnly = Boolean(options && options.columns === false);
  113. const { generatedLine, generatedColumn, source } = streamChunks(
  114. this._source,
  115. options,
  116. (
  117. chunk,
  118. generatedLine,
  119. generatedColumn,
  120. sourceIndex,
  121. originalLine,
  122. originalColumn,
  123. nameIndex,
  124. ) => {
  125. if (generatedColumn !== 0) {
  126. // In the middle of the line, we just adject the column
  127. generatedColumn += prefixOffset;
  128. } else if (chunk !== undefined) {
  129. // At the start of the line, when we have source content
  130. // add the prefix as generated mapping
  131. // (in lines only mode we just add it to the original mapping
  132. // for performance reasons)
  133. if (linesOnly || sourceIndex < 0) {
  134. chunk = prefix + chunk;
  135. } else if (prefixOffset > 0) {
  136. onChunk(prefix, generatedLine, generatedColumn, -1, -1, -1, -1);
  137. generatedColumn += prefixOffset;
  138. }
  139. } else if (!linesOnly) {
  140. // Without source content, we only need to adject the column info
  141. // expect in lines only mode where prefix is added to original mapping
  142. generatedColumn += prefixOffset;
  143. }
  144. onChunk(
  145. chunk,
  146. generatedLine,
  147. generatedColumn,
  148. sourceIndex,
  149. originalLine,
  150. originalColumn,
  151. nameIndex,
  152. );
  153. },
  154. onSource,
  155. onName,
  156. );
  157. return {
  158. generatedLine,
  159. generatedColumn:
  160. generatedColumn === 0
  161. ? 0
  162. : prefixOffset + /** @type {number} */ (generatedColumn),
  163. source: source !== undefined ? buildPrefixed(prefix, source) : undefined,
  164. };
  165. }
  166. /**
  167. * @param {HashLike} hash hash
  168. * @returns {void}
  169. */
  170. updateHash(hash) {
  171. hash.update("PrefixSource");
  172. this._source.updateHash(hash);
  173. hash.update(this._prefix);
  174. }
  175. /**
  176. * Release cached data held by this source. clearCache is a memory
  177. * hint: it never affects correctness or output, only how expensive
  178. * the next read is. Subclasses override; the base is a no-op so
  179. * every Source supports the call. Composite sources always recurse
  180. * into wrapped sources. When the same child is reachable via several
  181. * parents (e.g. modules shared across webpack chunks), pass a shared
  182. * `visited` WeakSet so each subtree is walked at most once.
  183. * Not safe to call concurrently with source/map/sourceAndMap/
  184. * streamChunks/updateHash on the same instance.
  185. * @param {ClearCacheOptions=} options selectors
  186. * @param {WeakSet<Source>=} visited de-duplication set shared across calls
  187. * @returns {void}
  188. */
  189. clearCache(options, visited) {
  190. if (visited !== undefined && visited.has(this)) return;
  191. let v = visited;
  192. if (v === undefined) v = new WeakSet();
  193. v.add(this);
  194. this._source.clearCache(options, v);
  195. }
  196. }
  197. module.exports = PrefixSource;