395 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const RawSource = require("./RawSource");
 | |
| const Source = require("./Source");
 | |
| const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
 | |
| const streamChunks = require("./helpers/streamChunks");
 | |
| 
 | |
| /** @typedef {import("./CompatSource").SourceLike} SourceLike */
 | |
| /** @typedef {import("./Source").HashLike} HashLike */
 | |
| /** @typedef {import("./Source").MapOptions} MapOptions */
 | |
| /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
 | |
| /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
 | |
| /** @typedef {import("./Source").SourceValue} SourceValue */
 | |
| /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
 | |
| /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
 | |
| /** @typedef {import("./helpers/streamChunks").OnName} OnName */
 | |
| /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
 | |
| /** @typedef {import("./helpers/streamChunks").Options} Options */
 | |
| 
 | |
| /** @typedef {string | Source | SourceLike} Child */
 | |
| 
 | |
| const stringsAsRawSources = new WeakSet();
 | |
| 
 | |
| class ConcatSource extends Source {
 | |
| 	/**
 | |
| 	 * @param {Child[]} args children
 | |
| 	 */
 | |
| 	constructor(...args) {
 | |
| 		super();
 | |
| 		/**
 | |
| 		 * @private
 | |
| 		 * @type {Child[]}
 | |
| 		 */
 | |
| 		this._children = [];
 | |
| 
 | |
| 		for (const item of args) {
 | |
| 			if (item instanceof ConcatSource) {
 | |
| 				for (const child of item._children) {
 | |
| 					this._children.push(child);
 | |
| 				}
 | |
| 			} else {
 | |
| 				this._children.push(item);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this._isOptimized = args.length === 0;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @returns {Source[]} children
 | |
| 	 */
 | |
| 	getChildren() {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		return /** @type {Source[]} */ (this._children);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {Child} item item
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	add(item) {
 | |
| 		if (item instanceof ConcatSource) {
 | |
| 			for (const child of item._children) {
 | |
| 				this._children.push(child);
 | |
| 			}
 | |
| 		} else {
 | |
| 			this._children.push(item);
 | |
| 		}
 | |
| 		this._isOptimized = false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {Child[]} items items
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	addAllSkipOptimizing(items) {
 | |
| 		for (const item of items) {
 | |
| 			this._children.push(item);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	buffer() {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		/** @type {Buffer[]} */
 | |
| 		const buffers = [];
 | |
| 		for (const child of /** @type {SourceLike[]} */ (this._children)) {
 | |
| 			if (typeof child.buffer === "function") {
 | |
| 				buffers.push(child.buffer());
 | |
| 			} else {
 | |
| 				const bufferOrString = child.source();
 | |
| 				if (Buffer.isBuffer(bufferOrString)) {
 | |
| 					buffers.push(bufferOrString);
 | |
| 				} else {
 | |
| 					// This will not happen
 | |
| 					buffers.push(Buffer.from(bufferOrString, "utf8"));
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return Buffer.concat(buffers);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @returns {SourceValue} source
 | |
| 	 */
 | |
| 	source() {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		let source = "";
 | |
| 		for (const child of this._children) {
 | |
| 			source += /** @type {Source} */ (child).source();
 | |
| 		}
 | |
| 		return source;
 | |
| 	}
 | |
| 
 | |
| 	size() {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		let size = 0;
 | |
| 		for (const child of this._children) {
 | |
| 			size += /** @type {Source} */ (child).size();
 | |
| 		}
 | |
| 		return size;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {MapOptions=} options map options
 | |
| 	 * @returns {RawSourceMap | null} map
 | |
| 	 */
 | |
| 	map(options) {
 | |
| 		return getMap(this, options);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {MapOptions=} options map options
 | |
| 	 * @returns {SourceAndMap} source and map
 | |
| 	 */
 | |
| 	sourceAndMap(options) {
 | |
| 		return getSourceAndMap(this, options);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {Options} options options
 | |
| 	 * @param {OnChunk} onChunk called for each chunk of code
 | |
| 	 * @param {OnSource} onSource called for each source
 | |
| 	 * @param {OnName} onName called for each name
 | |
| 	 * @returns {GeneratedSourceInfo} generated source info
 | |
| 	 */
 | |
| 	streamChunks(options, onChunk, onSource, onName) {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		if (this._children.length === 1) {
 | |
| 			return /** @type {ConcatSource[]} */ (this._children)[0].streamChunks(
 | |
| 				options,
 | |
| 				onChunk,
 | |
| 				onSource,
 | |
| 				onName,
 | |
| 			);
 | |
| 		}
 | |
| 		let currentLineOffset = 0;
 | |
| 		let currentColumnOffset = 0;
 | |
| 		const sourceMapping = new Map();
 | |
| 		const nameMapping = new Map();
 | |
| 		const finalSource = Boolean(options && options.finalSource);
 | |
| 		let code = "";
 | |
| 		let needToCloseMapping = false;
 | |
| 		for (const item of /** @type {Source[]} */ (this._children)) {
 | |
| 			/** @type {number[]} */
 | |
| 			const sourceIndexMapping = [];
 | |
| 			/** @type {number[]} */
 | |
| 			const nameIndexMapping = [];
 | |
| 			let lastMappingLine = 0;
 | |
| 			const { generatedLine, generatedColumn, source } = streamChunks(
 | |
| 				item,
 | |
| 				options,
 | |
| 				// eslint-disable-next-line no-loop-func
 | |
| 				(
 | |
| 					chunk,
 | |
| 					generatedLine,
 | |
| 					generatedColumn,
 | |
| 					sourceIndex,
 | |
| 					originalLine,
 | |
| 					originalColumn,
 | |
| 					nameIndex,
 | |
| 				) => {
 | |
| 					const line = generatedLine + currentLineOffset;
 | |
| 					const column =
 | |
| 						generatedLine === 1
 | |
| 							? generatedColumn + currentColumnOffset
 | |
| 							: generatedColumn;
 | |
| 					if (needToCloseMapping) {
 | |
| 						if (generatedLine !== 1 || generatedColumn !== 0) {
 | |
| 							onChunk(
 | |
| 								undefined,
 | |
| 								currentLineOffset + 1,
 | |
| 								currentColumnOffset,
 | |
| 								-1,
 | |
| 								-1,
 | |
| 								-1,
 | |
| 								-1,
 | |
| 							);
 | |
| 						}
 | |
| 						needToCloseMapping = false;
 | |
| 					}
 | |
| 					const resultSourceIndex =
 | |
| 						sourceIndex < 0 || sourceIndex >= sourceIndexMapping.length
 | |
| 							? -1
 | |
| 							: sourceIndexMapping[sourceIndex];
 | |
| 					const resultNameIndex =
 | |
| 						nameIndex < 0 || nameIndex >= nameIndexMapping.length
 | |
| 							? -1
 | |
| 							: nameIndexMapping[nameIndex];
 | |
| 					lastMappingLine = resultSourceIndex < 0 ? 0 : generatedLine;
 | |
| 					let _chunk;
 | |
| 					// When using finalSource, we process the entire source code at once at the end, rather than chunk by chunk
 | |
| 					if (finalSource) {
 | |
| 						if (chunk !== undefined) code += chunk;
 | |
| 					} else {
 | |
| 						_chunk = chunk;
 | |
| 					}
 | |
| 					if (resultSourceIndex < 0) {
 | |
| 						onChunk(_chunk, line, column, -1, -1, -1, -1);
 | |
| 					} else {
 | |
| 						onChunk(
 | |
| 							_chunk,
 | |
| 							line,
 | |
| 							column,
 | |
| 							resultSourceIndex,
 | |
| 							originalLine,
 | |
| 							originalColumn,
 | |
| 							resultNameIndex,
 | |
| 						);
 | |
| 					}
 | |
| 				},
 | |
| 				(i, source, sourceContent) => {
 | |
| 					let globalIndex = sourceMapping.get(source);
 | |
| 					if (globalIndex === undefined) {
 | |
| 						sourceMapping.set(source, (globalIndex = sourceMapping.size));
 | |
| 						onSource(globalIndex, source, sourceContent);
 | |
| 					}
 | |
| 					sourceIndexMapping[i] = globalIndex;
 | |
| 				},
 | |
| 				(i, name) => {
 | |
| 					let globalIndex = nameMapping.get(name);
 | |
| 					if (globalIndex === undefined) {
 | |
| 						nameMapping.set(name, (globalIndex = nameMapping.size));
 | |
| 						onName(globalIndex, name);
 | |
| 					}
 | |
| 					nameIndexMapping[i] = globalIndex;
 | |
| 				},
 | |
| 			);
 | |
| 			if (source !== undefined) code += source;
 | |
| 			if (
 | |
| 				needToCloseMapping &&
 | |
| 				(generatedLine !== 1 || generatedColumn !== 0)
 | |
| 			) {
 | |
| 				onChunk(
 | |
| 					undefined,
 | |
| 					currentLineOffset + 1,
 | |
| 					currentColumnOffset,
 | |
| 					-1,
 | |
| 					-1,
 | |
| 					-1,
 | |
| 					-1,
 | |
| 				);
 | |
| 				needToCloseMapping = false;
 | |
| 			}
 | |
| 			if (/** @type {number} */ (generatedLine) > 1) {
 | |
| 				currentColumnOffset = /** @type {number} */ (generatedColumn);
 | |
| 			} else {
 | |
| 				currentColumnOffset += /** @type {number} */ (generatedColumn);
 | |
| 			}
 | |
| 			needToCloseMapping =
 | |
| 				needToCloseMapping ||
 | |
| 				(finalSource && lastMappingLine === generatedLine);
 | |
| 			currentLineOffset += /** @type {number} */ (generatedLine) - 1;
 | |
| 		}
 | |
| 		return {
 | |
| 			generatedLine: currentLineOffset + 1,
 | |
| 			generatedColumn: currentColumnOffset,
 | |
| 			source: finalSource ? code : undefined,
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {HashLike} hash hash
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	updateHash(hash) {
 | |
| 		if (!this._isOptimized) this._optimize();
 | |
| 		hash.update("ConcatSource");
 | |
| 		for (const item of this._children) {
 | |
| 			/** @type {Source} */
 | |
| 			(item).updateHash(hash);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	_optimize() {
 | |
| 		const newChildren = [];
 | |
| 		let currentString;
 | |
| 		/** @type {undefined | string | [string, string] | SourceLike} */
 | |
| 		let currentRawSources;
 | |
| 		/**
 | |
| 		 * @param {string} string string
 | |
| 		 * @returns {void}
 | |
| 		 */
 | |
| 		const addStringToRawSources = (string) => {
 | |
| 			if (currentRawSources === undefined) {
 | |
| 				currentRawSources = string;
 | |
| 			} else if (Array.isArray(currentRawSources)) {
 | |
| 				currentRawSources.push(string);
 | |
| 			} else {
 | |
| 				currentRawSources = [
 | |
| 					typeof currentRawSources === "string"
 | |
| 						? currentRawSources
 | |
| 						: /** @type {string} */ (currentRawSources.source()),
 | |
| 					string,
 | |
| 				];
 | |
| 			}
 | |
| 		};
 | |
| 		/**
 | |
| 		 * @param {SourceLike} source source
 | |
| 		 * @returns {void}
 | |
| 		 */
 | |
| 		const addSourceToRawSources = (source) => {
 | |
| 			if (currentRawSources === undefined) {
 | |
| 				currentRawSources = source;
 | |
| 			} else if (Array.isArray(currentRawSources)) {
 | |
| 				currentRawSources.push(
 | |
| 					/** @type {string} */
 | |
| 					(source.source()),
 | |
| 				);
 | |
| 			} else {
 | |
| 				currentRawSources = [
 | |
| 					typeof currentRawSources === "string"
 | |
| 						? currentRawSources
 | |
| 						: /** @type {string} */ (currentRawSources.source()),
 | |
| 					/** @type {string} */
 | |
| 					(source.source()),
 | |
| 				];
 | |
| 			}
 | |
| 		};
 | |
| 		const mergeRawSources = () => {
 | |
| 			if (Array.isArray(currentRawSources)) {
 | |
| 				const rawSource = new RawSource(currentRawSources.join(""));
 | |
| 				stringsAsRawSources.add(rawSource);
 | |
| 				newChildren.push(rawSource);
 | |
| 			} else if (typeof currentRawSources === "string") {
 | |
| 				const rawSource = new RawSource(currentRawSources);
 | |
| 				stringsAsRawSources.add(rawSource);
 | |
| 				newChildren.push(rawSource);
 | |
| 			} else {
 | |
| 				newChildren.push(currentRawSources);
 | |
| 			}
 | |
| 		};
 | |
| 		for (const child of this._children) {
 | |
| 			if (typeof child === "string") {
 | |
| 				if (currentString === undefined) {
 | |
| 					currentString = child;
 | |
| 				} else {
 | |
| 					currentString += child;
 | |
| 				}
 | |
| 			} else {
 | |
| 				if (currentString !== undefined) {
 | |
| 					addStringToRawSources(currentString);
 | |
| 					currentString = undefined;
 | |
| 				}
 | |
| 				if (stringsAsRawSources.has(child)) {
 | |
| 					addSourceToRawSources(
 | |
| 						/** @type {SourceLike} */
 | |
| 						(child),
 | |
| 					);
 | |
| 				} else {
 | |
| 					if (currentRawSources !== undefined) {
 | |
| 						mergeRawSources();
 | |
| 						currentRawSources = undefined;
 | |
| 					}
 | |
| 					newChildren.push(child);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if (currentString !== undefined) {
 | |
| 			addStringToRawSources(currentString);
 | |
| 		}
 | |
| 		if (currentRawSources !== undefined) {
 | |
| 			mergeRawSources();
 | |
| 		}
 | |
| 		this._children = newChildren;
 | |
| 		this._isOptimized = true;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| module.exports = ConcatSource;
 |