ConcatSource.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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 RawSource = require("./RawSource");
  8. const streamChunks = require("./helpers/streamChunks");
  9. const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
  10. const stringsAsRawSources = new WeakSet();
  11. class ConcatSource extends Source {
  12. constructor() {
  13. super();
  14. this._children = [];
  15. for (let i = 0; i < arguments.length; i++) {
  16. const item = arguments[i];
  17. if (item instanceof ConcatSource) {
  18. for (const child of item._children) {
  19. this._children.push(child);
  20. }
  21. } else {
  22. this._children.push(item);
  23. }
  24. }
  25. this._isOptimized = arguments.length === 0;
  26. }
  27. getChildren() {
  28. if (!this._isOptimized) this._optimize();
  29. return this._children;
  30. }
  31. add(item) {
  32. if (item instanceof ConcatSource) {
  33. for (const child of item._children) {
  34. this._children.push(child);
  35. }
  36. } else {
  37. this._children.push(item);
  38. }
  39. this._isOptimized = false;
  40. }
  41. addAllSkipOptimizing(items) {
  42. for (const item of items) {
  43. this._children.push(item);
  44. }
  45. }
  46. buffer() {
  47. if (!this._isOptimized) this._optimize();
  48. const buffers = [];
  49. for (const child of this._children) {
  50. if (typeof child.buffer === "function") {
  51. buffers.push(child.buffer());
  52. } else {
  53. const bufferOrString = child.source();
  54. if (Buffer.isBuffer(bufferOrString)) {
  55. buffers.push(bufferOrString);
  56. } else {
  57. // This will not happen
  58. buffers.push(Buffer.from(bufferOrString, "utf-8"));
  59. }
  60. }
  61. }
  62. return Buffer.concat(buffers);
  63. }
  64. source() {
  65. if (!this._isOptimized) this._optimize();
  66. let source = "";
  67. for (const child of this._children) {
  68. source += child.source();
  69. }
  70. return source;
  71. }
  72. size() {
  73. if (!this._isOptimized) this._optimize();
  74. let size = 0;
  75. for (const child of this._children) {
  76. size += child.size();
  77. }
  78. return size;
  79. }
  80. map(options) {
  81. return getMap(this, options);
  82. }
  83. sourceAndMap(options) {
  84. return getSourceAndMap(this, options);
  85. }
  86. streamChunks(options, onChunk, onSource, onName) {
  87. if (!this._isOptimized) this._optimize();
  88. if (this._children.length === 1)
  89. return this._children[0].streamChunks(options, onChunk, onSource, onName);
  90. let currentLineOffset = 0;
  91. let currentColumnOffset = 0;
  92. let sourceMapping = new Map();
  93. let nameMapping = new Map();
  94. const finalSource = !!(options && options.finalSource);
  95. let code = "";
  96. let needToCloseMapping = false;
  97. for (const item of this._children) {
  98. const sourceIndexMapping = [];
  99. const nameIndexMapping = [];
  100. let lastMappingLine = 0;
  101. const { generatedLine, generatedColumn, source } = streamChunks(
  102. item,
  103. options,
  104. // eslint-disable-next-line no-loop-func
  105. (
  106. chunk,
  107. generatedLine,
  108. generatedColumn,
  109. sourceIndex,
  110. originalLine,
  111. originalColumn,
  112. nameIndex
  113. ) => {
  114. const line = generatedLine + currentLineOffset;
  115. const column =
  116. generatedLine === 1
  117. ? generatedColumn + currentColumnOffset
  118. : generatedColumn;
  119. if (needToCloseMapping) {
  120. if (generatedLine !== 1 || generatedColumn !== 0) {
  121. onChunk(
  122. undefined,
  123. currentLineOffset + 1,
  124. currentColumnOffset,
  125. -1,
  126. -1,
  127. -1,
  128. -1
  129. );
  130. }
  131. needToCloseMapping = false;
  132. }
  133. const resultSourceIndex =
  134. sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
  135. ? -1
  136. : sourceIndexMapping[sourceIndex];
  137. const resultNameIndex =
  138. nameIndex < 0 || nameIndex >= nameIndexMapping.length
  139. ? -1
  140. : nameIndexMapping[nameIndex];
  141. lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine;
  142. if (finalSource) {
  143. if (chunk !== undefined) code += chunk;
  144. if (resultSourceIndex >= 0) {
  145. onChunk(
  146. undefined,
  147. line,
  148. column,
  149. resultSourceIndex,
  150. originalLine,
  151. originalColumn,
  152. resultNameIndex
  153. );
  154. }
  155. } else {
  156. if (resultSourceIndex < 0) {
  157. onChunk(chunk, line, column, -1, -1, -1, -1);
  158. } else {
  159. onChunk(
  160. chunk,
  161. line,
  162. column,
  163. resultSourceIndex,
  164. originalLine,
  165. originalColumn,
  166. resultNameIndex
  167. );
  168. }
  169. }
  170. },
  171. (i, source, sourceContent) => {
  172. let globalIndex = sourceMapping.get(source);
  173. if (globalIndex === undefined) {
  174. sourceMapping.set(source, (globalIndex = sourceMapping.size));
  175. onSource(globalIndex, source, sourceContent);
  176. }
  177. sourceIndexMapping[i] = globalIndex;
  178. },
  179. (i, name) => {
  180. let globalIndex = nameMapping.get(name);
  181. if (globalIndex === undefined) {
  182. nameMapping.set(name, (globalIndex = nameMapping.size));
  183. onName(globalIndex, name);
  184. }
  185. nameIndexMapping[i] = globalIndex;
  186. }
  187. );
  188. if (source !== undefined) code += source;
  189. if (needToCloseMapping) {
  190. if (generatedLine !== 1 || generatedColumn !== 0) {
  191. onChunk(
  192. undefined,
  193. currentLineOffset + 1,
  194. currentColumnOffset,
  195. -1,
  196. -1,
  197. -1,
  198. -1
  199. );
  200. needToCloseMapping = false;
  201. }
  202. }
  203. if (generatedLine > 1) {
  204. currentColumnOffset = generatedColumn;
  205. } else {
  206. currentColumnOffset += generatedColumn;
  207. }
  208. needToCloseMapping =
  209. needToCloseMapping ||
  210. (finalSource && lastMappingLine === generatedLine);
  211. currentLineOffset += generatedLine - 1;
  212. }
  213. return {
  214. generatedLine: currentLineOffset + 1,
  215. generatedColumn: currentColumnOffset,
  216. source: finalSource ? code : undefined
  217. };
  218. }
  219. updateHash(hash) {
  220. if (!this._isOptimized) this._optimize();
  221. hash.update("ConcatSource");
  222. for (const item of this._children) {
  223. item.updateHash(hash);
  224. }
  225. }
  226. _optimize() {
  227. const newChildren = [];
  228. let currentString = undefined;
  229. let currentRawSources = undefined;
  230. const addStringToRawSources = string => {
  231. if (currentRawSources === undefined) {
  232. currentRawSources = string;
  233. } else if (Array.isArray(currentRawSources)) {
  234. currentRawSources.push(string);
  235. } else {
  236. currentRawSources = [
  237. typeof currentRawSources === "string"
  238. ? currentRawSources
  239. : currentRawSources.source(),
  240. string
  241. ];
  242. }
  243. };
  244. const addSourceToRawSources = source => {
  245. if (currentRawSources === undefined) {
  246. currentRawSources = source;
  247. } else if (Array.isArray(currentRawSources)) {
  248. currentRawSources.push(source.source());
  249. } else {
  250. currentRawSources = [
  251. typeof currentRawSources === "string"
  252. ? currentRawSources
  253. : currentRawSources.source(),
  254. source.source()
  255. ];
  256. }
  257. };
  258. const mergeRawSources = () => {
  259. if (Array.isArray(currentRawSources)) {
  260. const rawSource = new RawSource(currentRawSources.join(""));
  261. stringsAsRawSources.add(rawSource);
  262. newChildren.push(rawSource);
  263. } else if (typeof currentRawSources === "string") {
  264. const rawSource = new RawSource(currentRawSources);
  265. stringsAsRawSources.add(rawSource);
  266. newChildren.push(rawSource);
  267. } else {
  268. newChildren.push(currentRawSources);
  269. }
  270. };
  271. for (const child of this._children) {
  272. if (typeof child === "string") {
  273. if (currentString === undefined) {
  274. currentString = child;
  275. } else {
  276. currentString += child;
  277. }
  278. } else {
  279. if (currentString !== undefined) {
  280. addStringToRawSources(currentString);
  281. currentString = undefined;
  282. }
  283. if (stringsAsRawSources.has(child)) {
  284. addSourceToRawSources(child);
  285. } else {
  286. if (currentRawSources !== undefined) {
  287. mergeRawSources();
  288. currentRawSources = undefined;
  289. }
  290. newChildren.push(child);
  291. }
  292. }
  293. }
  294. if (currentString !== undefined) {
  295. addStringToRawSources(currentString);
  296. }
  297. if (currentRawSources !== undefined) {
  298. mergeRawSources();
  299. }
  300. this._children = newChildren;
  301. this._isOptimized = true;
  302. }
  303. }
  304. module.exports = ConcatSource;