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
		
	
| 
											9 months ago
										 | /* | ||
|  | 	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; |