ConcatSource.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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("./CompatSource").SourceLike} SourceLike */
  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. /** @typedef {string | Source | SourceLike} Child */
  22. const stringsAsRawSources = new WeakSet();
  23. class ConcatSource extends Source {
  24. /**
  25. * @param {Child[]} args children
  26. */
  27. constructor(...args) {
  28. super();
  29. /**
  30. * @private
  31. * @type {Child[]}
  32. */
  33. this._children = [];
  34. for (const item of args) {
  35. if (item instanceof ConcatSource) {
  36. for (const child of item._children) {
  37. this._children.push(child);
  38. }
  39. } else {
  40. this._children.push(item);
  41. }
  42. }
  43. /**
  44. * @private
  45. * @type {boolean}
  46. */
  47. this._isOptimized = args.length === 0;
  48. }
  49. /**
  50. * @returns {Source[]} children
  51. */
  52. getChildren() {
  53. if (!this._isOptimized) this._optimize();
  54. return /** @type {Source[]} */ (this._children);
  55. }
  56. /**
  57. * @param {Child} item item
  58. * @returns {void}
  59. */
  60. add(item) {
  61. if (item instanceof ConcatSource) {
  62. for (const child of item._children) {
  63. this._children.push(child);
  64. }
  65. } else {
  66. this._children.push(item);
  67. }
  68. this._isOptimized = false;
  69. }
  70. /**
  71. * @param {Child[]} items items
  72. * @returns {void}
  73. */
  74. addAllSkipOptimizing(items) {
  75. for (const item of items) {
  76. this._children.push(item);
  77. }
  78. }
  79. buffer() {
  80. if (!this._isOptimized) this._optimize();
  81. /** @type {Buffer[]} */
  82. const buffers = [];
  83. for (const child of /** @type {SourceLike[]} */ (this._children)) {
  84. if (typeof child.buffer === "function") {
  85. buffers.push(child.buffer());
  86. } else {
  87. const bufferOrString = child.source();
  88. if (Buffer.isBuffer(bufferOrString)) {
  89. buffers.push(bufferOrString);
  90. } else {
  91. // This will not happen
  92. buffers.push(Buffer.from(bufferOrString, "utf8"));
  93. }
  94. }
  95. }
  96. return Buffer.concat(buffers);
  97. }
  98. /**
  99. * @returns {SourceValue} source
  100. */
  101. source() {
  102. if (!this._isOptimized) this._optimize();
  103. let source = "";
  104. for (const child of this._children) {
  105. source += /** @type {Source} */ (child).source();
  106. }
  107. return source;
  108. }
  109. size() {
  110. if (!this._isOptimized) this._optimize();
  111. let size = 0;
  112. for (const child of this._children) {
  113. size += /** @type {Source} */ (child).size();
  114. }
  115. return size;
  116. }
  117. /**
  118. * @param {MapOptions=} options map options
  119. * @returns {RawSourceMap | null} map
  120. */
  121. map(options) {
  122. return getMap(this, options);
  123. }
  124. /**
  125. * @param {MapOptions=} options map options
  126. * @returns {SourceAndMap} source and map
  127. */
  128. sourceAndMap(options) {
  129. return getSourceAndMap(this, options);
  130. }
  131. /**
  132. * @param {Options} options options
  133. * @param {OnChunk} onChunk called for each chunk of code
  134. * @param {OnSource} onSource called for each source
  135. * @param {OnName} onName called for each name
  136. * @returns {GeneratedSourceInfo} generated source info
  137. */
  138. streamChunks(options, onChunk, onSource, onName) {
  139. if (!this._isOptimized) this._optimize();
  140. if (this._children.length === 1) {
  141. return /** @type {ConcatSource[]} */ (this._children)[0].streamChunks(
  142. options,
  143. onChunk,
  144. onSource,
  145. onName,
  146. );
  147. }
  148. let currentLineOffset = 0;
  149. let currentColumnOffset = 0;
  150. const sourceMapping = new Map();
  151. const nameMapping = new Map();
  152. const finalSource = Boolean(options && options.finalSource);
  153. let code = "";
  154. let needToCloseMapping = false;
  155. for (const item of /** @type {Source[]} */ (this._children)) {
  156. /** @type {number[]} */
  157. const sourceIndexMapping = [];
  158. /** @type {number[]} */
  159. const nameIndexMapping = [];
  160. let lastMappingLine = 0;
  161. const { generatedLine, generatedColumn, source } = streamChunks(
  162. item,
  163. options,
  164. // eslint-disable-next-line no-loop-func
  165. (
  166. chunk,
  167. generatedLine,
  168. generatedColumn,
  169. sourceIndex,
  170. originalLine,
  171. originalColumn,
  172. nameIndex,
  173. ) => {
  174. const line = generatedLine + currentLineOffset;
  175. const column =
  176. generatedLine === 1
  177. ? generatedColumn + currentColumnOffset
  178. : generatedColumn;
  179. if (needToCloseMapping) {
  180. if (generatedLine !== 1 || generatedColumn !== 0) {
  181. onChunk(
  182. undefined,
  183. currentLineOffset + 1,
  184. currentColumnOffset,
  185. -1,
  186. -1,
  187. -1,
  188. -1,
  189. );
  190. }
  191. needToCloseMapping = false;
  192. }
  193. const resultSourceIndex =
  194. sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
  195. ? -1
  196. : sourceIndexMapping[sourceIndex];
  197. const resultNameIndex =
  198. nameIndex < 0 || nameIndex >= nameIndexMapping.length
  199. ? -1
  200. : nameIndexMapping[nameIndex];
  201. lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine;
  202. let _chunk;
  203. // When using finalSource, we process the entire source code at once at the end, rather than chunk by chunk
  204. if (finalSource) {
  205. if (chunk !== undefined) code += chunk;
  206. } else {
  207. _chunk = chunk;
  208. }
  209. if (resultSourceIndex < 0) {
  210. onChunk(_chunk, line, column, -1, -1, -1, -1);
  211. } else {
  212. onChunk(
  213. _chunk,
  214. line,
  215. column,
  216. resultSourceIndex,
  217. originalLine,
  218. originalColumn,
  219. resultNameIndex,
  220. );
  221. }
  222. },
  223. (i, source, sourceContent) => {
  224. let globalIndex = sourceMapping.get(source);
  225. if (globalIndex === undefined) {
  226. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  227. onSource(globalIndex, source, sourceContent);
  228. }
  229. sourceIndexMapping[i] = globalIndex;
  230. },
  231. (i, name) => {
  232. let globalIndex = nameMapping.get(name);
  233. if (globalIndex === undefined) {
  234. nameMapping.set(name, (globalIndex = nameMapping.size));
  235. onName(globalIndex, name);
  236. }
  237. nameIndexMapping[i] = globalIndex;
  238. },
  239. );
  240. if (source !== undefined) code += source;
  241. if (
  242. needToCloseMapping &&
  243. (generatedLine !== 1 || generatedColumn !== 0)
  244. ) {
  245. onChunk(
  246. undefined,
  247. currentLineOffset + 1,
  248. currentColumnOffset,
  249. -1,
  250. -1,
  251. -1,
  252. -1,
  253. );
  254. needToCloseMapping = false;
  255. }
  256. if (/** @type {number} */ (generatedLine) > 1) {
  257. currentColumnOffset = /** @type {number} */ (generatedColumn);
  258. } else {
  259. currentColumnOffset += /** @type {number} */ (generatedColumn);
  260. }
  261. needToCloseMapping =
  262. needToCloseMapping ||
  263. (finalSource && lastMappingLine === generatedLine);
  264. currentLineOffset += /** @type {number} */ (generatedLine) - 1;
  265. }
  266. return {
  267. generatedLine: currentLineOffset + 1,
  268. generatedColumn: currentColumnOffset,
  269. source: finalSource ? code : undefined,
  270. };
  271. }
  272. /**
  273. * @param {HashLike} hash hash
  274. * @returns {void}
  275. */
  276. updateHash(hash) {
  277. if (!this._isOptimized) this._optimize();
  278. hash.update("ConcatSource");
  279. for (const item of this._children) {
  280. /** @type {Source} */
  281. (item).updateHash(hash);
  282. }
  283. }
  284. _optimize() {
  285. const newChildren = [];
  286. let currentString;
  287. /** @type {undefined | string | [string, string] | SourceLike} */
  288. let currentRawSources;
  289. /**
  290. * @param {string} string string
  291. * @returns {void}
  292. */
  293. const addStringToRawSources = (string) => {
  294. if (currentRawSources === undefined) {
  295. currentRawSources = string;
  296. } else if (Array.isArray(currentRawSources)) {
  297. currentRawSources.push(string);
  298. } else {
  299. currentRawSources = [
  300. typeof currentRawSources === "string"
  301. ? currentRawSources
  302. : /** @type {string} */ (currentRawSources.source()),
  303. string,
  304. ];
  305. }
  306. };
  307. /**
  308. * @param {SourceLike} source source
  309. * @returns {void}
  310. */
  311. const addSourceToRawSources = (source) => {
  312. if (currentRawSources === undefined) {
  313. currentRawSources = source;
  314. } else if (Array.isArray(currentRawSources)) {
  315. currentRawSources.push(
  316. /** @type {string} */
  317. (source.source()),
  318. );
  319. } else {
  320. currentRawSources = [
  321. typeof currentRawSources === "string"
  322. ? currentRawSources
  323. : /** @type {string} */ (currentRawSources.source()),
  324. /** @type {string} */
  325. (source.source()),
  326. ];
  327. }
  328. };
  329. const mergeRawSources = () => {
  330. if (Array.isArray(currentRawSources)) {
  331. const rawSource = new RawSource(currentRawSources.join(""));
  332. stringsAsRawSources.add(rawSource);
  333. newChildren.push(rawSource);
  334. } else if (typeof currentRawSources === "string") {
  335. const rawSource = new RawSource(currentRawSources);
  336. stringsAsRawSources.add(rawSource);
  337. newChildren.push(rawSource);
  338. } else {
  339. newChildren.push(currentRawSources);
  340. }
  341. };
  342. for (const child of this._children) {
  343. if (typeof child === "string") {
  344. if (currentString === undefined) {
  345. currentString = child;
  346. } else {
  347. currentString += child;
  348. }
  349. } else {
  350. if (currentString !== undefined) {
  351. addStringToRawSources(currentString);
  352. currentString = undefined;
  353. }
  354. if (stringsAsRawSources.has(child)) {
  355. addSourceToRawSources(
  356. /** @type {SourceLike} */
  357. (child),
  358. );
  359. } else {
  360. if (currentRawSources !== undefined) {
  361. mergeRawSources();
  362. currentRawSources = undefined;
  363. }
  364. newChildren.push(child);
  365. }
  366. }
  367. }
  368. if (currentString !== undefined) {
  369. addStringToRawSources(currentString);
  370. }
  371. if (currentRawSources !== undefined) {
  372. mergeRawSources();
  373. }
  374. this._children = newChildren;
  375. this._isOptimized = true;
  376. }
  377. }
  378. module.exports = ConcatSource;