You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			468 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
			
		
		
	
	
			468 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| "use strict";
 | |
| 
 | |
| const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
 | |
| const streamChunks = require("./helpers/streamChunks");
 | |
| const Source = require("./Source");
 | |
| const splitIntoLines = require("./helpers/splitIntoLines");
 | |
| 
 | |
| // since v8 7.0, Array.prototype.sort is stable
 | |
| const hasStableSort =
 | |
| 	typeof process === "object" &&
 | |
| 	process.versions &&
 | |
| 	typeof process.versions.v8 === "string" &&
 | |
| 	!/^[0-6]\./.test(process.versions.v8);
 | |
| 
 | |
| // This is larger than max string length
 | |
| const MAX_SOURCE_POSITION = 0x20000000;
 | |
| 
 | |
| class Replacement {
 | |
| 	constructor(start, end, content, name) {
 | |
| 		this.start = start;
 | |
| 		this.end = end;
 | |
| 		this.content = content;
 | |
| 		this.name = name;
 | |
| 		if (!hasStableSort) {
 | |
| 			this.index = -1;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| class ReplaceSource extends Source {
 | |
| 	constructor(source, name) {
 | |
| 		super();
 | |
| 		this._source = source;
 | |
| 		this._name = name;
 | |
| 		/** @type {Replacement[]} */
 | |
| 		this._replacements = [];
 | |
| 		this._isSorted = true;
 | |
| 	}
 | |
| 
 | |
| 	getName() {
 | |
| 		return this._name;
 | |
| 	}
 | |
| 
 | |
| 	getReplacements() {
 | |
| 		this._sortReplacements();
 | |
| 		return this._replacements;
 | |
| 	}
 | |
| 
 | |
| 	replace(start, end, newValue, name) {
 | |
| 		if (typeof newValue !== "string")
 | |
| 			throw new Error(
 | |
| 				"insertion must be a string, but is a " + typeof newValue
 | |
| 			);
 | |
| 		this._replacements.push(new Replacement(start, end, newValue, name));
 | |
| 		this._isSorted = false;
 | |
| 	}
 | |
| 
 | |
| 	insert(pos, newValue, name) {
 | |
| 		if (typeof newValue !== "string")
 | |
| 			throw new Error(
 | |
| 				"insertion must be a string, but is a " +
 | |
| 					typeof newValue +
 | |
| 					": " +
 | |
| 					newValue
 | |
| 			);
 | |
| 		this._replacements.push(new Replacement(pos, pos - 1, newValue, name));
 | |
| 		this._isSorted = false;
 | |
| 	}
 | |
| 
 | |
| 	source() {
 | |
| 		if (this._replacements.length === 0) {
 | |
| 			return this._source.source();
 | |
| 		}
 | |
| 		let current = this._source.source();
 | |
| 		let pos = 0;
 | |
| 		const result = [];
 | |
| 
 | |
| 		this._sortReplacements();
 | |
| 		for (const replacement of this._replacements) {
 | |
| 			const start = Math.floor(replacement.start);
 | |
| 			const end = Math.floor(replacement.end + 1);
 | |
| 			if (pos < start) {
 | |
| 				const offset = start - pos;
 | |
| 				result.push(current.slice(0, offset));
 | |
| 				current = current.slice(offset);
 | |
| 				pos = start;
 | |
| 			}
 | |
| 			result.push(replacement.content);
 | |
| 			if (pos < end) {
 | |
| 				const offset = end - pos;
 | |
| 				current = current.slice(offset);
 | |
| 				pos = end;
 | |
| 			}
 | |
| 		}
 | |
| 		result.push(current);
 | |
| 		return result.join("");
 | |
| 	}
 | |
| 
 | |
| 	map(options) {
 | |
| 		if (this._replacements.length === 0) {
 | |
| 			return this._source.map(options);
 | |
| 		}
 | |
| 		return getMap(this, options);
 | |
| 	}
 | |
| 
 | |
| 	sourceAndMap(options) {
 | |
| 		if (this._replacements.length === 0) {
 | |
| 			return this._source.sourceAndMap(options);
 | |
| 		}
 | |
| 		return getSourceAndMap(this, options);
 | |
| 	}
 | |
| 
 | |
| 	original() {
 | |
| 		return this._source;
 | |
| 	}
 | |
| 
 | |
| 	_sortReplacements() {
 | |
| 		if (this._isSorted) return;
 | |
| 		if (hasStableSort) {
 | |
| 			this._replacements.sort(function (a, b) {
 | |
| 				const diff1 = a.start - b.start;
 | |
| 				if (diff1 !== 0) return diff1;
 | |
| 				const diff2 = a.end - b.end;
 | |
| 				if (diff2 !== 0) return diff2;
 | |
| 				return 0;
 | |
| 			});
 | |
| 		} else {
 | |
| 			this._replacements.forEach((repl, i) => (repl.index = i));
 | |
| 			this._replacements.sort(function (a, b) {
 | |
| 				const diff1 = a.start - b.start;
 | |
| 				if (diff1 !== 0) return diff1;
 | |
| 				const diff2 = a.end - b.end;
 | |
| 				if (diff2 !== 0) return diff2;
 | |
| 				return a.index - b.index;
 | |
| 			});
 | |
| 		}
 | |
| 		this._isSorted = true;
 | |
| 	}
 | |
| 
 | |
| 	streamChunks(options, onChunk, onSource, onName) {
 | |
| 		this._sortReplacements();
 | |
| 		const repls = this._replacements;
 | |
| 		let pos = 0;
 | |
| 		let i = 0;
 | |
| 		let replacmentEnd = -1;
 | |
| 		let nextReplacement =
 | |
| 			i < repls.length ? Math.floor(repls[i].start) : MAX_SOURCE_POSITION;
 | |
| 		let generatedLineOffset = 0;
 | |
| 		let generatedColumnOffset = 0;
 | |
| 		let generatedColumnOffsetLine = 0;
 | |
| 		const sourceContents = [];
 | |
| 		const nameMapping = new Map();
 | |
| 		const nameIndexMapping = [];
 | |
| 		const checkOriginalContent = (sourceIndex, line, column, expectedChunk) => {
 | |
| 			let content =
 | |
| 				sourceIndex < sourceContents.length
 | |
| 					? sourceContents[sourceIndex]
 | |
| 					: undefined;
 | |
| 			if (content === undefined) return false;
 | |
| 			if (typeof content === "string") {
 | |
| 				content = splitIntoLines(content);
 | |
| 				sourceContents[sourceIndex] = content;
 | |
| 			}
 | |
| 			const contentLine = line <= content.length ? content[line - 1] : null;
 | |
| 			if (contentLine === null) return false;
 | |
| 			return (
 | |
| 				contentLine.slice(column, column + expectedChunk.length) ===
 | |
| 				expectedChunk
 | |
| 			);
 | |
| 		};
 | |
| 		let { generatedLine, generatedColumn } = streamChunks(
 | |
| 			this._source,
 | |
| 			Object.assign({}, options, { finalSource: false }),
 | |
| 			(
 | |
| 				chunk,
 | |
| 				generatedLine,
 | |
| 				generatedColumn,
 | |
| 				sourceIndex,
 | |
| 				originalLine,
 | |
| 				originalColumn,
 | |
| 				nameIndex
 | |
| 			) => {
 | |
| 				let chunkPos = 0;
 | |
| 				let endPos = pos + chunk.length;
 | |
| 
 | |
| 				// Skip over when it has been replaced
 | |
| 				if (replacmentEnd > pos) {
 | |
| 					// Skip over the whole chunk
 | |
| 					if (replacmentEnd >= endPos) {
 | |
| 						const line = generatedLine + generatedLineOffset;
 | |
| 						if (chunk.endsWith("\n")) {
 | |
| 							generatedLineOffset--;
 | |
| 							if (generatedColumnOffsetLine === line) {
 | |
| 								// undo exiting corrections form the current line
 | |
| 								generatedColumnOffset += generatedColumn;
 | |
| 							}
 | |
| 						} else if (generatedColumnOffsetLine === line) {
 | |
| 							generatedColumnOffset -= chunk.length;
 | |
| 						} else {
 | |
| 							generatedColumnOffset = -chunk.length;
 | |
| 							generatedColumnOffsetLine = line;
 | |
| 						}
 | |
| 						pos = endPos;
 | |
| 						return;
 | |
| 					}
 | |
| 
 | |
| 					// Partially skip over chunk
 | |
| 					chunkPos = replacmentEnd - pos;
 | |
| 					if (
 | |
| 						checkOriginalContent(
 | |
| 							sourceIndex,
 | |
| 							originalLine,
 | |
| 							originalColumn,
 | |
| 							chunk.slice(0, chunkPos)
 | |
| 						)
 | |
| 					) {
 | |
| 						originalColumn += chunkPos;
 | |
| 					}
 | |
| 					pos += chunkPos;
 | |
| 					const line = generatedLine + generatedLineOffset;
 | |
| 					if (generatedColumnOffsetLine === line) {
 | |
| 						generatedColumnOffset -= chunkPos;
 | |
| 					} else {
 | |
| 						generatedColumnOffset = -chunkPos;
 | |
| 						generatedColumnOffsetLine = line;
 | |
| 					}
 | |
| 					generatedColumn += chunkPos;
 | |
| 				}
 | |
| 
 | |
| 				// Is a replacement in the chunk?
 | |
| 				if (nextReplacement < endPos) {
 | |
| 					do {
 | |
| 						let line = generatedLine + generatedLineOffset;
 | |
| 						if (nextReplacement > pos) {
 | |
| 							// Emit chunk until replacement
 | |
| 							const offset = nextReplacement - pos;
 | |
| 							const chunkSlice = chunk.slice(chunkPos, chunkPos + offset);
 | |
| 							onChunk(
 | |
| 								chunkSlice,
 | |
| 								line,
 | |
| 								generatedColumn +
 | |
| 									(line === generatedColumnOffsetLine
 | |
| 										? generatedColumnOffset
 | |
| 										: 0),
 | |
| 								sourceIndex,
 | |
| 								originalLine,
 | |
| 								originalColumn,
 | |
| 								nameIndex < 0 || nameIndex >= nameIndexMapping.length
 | |
| 									? -1
 | |
| 									: nameIndexMapping[nameIndex]
 | |
| 							);
 | |
| 							generatedColumn += offset;
 | |
| 							chunkPos += offset;
 | |
| 							pos = nextReplacement;
 | |
| 							if (
 | |
| 								checkOriginalContent(
 | |
| 									sourceIndex,
 | |
| 									originalLine,
 | |
| 									originalColumn,
 | |
| 									chunkSlice
 | |
| 								)
 | |
| 							) {
 | |
| 								originalColumn += chunkSlice.length;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						// Insert replacement content splitted into chunks by lines
 | |
| 						const { content, name } = repls[i];
 | |
| 						let matches = splitIntoLines(content);
 | |
| 						let replacementNameIndex = nameIndex;
 | |
| 						if (sourceIndex >= 0 && name) {
 | |
| 							let globalIndex = nameMapping.get(name);
 | |
| 							if (globalIndex === undefined) {
 | |
| 								globalIndex = nameMapping.size;
 | |
| 								nameMapping.set(name, globalIndex);
 | |
| 								onName(globalIndex, name);
 | |
| 							}
 | |
| 							replacementNameIndex = globalIndex;
 | |
| 						}
 | |
| 						for (let m = 0; m < matches.length; m++) {
 | |
| 							const contentLine = matches[m];
 | |
| 							onChunk(
 | |
| 								contentLine,
 | |
| 								line,
 | |
| 								generatedColumn +
 | |
| 									(line === generatedColumnOffsetLine
 | |
| 										? generatedColumnOffset
 | |
| 										: 0),
 | |
| 								sourceIndex,
 | |
| 								originalLine,
 | |
| 								originalColumn,
 | |
| 								replacementNameIndex
 | |
| 							);
 | |
| 
 | |
| 							// Only the first chunk has name assigned
 | |
| 							replacementNameIndex = -1;
 | |
| 
 | |
| 							if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
 | |
| 								if (generatedColumnOffsetLine === line) {
 | |
| 									generatedColumnOffset += contentLine.length;
 | |
| 								} else {
 | |
| 									generatedColumnOffset = contentLine.length;
 | |
| 									generatedColumnOffsetLine = line;
 | |
| 								}
 | |
| 							} else {
 | |
| 								generatedLineOffset++;
 | |
| 								line++;
 | |
| 								generatedColumnOffset = -generatedColumn;
 | |
| 								generatedColumnOffsetLine = line;
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						// Remove replaced content by settings this variable
 | |
| 						replacmentEnd = Math.max(
 | |
| 							replacmentEnd,
 | |
| 							Math.floor(repls[i].end + 1)
 | |
| 						);
 | |
| 
 | |
| 						// Move to next replacment
 | |
| 						i++;
 | |
| 						nextReplacement =
 | |
| 							i < repls.length
 | |
| 								? Math.floor(repls[i].start)
 | |
| 								: MAX_SOURCE_POSITION;
 | |
| 
 | |
| 						// Skip over when it has been replaced
 | |
| 						const offset = chunk.length - endPos + replacmentEnd - chunkPos;
 | |
| 						if (offset > 0) {
 | |
| 							// Skip over whole chunk
 | |
| 							if (replacmentEnd >= endPos) {
 | |
| 								let line = generatedLine + generatedLineOffset;
 | |
| 								if (chunk.endsWith("\n")) {
 | |
| 									generatedLineOffset--;
 | |
| 									if (generatedColumnOffsetLine === line) {
 | |
| 										// undo exiting corrections form the current line
 | |
| 										generatedColumnOffset += generatedColumn;
 | |
| 									}
 | |
| 								} else if (generatedColumnOffsetLine === line) {
 | |
| 									generatedColumnOffset -= chunk.length - chunkPos;
 | |
| 								} else {
 | |
| 									generatedColumnOffset = chunkPos - chunk.length;
 | |
| 									generatedColumnOffsetLine = line;
 | |
| 								}
 | |
| 								pos = endPos;
 | |
| 								return;
 | |
| 							}
 | |
| 
 | |
| 							// Partially skip over chunk
 | |
| 							const line = generatedLine + generatedLineOffset;
 | |
| 							if (
 | |
| 								checkOriginalContent(
 | |
| 									sourceIndex,
 | |
| 									originalLine,
 | |
| 									originalColumn,
 | |
| 									chunk.slice(chunkPos, chunkPos + offset)
 | |
| 								)
 | |
| 							) {
 | |
| 								originalColumn += offset;
 | |
| 							}
 | |
| 							chunkPos += offset;
 | |
| 							pos += offset;
 | |
| 							if (generatedColumnOffsetLine === line) {
 | |
| 								generatedColumnOffset -= offset;
 | |
| 							} else {
 | |
| 								generatedColumnOffset = -offset;
 | |
| 								generatedColumnOffsetLine = line;
 | |
| 							}
 | |
| 							generatedColumn += offset;
 | |
| 						}
 | |
| 					} while (nextReplacement < endPos);
 | |
| 				}
 | |
| 
 | |
| 				// Emit remaining chunk
 | |
| 				if (chunkPos < chunk.length) {
 | |
| 					const chunkSlice = chunkPos === 0 ? chunk : chunk.slice(chunkPos);
 | |
| 					const line = generatedLine + generatedLineOffset;
 | |
| 					onChunk(
 | |
| 						chunkSlice,
 | |
| 						line,
 | |
| 						generatedColumn +
 | |
| 							(line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
 | |
| 						sourceIndex,
 | |
| 						originalLine,
 | |
| 						originalColumn,
 | |
| 						nameIndex < 0 ? -1 : nameIndexMapping[nameIndex]
 | |
| 					);
 | |
| 				}
 | |
| 				pos = endPos;
 | |
| 			},
 | |
| 			(sourceIndex, source, sourceContent) => {
 | |
| 				while (sourceContents.length < sourceIndex)
 | |
| 					sourceContents.push(undefined);
 | |
| 				sourceContents[sourceIndex] = sourceContent;
 | |
| 				onSource(sourceIndex, source, sourceContent);
 | |
| 			},
 | |
| 			(nameIndex, name) => {
 | |
| 				let globalIndex = nameMapping.get(name);
 | |
| 				if (globalIndex === undefined) {
 | |
| 					globalIndex = nameMapping.size;
 | |
| 					nameMapping.set(name, globalIndex);
 | |
| 					onName(globalIndex, name);
 | |
| 				}
 | |
| 				nameIndexMapping[nameIndex] = globalIndex;
 | |
| 			}
 | |
| 		);
 | |
| 
 | |
| 		// Handle remaining replacements
 | |
| 		let remainer = "";
 | |
| 		for (; i < repls.length; i++) {
 | |
| 			remainer += repls[i].content;
 | |
| 		}
 | |
| 
 | |
| 		// Insert remaining replacements content splitted into chunks by lines
 | |
| 		let line = generatedLine + generatedLineOffset;
 | |
| 		let matches = splitIntoLines(remainer);
 | |
| 		for (let m = 0; m < matches.length; m++) {
 | |
| 			const contentLine = matches[m];
 | |
| 			onChunk(
 | |
| 				contentLine,
 | |
| 				line,
 | |
| 				generatedColumn +
 | |
| 					(line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
 | |
| 				-1,
 | |
| 				-1,
 | |
| 				-1,
 | |
| 				-1
 | |
| 			);
 | |
| 
 | |
| 			if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
 | |
| 				if (generatedColumnOffsetLine === line) {
 | |
| 					generatedColumnOffset += contentLine.length;
 | |
| 				} else {
 | |
| 					generatedColumnOffset = contentLine.length;
 | |
| 					generatedColumnOffsetLine = line;
 | |
| 				}
 | |
| 			} else {
 | |
| 				generatedLineOffset++;
 | |
| 				line++;
 | |
| 				generatedColumnOffset = -generatedColumn;
 | |
| 				generatedColumnOffsetLine = line;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return {
 | |
| 			generatedLine: line,
 | |
| 			generatedColumn:
 | |
| 				generatedColumn +
 | |
| 				(line === generatedColumnOffsetLine ? generatedColumnOffset : 0)
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	updateHash(hash) {
 | |
| 		this._sortReplacements();
 | |
| 		hash.update("ReplaceSource");
 | |
| 		this._source.updateHash(hash);
 | |
| 		hash.update(this._name || "");
 | |
| 		for (const repl of this._replacements) {
 | |
| 			hash.update(`${repl.start}${repl.end}${repl.content}${repl.name}`);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| module.exports = ReplaceSource;
 |