PrefixSource.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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").HashLike} HashLike */
  11. /** @typedef {import("./Source").MapOptions} MapOptions */
  12. /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
  13. /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
  14. /** @typedef {import("./Source").SourceValue} SourceValue */
  15. /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
  16. /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
  17. /** @typedef {import("./helpers/streamChunks").OnName} OnName */
  18. /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
  19. /** @typedef {import("./helpers/streamChunks").Options} Options */
  20. // `/\n/g` (no lookahead) lets V8's regex compiler take its
  21. // literal-character fast path; the previous `/\n(?=.|\s)/g` form
  22. // disabled it. Output stays identical because `buildPrefixed` strips
  23. // the spurious trailing prefix when the input ended with a newline.
  24. const NEWLINE_REGEX = /\n/g;
  25. /**
  26. * Prepend `prefix` and insert `prefix` after every newline that has
  27. * content following — the original `/\n(?=.|\s)/g` semantics, but
  28. * implemented as a fast-path regex + tail-strip.
  29. * @param {string} prefix prefix
  30. * @param {string} node underlying source string
  31. * @returns {string} prefixed string
  32. */
  33. const buildPrefixed = (prefix, node) => {
  34. if (prefix.length === 0) return node;
  35. const replaced = node.replace(NEWLINE_REGEX, `\n${prefix}`);
  36. const len = node.length;
  37. // `/\n/g` matches the trailing newline too, so the replace appended
  38. // a spurious prefix at the end. Trim it.
  39. if (len > 0 && node.charCodeAt(len - 1) === 10) {
  40. return prefix + replaced.slice(0, replaced.length - prefix.length);
  41. }
  42. return prefix + replaced;
  43. };
  44. class PrefixSource extends Source {
  45. /**
  46. * @param {string} prefix prefix
  47. * @param {string | Buffer | Source} source source
  48. */
  49. constructor(prefix, source) {
  50. super();
  51. /**
  52. * @private
  53. * @type {string}
  54. */
  55. this._prefix = prefix;
  56. /**
  57. * @private
  58. * @type {Source}
  59. */
  60. this._source =
  61. typeof source === "string" || Buffer.isBuffer(source)
  62. ? new RawSource(source, true)
  63. : source;
  64. }
  65. getPrefix() {
  66. return this._prefix;
  67. }
  68. original() {
  69. return this._source;
  70. }
  71. /**
  72. * @returns {SourceValue} source
  73. */
  74. source() {
  75. return buildPrefixed(
  76. this._prefix,
  77. /** @type {string} */ (this._source.source()),
  78. );
  79. }
  80. // buffer() / buffers() / size() inherit from Source.prototype.
  81. // Source.buffer() does Buffer.from(this.source(), "utf8") — cheaper
  82. // in CodSpeed instruction count than any safe override we tried
  83. // (caching is unsafe with mutable child; a JS-side splice loop
  84. // regressed ~5x in instruction count). Speeding up source() via the
  85. // simpler regex above lifts buffer(), buffers(), size(), and any
  86. // other `this.source()`-using path with no override needed.
  87. /**
  88. * @param {MapOptions=} options map options
  89. * @returns {RawSourceMap | null} map
  90. */
  91. map(options) {
  92. return getMap(this, options);
  93. }
  94. /**
  95. * @param {MapOptions=} options map options
  96. * @returns {SourceAndMap} source and map
  97. */
  98. sourceAndMap(options) {
  99. return getSourceAndMap(this, options);
  100. }
  101. /**
  102. * @param {Options} options options
  103. * @param {OnChunk} onChunk called for each chunk of code
  104. * @param {OnSource} onSource called for each source
  105. * @param {OnName} onName called for each name
  106. * @returns {GeneratedSourceInfo} generated source info
  107. */
  108. streamChunks(options, onChunk, onSource, onName) {
  109. const prefix = this._prefix;
  110. const prefixOffset = prefix.length;
  111. const linesOnly = Boolean(options && options.columns === false);
  112. const { generatedLine, generatedColumn, source } = streamChunks(
  113. this._source,
  114. options,
  115. (
  116. chunk,
  117. generatedLine,
  118. generatedColumn,
  119. sourceIndex,
  120. originalLine,
  121. originalColumn,
  122. nameIndex,
  123. ) => {
  124. if (generatedColumn !== 0) {
  125. // In the middle of the line, we just adject the column
  126. generatedColumn += prefixOffset;
  127. } else if (chunk !== undefined) {
  128. // At the start of the line, when we have source content
  129. // add the prefix as generated mapping
  130. // (in lines only mode we just add it to the original mapping
  131. // for performance reasons)
  132. if (linesOnly || sourceIndex < 0) {
  133. chunk = prefix + chunk;
  134. } else if (prefixOffset > 0) {
  135. onChunk(prefix, generatedLine, generatedColumn, -1, -1, -1, -1);
  136. generatedColumn += prefixOffset;
  137. }
  138. } else if (!linesOnly) {
  139. // Without source content, we only need to adject the column info
  140. // expect in lines only mode where prefix is added to original mapping
  141. generatedColumn += prefixOffset;
  142. }
  143. onChunk(
  144. chunk,
  145. generatedLine,
  146. generatedColumn,
  147. sourceIndex,
  148. originalLine,
  149. originalColumn,
  150. nameIndex,
  151. );
  152. },
  153. onSource,
  154. onName,
  155. );
  156. return {
  157. generatedLine,
  158. generatedColumn:
  159. generatedColumn === 0
  160. ? 0
  161. : prefixOffset + /** @type {number} */ (generatedColumn),
  162. source: source !== undefined ? buildPrefixed(prefix, source) : undefined,
  163. };
  164. }
  165. /**
  166. * @param {HashLike} hash hash
  167. * @returns {void}
  168. */
  169. updateHash(hash) {
  170. hash.update("PrefixSource");
  171. this._source.updateHash(hash);
  172. hash.update(this._prefix);
  173. }
  174. }
  175. module.exports = PrefixSource;