ConcatSource.js 10.0 KB

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