UNPKG

magic-string

Version:

Modify strings, generate sourcemaps

1,770 lines • 59.8 kB
import { encode, encodeRangeMappings } from "@jridgewell/sourcemap-codec";
//#region src/BitSet.ts
var BitSet = class BitSet {
	constructor(arg) {
		this.bits = arg instanceof BitSet ? arg.bits.slice() : [];
	}
	add(n) {
		this.bits[n >> 5] |= 1 << (n & 31);
	}
	has(n) {
		return !!(this.bits[n >> 5] & 1 << (n & 31));
	}
};
//#endregion
//#region src/Chunk.ts
var Chunk = class Chunk {
	constructor(start, end, content) {
		this.start = start;
		this.end = end;
		this.original = content;
		this.intro = "";
		this.outro = "";
		this.content = content;
		this.storeName = false;
		this.edited = false;
		this.previous = null;
		this.next = null;
	}
	appendLeft(content) {
		this.outro += content;
	}
	appendRight(content) {
		this.intro = this.intro + content;
	}
	clone() {
		const chunk = new Chunk(this.start, this.end, this.original);
		chunk.intro = this.intro;
		chunk.outro = this.outro;
		chunk.content = this.content;
		chunk.storeName = this.storeName;
		chunk.edited = this.edited;
		return chunk;
	}
	contains(index) {
		return this.start < index && index < this.end;
	}
	eachNext(fn) {
		fn(this);
		let chunk = this.next;
		while (chunk) {
			fn(chunk);
			chunk = chunk.next;
		}
	}
	eachPrevious(fn) {
		fn(this);
		let chunk = this.previous;
		while (chunk) {
			fn(chunk);
			chunk = chunk.previous;
		}
	}
	edit(content, storeName, contentOnly) {
		this.content = content;
		if (!contentOnly) {
			this.intro = "";
			this.outro = "";
		}
		this.storeName = storeName;
		this.edited = true;
		return this;
	}
	prependLeft(content) {
		this.outro = content + this.outro;
	}
	prependRight(content) {
		this.intro = content + this.intro;
	}
	reset() {
		this.intro = "";
		this.outro = "";
		if (this.edited) {
			this.content = this.original;
			this.storeName = false;
			this.edited = false;
		}
	}
	split(index) {
		const sliceIndex = index - this.start;
		const originalBefore = this.original.slice(0, sliceIndex);
		const originalAfter = this.original.slice(sliceIndex);
		this.original = originalBefore;
		const newChunk = new Chunk(index, this.end, originalAfter);
		newChunk.outro = this.outro;
		this.outro = "";
		this.end = index;
		if (this.edited) {
			newChunk.edit("", false, true);
			this.content = "";
		} else this.content = originalBefore;
		newChunk.next = this.next;
		if (newChunk.next) newChunk.next.previous = newChunk;
		newChunk.previous = this;
		this.next = newChunk;
		return newChunk;
	}
	toString() {
		return this.intro + this.content + this.outro;
	}
	trimEnd(rx) {
		this.outro = this.outro.replace(rx, "");
		if (this.outro.length) return true;
		const trimmed = this.content.replace(rx, "");
		if (trimmed.length) {
			if (trimmed !== this.content) {
				if (this.edited) this.edit(trimmed, this.storeName, true);
				else this.split(this.start + trimmed.length).edit("", void 0, true);
			}
			return true;
		} else {
			this.edit("", void 0, true);
			this.intro = this.intro.replace(rx, "");
			if (this.intro.length) return true;
		}
	}
	trimStart(rx) {
		this.intro = this.intro.replace(rx, "");
		if (this.intro.length) return true;
		const trimmed = this.content.replace(rx, "");
		if (trimmed.length) {
			if (trimmed !== this.content) {
				if (this.edited) this.edit(trimmed, this.storeName, true);
				else {
					this.split(this.end - trimmed.length);
					this.edit("", void 0, true);
				}
			}
			return true;
		} else {
			this.edit("", void 0, true);
			this.outro = this.outro.replace(rx, "");
			if (this.outro.length) return true;
		}
	}
};
//#endregion
//#region src/MagicStringError.ts
/**
* The single error type thrown by MagicString.
*
* Every message is prefixed with `[MagicString]` so its source is obvious at a
* glance, and is kept short and consistent in tone.
*/
var MagicStringError = class extends Error {
	name = "MagicStringError";
	constructor(message, options) {
		super(`[MagicString] ${message}`, options);
	}
};
//#endregion
//#region src/SourceMap.ts
function getBtoa() {
	/* v8 ignore next -- environment fallback, unreachable when `btoa` is present */
	if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function") return (str) => globalThis.btoa(unescape(encodeURIComponent(str)));
	const buffer = globalThis["Buffer"];
	if (buffer) return (str) => buffer.from(str, "utf-8").toString("base64");
	return () => {
		throw new MagicStringError("unsupported environment: `btoa` or `Buffer` is required");
	};
	/* v8 ignore stop */
}
const btoa = /* #__PURE__ */ getBtoa();
var SourceMap = class {
	constructor(properties) {
		this.version = 3;
		this.file = properties.file;
		this.sources = properties.sources;
		this.sourcesContent = properties.sourcesContent;
		this.names = properties.names;
		this.mappings = typeof properties.mappings === "string" ? properties.mappings : encode(properties.mappings);
		if (typeof properties.x_google_ignoreList !== "undefined") this.x_google_ignoreList = properties.x_google_ignoreList;
		if (typeof properties.debugId !== "undefined") this.debugId = properties.debugId;
		if (typeof properties.rangeMappings !== "undefined") {
			let shouldOutputRangeMapping = false;
			for (const line of properties.rangeMappings) if (line.length !== 0) {
				shouldOutputRangeMapping = true;
				break;
			}
			if (shouldOutputRangeMapping) this.rangeMappings = encodeRangeMappings(properties.rangeMappings);
		}
	}
	/**
	* Returns the equivalent of `JSON.stringify(map)`
	*/
	toString() {
		return JSON.stringify(this);
	}
	/**
	* Returns a DataURI containing the sourcemap. Useful for doing this sort of thing:
	* `generateMap(options?: SourceMapOptions): SourceMap;`
	*/
	toUrl() {
		return `data:application/json;charset=utf-8;base64,${btoa(this.toString())}`;
	}
};
//#endregion
//#region src/utils/getLocator.ts
function getLocator(source) {
	const lineOffsets = [0];
	for (let i = source.indexOf("\n"); i !== -1; i = source.indexOf("\n", i + 1)) lineOffsets.push(i + 1);
	return function locate(index) {
		let i = 0;
		let j = lineOffsets.length;
		while (i < j) {
			const m = i + j >> 1;
			if (index < lineOffsets[m]) j = m;
			else i = m + 1;
		}
		const line = i - 1;
		return {
			line,
			column: index - lineOffsets[line]
		};
	};
}
//#endregion
//#region src/utils/getRelativePath.ts
function getRelativePath(from, to) {
	const fromParts = from.split(/[/\\]/);
	const toParts = to.split(/[/\\]/);
	fromParts.pop();
	while (fromParts[0] === toParts[0]) {
		fromParts.shift();
		toParts.shift();
	}
	if (fromParts.length) {
		let i = fromParts.length;
		while (i--) fromParts[i] = "..";
	}
	return fromParts.concat(toParts).join("/");
}
//#endregion
//#region src/utils/guessIndent.ts
function guessIndent(code) {
	const lines = code.split("\n");
	const tabbed = lines.filter((line) => /^\t+/.test(line));
	const spaced = lines.filter((line) => /^ {2,}/.test(line));
	if (tabbed.length === 0 && spaced.length === 0) return null;
	if (tabbed.length >= spaced.length) return "	";
	const min = spaced.reduce((previous, current) => {
		const numSpaces = /^ +/.exec(current)[0].length;
		return Math.min(numSpaces, previous);
	}, Infinity);
	return " ".repeat(min);
}
//#endregion
//#region src/utils/isObject.ts
const toString = Object.prototype.toString;
function isObject(thing) {
	return toString.call(thing) === "[object Object]";
}
const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const intToChar = /* #__PURE__ */ (() => {
	const chars = /* @__PURE__ */ new Uint8Array(64);
	for (let i = 0; i < 64; i++) chars[i] = BASE64_CHARS.charCodeAt(i);
	return chars;
})();
const COMMA = 44;
const SEMICOLON = 59;
const BUFFER_SIZE = 16384;
const FLUSH_THRESHOLD = 16348;
const scratch = /* #__PURE__ */ new Uint8Array(BUFFER_SIZE);
function writeVlq(pos, num) {
	num = num < 0 ? -num << 1 | 1 : num << 1;
	do {
		let clamped = num & 31;
		num >>>= 5;
		if (num > 0) clamped |= 32;
		scratch[pos++] = intToChar[clamped];
	} while (num > 0);
	return pos;
}
const decoder = /* #__PURE__ */ new TextDecoder();
var MappingsEncoder = class {
	constructor() {
		this.out = "";
		this.pos = 0;
		this.lines = [];
		this.buffered = 0;
		this.needsComma = false;
		this.prevGenColumn = 0;
		this.prevSourceIndex = 0;
		this.prevSourceLine = 0;
		this.prevSourceColumn = 0;
		this.prevNameIndex = 0;
	}
	endLine(segments) {
		this.lines.push(segments);
		this.buffered += segments.length + 1;
		if (this.buffered >= 4096) this.drain(null);
	}
	segments(segments) {
		this.drain(segments);
	}
	finish(segments) {
		this.drain(segments);
		this.flush();
		return this.out;
	}
	drain(trailing) {
		const lines = this.lines;
		const lineCount = lines.length;
		for (let l = 0; l <= lineCount; l++) {
			const line = l < lineCount ? lines[l] : trailing;
			if (line === null) break;
			for (let i = 0; i < line.length; i++) {
				if (this.pos > FLUSH_THRESHOLD) this.flush();
				const segment = line[i];
				if (this.needsComma) scratch[this.pos++] = COMMA;
				this.needsComma = true;
				this.pos = writeVlq(this.pos, segment[0] - this.prevGenColumn);
				this.prevGenColumn = segment[0];
				this.pos = writeVlq(this.pos, segment[1] - this.prevSourceIndex);
				this.prevSourceIndex = segment[1];
				this.pos = writeVlq(this.pos, segment[2] - this.prevSourceLine);
				this.prevSourceLine = segment[2];
				this.pos = writeVlq(this.pos, segment[3] - this.prevSourceColumn);
				this.prevSourceColumn = segment[3];
				if (segment.length === 5) {
					this.pos = writeVlq(this.pos, segment[4] - this.prevNameIndex);
					this.prevNameIndex = segment[4];
				}
			}
			if (l < lineCount) {
				if (this.pos > FLUSH_THRESHOLD) this.flush();
				scratch[this.pos++] = SEMICOLON;
				this.needsComma = false;
				this.prevGenColumn = 0;
			}
		}
		lines.length = 0;
		this.buffered = 0;
		this.flush();
	}
	flush() {
		this.out += decoder.decode(scratch.subarray(0, this.pos));
		this.pos = 0;
	}
};
//#endregion
//#region src/utils/Mappings.ts
const NEWLINE_CHAR$1 = 10;
function isWordCode(code) {
	return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 95;
}
var Mappings = class {
	constructor(hires, encoder = null) {
		this.hires = hires;
		this.generatedCodeLine = 0;
		this.generatedCodeColumn = 0;
		this.raw = [];
		this.rawSegments = this.raw[this.generatedCodeLine] = [];
		this.rawRangeMappings = [];
		this.rawRangeMappingsIndices = this.rawRangeMappings[this.generatedCodeLine] = [];
		this.encoder = encoder;
	}
	nextLine() {
		if (this.encoder === null) {
			this.generatedCodeLine += 1;
			this.raw[this.generatedCodeLine] = this.rawSegments = [];
		} else {
			this.encoder.endLine(this.rawSegments);
			this.rawSegments = [];
			this.generatedCodeLine += 1;
		}
		this.generatedCodeColumn = 0;
		this.rawRangeMappings[this.generatedCodeLine] = this.rawRangeMappingsIndices = [];
	}
	addEdit(sourceIndex, content, loc, nameIndex) {
		if (content.length) {
			const contentLengthMinusOne = content.length - 1;
			let contentLineEnd = content.indexOf("\n", 0);
			let previousContentLineEnd = -1;
			while (contentLineEnd >= 0 && contentLengthMinusOne > contentLineEnd) {
				const segment = [
					this.generatedCodeColumn,
					sourceIndex,
					loc.line,
					loc.column
				];
				if (nameIndex >= 0) segment.push(nameIndex);
				this.rawSegments.push(segment);
				this.nextLine();
				previousContentLineEnd = contentLineEnd;
				contentLineEnd = content.indexOf("\n", contentLineEnd + 1);
			}
			const segment = [
				this.generatedCodeColumn,
				sourceIndex,
				loc.line,
				loc.column
			];
			if (nameIndex >= 0) segment.push(nameIndex);
			this.rawSegments.push(segment);
			this.advance(content.slice(previousContentLineEnd + 1));
		}
	}
	addUneditedChunk(sourceIndex, chunk, original, loc, sourcemapLocations) {
		const end = chunk.end;
		let i = chunk.start;
		if (this.hires) {
			const boundary = this.hires === "boundary";
			const experimentalRange = this.hires === "experimental-range";
			const encoder = experimentalRange ? null : this.encoder;
			let charInHiresBoundary = false;
			while (i < end) {
				if (encoder !== null && this.rawSegments.length >= 4096) {
					encoder.segments(this.rawSegments);
					this.rawSegments.length = 0;
				}
				if (experimentalRange && i + 1 >= end) this.rawSegments.push([
					this.generatedCodeColumn,
					sourceIndex,
					loc.line,
					loc.column
				]);
				const code = original.charCodeAt(i);
				if (code === NEWLINE_CHAR$1) {
					loc.line += 1;
					loc.column = 0;
					this.nextLine();
					charInHiresBoundary = false;
				} else {
					if (boundary) {
						if (isWordCode(code)) {
							if (!charInHiresBoundary) {
								this.rawSegments.push([
									this.generatedCodeColumn,
									sourceIndex,
									loc.line,
									loc.column
								]);
								charInHiresBoundary = true;
							}
						} else {
							this.rawSegments.push([
								this.generatedCodeColumn,
								sourceIndex,
								loc.line,
								loc.column
							]);
							charInHiresBoundary = false;
						}
					} else if (experimentalRange) {
						if (i === chunk.start) {
							this.rawRangeMappingsIndices.push(this.rawSegments.length);
							this.rawSegments.push([
								this.generatedCodeColumn,
								sourceIndex,
								loc.line,
								loc.column
							]);
						}
					} else this.rawSegments.push([
						this.generatedCodeColumn,
						sourceIndex,
						loc.line,
						loc.column
					]);
					loc.column += 1;
					this.generatedCodeColumn += 1;
				}
				i += 1;
			}
		} else {
			const bits = sourcemapLocations.bits;
			while (i < end) {
				let newline = original.indexOf("\n", i);
				if (newline === -1 || newline > end) newline = end;
				if (newline > i) {
					this.rawSegments.push([
						this.generatedCodeColumn,
						sourceIndex,
						loc.line,
						loc.column
					]);
					for (let w = i + 1 >> 5, last = newline - 1 >> 5; w <= last; w++) {
						let word = bits[w];
						if (!word) continue;
						const base = w << 5;
						while (word) {
							const lowest = word & -word;
							const index = base + 31 - Math.clz32(lowest);
							if (index > i && index < newline) {
								const offset = index - i;
								this.rawSegments.push([
									this.generatedCodeColumn + offset,
									sourceIndex,
									loc.line,
									loc.column + offset
								]);
							}
							word ^= lowest;
						}
					}
					loc.column += newline - i;
					this.generatedCodeColumn += newline - i;
				}
				if (newline === end) break;
				loc.line += 1;
				loc.column = 0;
				this.nextLine();
				i = newline + 1;
			}
		}
	}
	advance(str) {
		if (!str) return;
		const lastNewline = str.lastIndexOf("\n");
		for (let i = str.indexOf("\n"); i !== -1; i = str.indexOf("\n", i + 1)) this.nextLine();
		this.generatedCodeColumn += str.length - lastNewline - 1;
	}
};
//#endregion
//#region src/MagicString.ts
const n = "\n";
const NEWLINE_CHAR = "\n".charCodeAt(0);
const CR_CHAR = "\r".charCodeAt(0);
const warned = {
	insertLeft: false,
	insertRight: false,
	storeName: false
};
/**
* Expands the `$` substitution patterns that `String.prototype.replace` accepts
* in a string replacement.
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter
*
* `captures` holds the capture groups in order, so `captures[0]` is `$1`.
* `namedCaptures` is `undefined` when the pattern has no named groups, and
* `$<name>` is then literal text - which is also the case for a string search
* value, where there are no groups of either kind.
*/
function expandReplacement(replacement, matched, position, str, captures, namedCaptures) {
	if (!replacement.includes("$")) return replacement;
	let result = "";
	let index = 0;
	while (index < replacement.length) {
		const dollar = replacement.indexOf("$", index);
		if (dollar === -1) {
			result += replacement.slice(index);
			break;
		}
		result += replacement.slice(index, dollar);
		const char = replacement[dollar + 1];
		let expansion = "$";
		let consumed = 1;
		if (char === "$") consumed = 2;
		else if (char === "&") {
			expansion = matched;
			consumed = 2;
		} else if (char === "`") {
			expansion = str.slice(0, position);
			consumed = 2;
		} else if (char === "'") {
			expansion = str.slice(position + matched.length);
			consumed = 2;
		} else if (char === "<" && namedCaptures !== void 0) {
			const close = replacement.indexOf(">", dollar + 2);
			if (close !== -1) {
				expansion = namedCaptures[replacement.slice(dollar + 2, close)] ?? "";
				consumed = close + 1 - dollar;
			}
		} else if (char >= "0" && char <= "9") {
			const second = replacement[dollar + 2];
			const double = second >= "0" && second <= "9" ? Number(char + second) : NaN;
			const single = Number(char);
			if (double >= 1 && double <= captures.length) {
				expansion = captures[double - 1] ?? "";
				consumed = 3;
			} else if (single >= 1 && single <= captures.length) {
				expansion = captures[single - 1] ?? "";
				consumed = 2;
			}
		}
		result += expansion;
		index = dollar + consumed;
	}
	return result;
}
var MagicString = class MagicString {
	constructor(string, options = {}) {
		const chunk = new Chunk(0, string.length, string);
		Object.defineProperties(this, {
			original: {
				writable: true,
				value: string
			},
			outro: {
				writable: true,
				value: ""
			},
			intro: {
				writable: true,
				value: ""
			},
			firstChunk: {
				writable: true,
				value: chunk
			},
			lastChunk: {
				writable: true,
				value: chunk
			},
			lastSearchedChunk: {
				writable: true,
				value: chunk
			},
			byStart: {
				writable: true,
				value: /* @__PURE__ */ new Map([[0, chunk]])
			},
			byEnd: {
				writable: true,
				value: /* @__PURE__ */ new Map([[string.length, chunk]])
			},
			filename: {
				writable: true,
				value: options.filename
			},
			indentExclusionRanges: {
				writable: true,
				value: options.indentExclusionRanges
			},
			sourcemapLocations: {
				writable: true,
				value: new BitSet()
			},
			storedNames: {
				writable: true,
				value: {}
			},
			indentStr: {
				writable: true,
				value: void 0
			},
			ignoreList: {
				writable: true,
				value: options.ignoreList
			},
			offset: {
				writable: true,
				value: options.offset || 0
			},
			hasMovedChunks: {
				writable: true,
				value: false
			}
		});
	}
	/**
	* Adds the specified character index (with respect to the original string) to sourcemap mappings, if `hires` is false.
	*/
	addSourcemapLocation(char) {
		this.sourcemapLocations.add(char);
	}
	/**
	* Appends the specified content to the end of the string.
	*/
	append(content) {
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this.outro += content;
		return this;
	}
	/**
	* Appends the specified content at the index in the original string.
	* If a range *ending* with index is subsequently moved, the insert will be moved with it.
	* See also `s.prependLeft(...)`.
	*/
	appendLeft(index, content) {
		index = index + this.offset;
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this._split(index);
		const chunk = this.byEnd.get(index);
		if (chunk) chunk.appendLeft(content);
		else this.intro += content;
		return this;
	}
	/**
	* Appends the specified content at the index in the original string.
	* If a range *starting* with index is subsequently moved, the insert will be moved with it.
	* See also `s.prependRight(...)`.
	*/
	appendRight(index, content) {
		index = index + this.offset;
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this._split(index);
		const chunk = this.byStart.get(index);
		if (chunk) chunk.appendRight(content);
		else this.outro += content;
		return this;
	}
	/**
	* Does what you'd expect.
	*/
	clone() {
		const cloned = new MagicString(this.original, {
			filename: this.filename,
			ignoreList: this.ignoreList,
			offset: this.offset
		});
		let originalChunk = this.firstChunk;
		let clonedChunk = cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone();
		while (originalChunk) {
			cloned.byStart.set(clonedChunk.start, clonedChunk);
			cloned.byEnd.set(clonedChunk.end, clonedChunk);
			const nextOriginalChunk = originalChunk.next;
			const nextClonedChunk = nextOriginalChunk && nextOriginalChunk.clone();
			if (nextClonedChunk) {
				clonedChunk.next = nextClonedChunk;
				nextClonedChunk.previous = clonedChunk;
				clonedChunk = nextClonedChunk;
			}
			originalChunk = nextOriginalChunk;
		}
		cloned.lastChunk = clonedChunk;
		if (this.indentExclusionRanges) cloned.indentExclusionRanges = this.indentExclusionRanges.slice();
		cloned.sourcemapLocations = new BitSet(this.sourcemapLocations);
		cloned.storedNames = { ...this.storedNames };
		cloned.intro = this.intro;
		cloned.outro = this.outro;
		cloned.hasMovedChunks = this.hasMovedChunks;
		return cloned;
	}
	/**
	* Generates a sourcemap object with raw mappings in array form, rather than encoded as a string.
	* Useful if you need to manipulate the sourcemap further, but most of the time you will use `generateMap` instead.
	*/
	generateDecodedMap(options) {
		options = options || {};
		const mappings = new Mappings(options.hires);
		const names = this._generateMappings(mappings);
		return {
			...this._mapProperties(options, names),
			mappings: mappings.raw,
			rangeMappings: mappings.rawRangeMappings
		};
	}
	/**
	* Generates a version 3 sourcemap.
	*/
	generateMap(options) {
		options = options || {};
		const encoder = new MappingsEncoder();
		const mappings = new Mappings(options.hires, encoder);
		const names = this._generateMappings(mappings);
		return new SourceMap({
			...this._mapProperties(options, names),
			mappings: encoder.finish(mappings.rawSegments),
			rangeMappings: mappings.rawRangeMappings
		});
	}
	/** @internal */
	_generateMappings(mappings) {
		const sourceIndex = 0;
		const names = Object.keys(this.storedNames);
		const locate = getLocator(this.original);
		if (this.intro) mappings.advance(this.intro);
		this.firstChunk.eachNext((chunk) => {
			const loc = locate(chunk.start);
			if (chunk.intro.length) mappings.advance(chunk.intro);
			if (chunk.edited) mappings.addEdit(sourceIndex, chunk.content, loc, chunk.storeName ? names.indexOf(chunk.original) : -1);
			else mappings.addUneditedChunk(sourceIndex, chunk, this.original, loc, this.sourcemapLocations);
			if (chunk.outro.length) mappings.advance(chunk.outro);
		});
		if (this.outro) mappings.advance(this.outro);
		return names;
	}
	/** @internal */
	_mapProperties(options, names) {
		return {
			file: options.file ? options.file.split(/[/\\]/).pop() : void 0,
			sources: [options.source ? getRelativePath(options.file || "", options.source) : options.file || ""],
			sourcesContent: options.includeContent ? [this.original] : void 0,
			names,
			x_google_ignoreList: this.ignoreList ? [0] : void 0
		};
	}
	/** @internal */
	_ensureindentStr() {
		if (this.indentStr === void 0) this.indentStr = guessIndent(this.original);
	}
	/** @internal */
	_getRawIndentString() {
		this._ensureindentStr();
		return this.indentStr;
	}
	getIndentString() {
		this._ensureindentStr();
		return this.indentStr === null ? "	" : this.indentStr;
	}
	indent(indentStr, options) {
		const pattern = /^[^\r\n]/gm;
		if (isObject(indentStr)) {
			options = indentStr;
			indentStr = void 0;
		}
		if (indentStr === void 0) {
			this._ensureindentStr();
			indentStr = this.indentStr || "	";
		}
		if (indentStr === "") return this;
		const resolvedIndentStr = indentStr;
		options = options || {};
		const isExcluded = {};
		if (options.exclude) (typeof options.exclude[0] === "number" ? [options.exclude] : options.exclude).forEach((exclusion) => {
			for (let i = exclusion[0]; i < exclusion[1]; i += 1) isExcluded[i] = true;
		});
		let shouldIndentNextCharacter = options.indentStart !== false;
		const indentPiece = (str) => {
			if (str === "") return str;
			const indented = str.replace(pattern, (match, offset) => offset > 0 || shouldIndentNextCharacter ? `${resolvedIndentStr}${match}` : match);
			shouldIndentNextCharacter = str[str.length - 1] === "\n";
			return indented;
		};
		this.intro = indentPiece(this.intro);
		let charIndex = 0;
		let chunk = this.firstChunk;
		const indentAt = (index) => {
			shouldIndentNextCharacter = false;
			if (index === chunk.start) chunk.appendRight(resolvedIndentStr);
			else {
				this._splitChunk(chunk, index);
				chunk = chunk.next;
				chunk.prependRight(resolvedIndentStr);
			}
		};
		while (chunk) {
			const end = chunk.end;
			if (!isExcluded[chunk.start]) chunk.intro = indentPiece(chunk.intro);
			if (chunk.edited) {
				if (!isExcluded[charIndex]) chunk.content = indentPiece(chunk.content);
			} else if (options.exclude) {
				charIndex = chunk.start;
				while (charIndex < end) {
					if (!isExcluded[charIndex]) {
						const char = this.original.charCodeAt(charIndex);
						if (char === NEWLINE_CHAR) shouldIndentNextCharacter = true;
						else if (char !== CR_CHAR && shouldIndentNextCharacter) indentAt(charIndex);
					}
					charIndex += 1;
				}
			} else {
				charIndex = chunk.start;
				while (charIndex < end) {
					if (!shouldIndentNextCharacter) {
						const nextLine = this.original.indexOf(n, charIndex);
						if (nextLine === -1 || nextLine >= end) break;
						shouldIndentNextCharacter = true;
						charIndex = nextLine + 1;
						continue;
					}
					const char = this.original.charCodeAt(charIndex);
					if (char === NEWLINE_CHAR || char === CR_CHAR) {
						charIndex += 1;
						continue;
					}
					indentAt(charIndex);
					charIndex += 1;
				}
			}
			if (!isExcluded[chunk.end - 1]) chunk.outro = indentPiece(chunk.outro);
			charIndex = chunk.end;
			chunk = chunk.next;
		}
		this.outro = indentPiece(this.outro);
		return this;
	}
	/** @internal */
	insert() {
		throw new MagicStringError("insert() is deprecated, use appendLeft() or prependRight()");
	}
	/** @internal */
	insertLeft(index, content) {
		if (!warned.insertLeft) {
			console.warn("magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead");
			warned.insertLeft = true;
		}
		return this.appendLeft(index, content);
	}
	/** @internal */
	insertRight(index, content) {
		if (!warned.insertRight) {
			console.warn("magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead");
			warned.insertRight = true;
		}
		return this.prependRight(index, content);
	}
	/**
	* Moves the characters from `start` and `end` to `index`.
	*
	* `affinity` controls where the range is anchored at `index`. With the
	* default `'right'`, it is inserted before the content that starts at `index`;
	* with `'left'`, it is inserted after the content that ends at `index`. The
	* two differ only when other content has already been moved to that boundary,
	* mirroring the `appendLeft`/`appendRight` distinction.
	*/
	move(start, end, index, affinity = "right") {
		start = start + this.offset;
		end = end + this.offset;
		index = index + this.offset;
		if (start === end) return this;
		if (index >= start && index <= end) throw new MagicStringError("cannot move a selection inside itself");
		this._split(start);
		this._split(end);
		this._split(index);
		const first = this.byStart.get(start);
		const last = this.byEnd.get(end);
		if (this.hasMovedChunks) {
			let cursor = first;
			while (cursor !== last) {
				cursor = cursor.next;
				if (!cursor || cursor.start < start || cursor.end > end) throw new MagicStringError(`cannot move ${start} to ${end} because an earlier move split that range`);
			}
		}
		const oldLeft = first.previous;
		const oldRight = last.next;
		let newLeft;
		let newRight;
		if (affinity === "left") {
			newLeft = this.byEnd.get(index) ?? null;
			if (!newLeft) {
				if (first === this.firstChunk) return this;
				newRight = this.firstChunk;
			} else {
				if (newLeft.next === first) return this;
				newRight = newLeft.next;
			}
		} else {
			newRight = this.byStart.get(index) ?? null;
			if (!newRight) {
				if (last === this.lastChunk) return this;
				newLeft = this.lastChunk;
			} else {
				if (newRight.previous === last) return this;
				newLeft = newRight.previous;
			}
		}
		if (oldLeft) oldLeft.next = oldRight;
		if (oldRight) oldRight.previous = oldLeft;
		if (newLeft) newLeft.next = first;
		if (newRight) newRight.previous = last;
		if (!first.previous) this.firstChunk = last.next;
		if (!last.next) {
			this.lastChunk = first.previous;
			this.lastChunk.next = null;
		}
		first.previous = newLeft;
		last.next = newRight || null;
		if (!newLeft) this.firstChunk = first;
		if (!newRight) this.lastChunk = last;
		this.hasMovedChunks = true;
		return this;
	}
	/**
	* Replaces the characters from `start` to `end` with `content`, along with the appended/prepended content in
	* that range. The same restrictions as `s.remove()` apply.
	*
	* The fourth argument is optional. It can have a storeName property - if true, the original name will be stored
	* for later inclusion in a sourcemap's names array - and a contentOnly property which determines whether only
	* the content is overwritten, or anything that was appended/prepended to the range as well.
	*
	* It may be preferred to use `s.update(...)` instead if you wish to avoid overwriting the appended/prepended content.
	*/
	overwrite(start, end, content, options) {
		const optionObject = typeof options === "object" && options ? options : {};
		return this.update(start, end, content, {
			...optionObject,
			overwrite: !optionObject.contentOnly
		});
	}
	/**
	* Replaces the characters from `start` to `end` with `content`. The same restrictions as `s.remove()` apply.
	*
	* The fourth argument is optional. It can have a storeName property - if true, the original name will be stored
	* for later inclusion in a sourcemap's names array - and an overwrite property which determines whether only
	* the content is overwritten, or anything that was appended/prepended to the range as well.
	*/
	update(start, end, content, options) {
		start = start + this.offset;
		end = end + this.offset;
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		if (this.original.length !== 0) {
			if (start < 0) start = Math.max(0, start + this.original.length);
			if (end < 0) end = Math.max(0, end + this.original.length);
		}
		if (start < 0) throw new MagicStringError(`start ${start} is out of bounds`);
		if (end > this.original.length) throw new MagicStringError(`end ${end} is out of bounds`);
		if (start === end) throw new MagicStringError(`cannot overwrite a zero-length range at ${start}, use appendLeft() or prependRight()`);
		if (start > end) throw new MagicStringError(`end must be greater than start (start: ${start}, end: ${end})`);
		this._split(start);
		this._split(end);
		if (options === true) {
			if (!warned.storeName) {
				console.warn("The final argument to magicString.overwrite(...) should be an options object. See https://github.com/rich-harris/magic-string");
				warned.storeName = true;
			}
			options = { storeName: true };
		}
		const optionObject = typeof options === "object" && options ? options : {};
		const storeName = optionObject.storeName || false;
		const overwrite = optionObject.overwrite || false;
		if (storeName) {
			const original = this.original.slice(start, end);
			Object.defineProperty(this.storedNames, original, {
				writable: true,
				value: true,
				enumerable: true
			});
		}
		const first = this.byStart.get(start);
		const last = this.byEnd.get(end);
		/* v8 ignore else -- unreachable: a valid start/end always yields a `first` chunk */
		if (first) {
			let chunk = first;
			while (chunk !== last) {
				if (chunk.next !== this.byStart.get(chunk.end)) throw new MagicStringError("cannot overwrite across a split point");
				chunk = chunk.next;
				chunk.edit("", false);
			}
			first.edit(content, storeName, !overwrite);
		} else {
			const newChunk = new Chunk(start, end, "").edit(content, storeName);
			last.next = newChunk;
			newChunk.previous = last;
		}
		return this;
	}
	/**
	* Prepends the string with the specified content.
	*/
	prepend(content) {
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this.intro = content + this.intro;
		return this;
	}
	/**
	* Same as `s.appendLeft(...)`, except that the inserted content will go *before* any previous appends or prepends at index
	*/
	prependLeft(index, content) {
		index = index + this.offset;
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this._split(index);
		const chunk = this.byEnd.get(index);
		if (chunk) chunk.prependLeft(content);
		else this.intro = content + this.intro;
		return this;
	}
	/**
	* Same as `s.appendRight(...)`, except that the inserted content will go *before* any previous appends or prepends at `index`
	*/
	prependRight(index, content) {
		index = index + this.offset;
		if (typeof content !== "string") throw new MagicStringError(`content must be a string, got ${typeof content}`);
		this._split(index);
		const chunk = this.byStart.get(index);
		if (chunk) chunk.prependRight(content);
		else this.outro = content + this.outro;
		return this;
	}
	/**
	* Removes the characters from `start` to `end` (of the original string, **not** the generated string).
	* Content appended or prepended at positions strictly inside the range is removed with it, while
	* content attached at `start` or `end` is preserved — use `s.overwrite(start, end, '')` to remove
	* the range including its edge inserts.
	* Repeated removals and removals with partially overlapping ranges are allowed.
	*/
	remove(start, end) {
		start = start + this.offset;
		end = end + this.offset;
		if (this.original.length !== 0) {
			if (start < 0) start = Math.max(0, start + this.original.length);
			if (end < 0) end = Math.max(0, end + this.original.length);
		}
		if (start === end) return this;
		if (start < 0 || end > this.original.length) throw new MagicStringError(`range ${start}–${end} is out of bounds`);
		if (start > end) throw new MagicStringError(`end must be greater than start (start: ${start}, end: ${end})`);
		this._split(start);
		this._split(end);
		let chunk = this.byStart.get(start);
		while (chunk) {
			if (chunk.start > start) chunk.intro = "";
			if (chunk.end < end) chunk.outro = "";
			chunk.edit("", false, true);
			chunk = end > chunk.end ? this.byStart.get(chunk.end) : null;
		}
		return this;
	}
	/**
	* Reset the modified characters from `start` to `end` (of the original string, **not** the generated string).
	*/
	reset(start, end) {
		start = start + this.offset;
		end = end + this.offset;
		if (this.original.length !== 0) {
			if (start < 0) start = Math.max(0, start + this.original.length);
			if (end < 0) end = Math.max(0, end + this.original.length);
		}
		if (start === end) return this;
		if (start < 0 || end > this.original.length) throw new MagicStringError(`range ${start}–${end} is out of bounds`);
		if (start > end) throw new MagicStringError(`end must be greater than start (start: ${start}, end: ${end})`);
		this._split(start);
		this._split(end);
		let chunk = this.byStart.get(start);
		while (chunk) {
			chunk.reset();
			chunk = end > chunk.end ? this.byStart.get(chunk.end) : null;
		}
		return this;
	}
	lastChar() {
		if (this.outro.length) return this.outro[this.outro.length - 1];
		let chunk = this.lastChunk;
		while (chunk) {
			if (chunk.outro.length) return chunk.outro[chunk.outro.length - 1];
			if (chunk.content.length) return chunk.content[chunk.content.length - 1];
			if (chunk.intro.length) return chunk.intro[chunk.intro.length - 1];
			chunk = chunk.previous;
		}
		if (this.intro.length) return this.intro[this.intro.length - 1];
		return "";
	}
	lastLine() {
		let lineIndex = this.outro.lastIndexOf(n);
		if (lineIndex !== -1) return this.outro.substr(lineIndex + 1);
		let lineStr = this.outro;
		let chunk = this.lastChunk;
		while (chunk) {
			if (chunk.outro.length > 0) {
				lineIndex = chunk.outro.lastIndexOf(n);
				if (lineIndex !== -1) return chunk.outro.substr(lineIndex + 1) + lineStr;
				lineStr = chunk.outro + lineStr;
			}
			if (chunk.content.length > 0) {
				lineIndex = chunk.content.lastIndexOf(n);
				if (lineIndex !== -1) return chunk.content.substr(lineIndex + 1) + lineStr;
				lineStr = chunk.content + lineStr;
			}
			if (chunk.intro.length > 0) {
				lineIndex = chunk.intro.lastIndexOf(n);
				if (lineIndex !== -1) return chunk.intro.substr(lineIndex + 1) + lineStr;
				lineStr = chunk.intro + lineStr;
			}
			chunk = chunk.previous;
		}
		lineIndex = this.intro.lastIndexOf(n);
		if (lineIndex !== -1) return this.intro.substr(lineIndex + 1) + lineStr;
		return this.intro + lineStr;
	}
	/**
	* Returns the content of the generated string that corresponds to the slice between `start` and `end` of the original string.
	* Throws error if the indices are for characters that were already removed.
	*/
	slice(start = 0, end = this.original.length - this.offset) {
		start = start + this.offset;
		end = end + this.offset;
		if (this.original.length !== 0) {
			if (start < 0) start = Math.max(0, start + this.original.length);
			if (end < 0) end = Math.max(0, end + this.original.length);
		}
		let result = "";
		let chunk = this.firstChunk;
		while (chunk && (chunk.start > start || chunk.end <= start)) {
			if (chunk.start < end && chunk.end >= end || end === 0 && chunk.start === 0) return result;
			chunk = chunk.next;
		}
		if (chunk && chunk.edited && chunk.start !== start) throw new MagicStringError(`cannot use edited character ${start} as slice start anchor`);
		const startChunk = chunk;
		while (chunk) {
			if (end === 0 && chunk.start === 0) break;
			if (chunk.intro && (startChunk !== chunk || chunk.start === start)) result += chunk.intro;
			const containsEnd = chunk.start < end && chunk.end >= end;
			if (containsEnd && chunk.edited && chunk.end !== end) throw new MagicStringError(`cannot use edited character ${end} as slice end anchor`);
			const sliceStart = startChunk === chunk ? start - chunk.start : 0;
			const sliceEnd = containsEnd ? chunk.content.length + end - chunk.end : chunk.content.length;
			result += chunk.content.slice(sliceStart, sliceEnd);
			if (chunk.outro && (!containsEnd || chunk.end === end)) result += chunk.outro;
			if (containsEnd) break;
			chunk = chunk.next;
		}
		return result;
	}
	/**
	* Returns a clone of `s`, with all content before the `start` and `end` characters of the original string removed.
	*/
	snip(start, end) {
		const clone = this.clone();
		clone.remove(0, start);
		clone.remove(end, clone.original.length);
		return clone;
	}
	/** @internal */
	_split(index) {
		if (this.byStart.get(index) || this.byEnd.get(index)) return;
		let chunk = this.lastSearchedChunk;
		let previousChunk = chunk;
		const searchForward = index > chunk.end;
		while (chunk) {
			if (chunk.contains(index)) return this._splitChunk(chunk, index);
			chunk = searchForward ? this.byStart.get(chunk.end) : this.byEnd.get(chunk.start);
			if (chunk === previousChunk) return;
			previousChunk = chunk;
		}
	}
	/** @internal */
	_splitChunk(chunk, index) {
		if (chunk.edited && chunk.content.length) {
			const loc = getLocator(this.original)(index);
			throw new MagicStringError(`cannot split a chunk that has already been edited (${loc.line}:${loc.column} – "${chunk.original}")`);
		}
		const newChunk = chunk.split(index);
		this.byEnd.set(index, chunk);
		this.byStart.set(index, newChunk);
		this.byEnd.set(newChunk.end, newChunk);
		if (chunk === this.lastChunk) this.lastChunk = newChunk;
		this.lastSearchedChunk = chunk;
		return true;
	}
	/**
	* Returns the generated string.
	*/
	toString() {
		let str = this.intro;
		let chunk = this.firstChunk;
		while (chunk) {
			str += chunk.toString();
			chunk = chunk.next;
		}
		return str + this.outro;
	}
	/**
	* Returns true if the resulting source is empty (disregarding white space).
	*/
	isEmpty() {
		if (this.intro.length && this.intro.trim()) return false;
		let chunk = this.firstChunk;
		while (chunk) {
			if (chunk.intro.length && chunk.intro.trim() || chunk.content.length && chunk.content.trim() || chunk.outro.length && chunk.outro.trim()) return false;
			chunk = chunk.next;
		}
		if (this.outro.length && this.outro.trim()) return false;
		return true;
	}
	length() {
		let chunk = this.firstChunk;
		let length = 0;
		while (chunk) {
			length += chunk.intro.length + chunk.content.length + chunk.outro.length;
			chunk = chunk.next;
		}
		return length;
	}
	/**
	* Removes empty lines from the start and end.
	*/
	trimLines() {
		return this.trim("[\\r\\n]");
	}
	/**
	* Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the start and end.
	*/
	trim(charType) {
		return this.trimStart(charType).trimEnd(charType);
	}
	/** @internal */
	trimEndAborted(charType) {
		const rx = new RegExp(`${charType || "\\s"}+$`);
		this.outro = this.outro.replace(rx, "");
		if (this.outro.length) return true;
		let chunk = this.lastChunk;
		do {
			const end = chunk.end;
			const aborted = chunk.trimEnd(rx);
			if (chunk.end !== end) {
				if (this.lastChunk === chunk) this.lastChunk = chunk.next;
				this.byEnd.set(chunk.end, chunk);
				this.byStart.set(chunk.next.start, chunk.next);
				this.byEnd.set(chunk.next.end, chunk.next);
			}
			if (aborted) return true;
			chunk = chunk.previous;
		} while (chunk);
		this.intro = this.intro.replace(rx, "");
		return this.intro.length > 0;
	}
	/**
	* Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the end.
	*/
	trimEnd(charType) {
		this.trimEndAborted(charType);
		return this;
	}
	/** @internal */
	trimStartAborted(charType) {
		const rx = new RegExp(`^${charType || "\\s"}+`);
		this.intro = this.intro.replace(rx, "");
		if (this.intro.length) return true;
		let chunk = this.firstChunk;
		do {
			const end = chunk.end;
			const aborted = chunk.trimStart(rx);
			if (chunk.end !== end) {
				if (chunk === this.lastChunk) this.lastChunk = chunk.next;
				this.byEnd.set(chunk.end, chunk);
				this.byStart.set(chunk.next.start, chunk.next);
				this.byEnd.set(chunk.next.end, chunk.next);
			}
			if (aborted) return true;
			chunk = chunk.next;
		} while (chunk);
		this.outro = this.outro.replace(rx, "");
		return this.outro.length > 0;
	}
	/**
	* Trims content matching `charType` (defaults to `\s`, i.e. whitespace) from the start.
	*/
	trimStart(charType) {
		this.trimStartAborted(charType);
		return this;
	}
	/**
	* Indicates if the string has been changed.
	*/
	hasChanged() {
		if (this.intro || this.outro) return true;
		let outputIndex = 0;
		let chunk = this.firstChunk;
		while (chunk) {
			if (chunk.intro || chunk.outro) return true;
			if (!chunk.edited && chunk.start === outputIndex) outputIndex = chunk.end;
			else {
				if (!this.original.startsWith(chunk.content, outputIndex)) return true;
				outputIndex += chunk.content.length;
			}
			chunk = chunk.next;
		}
		return outputIndex !== this.original.length;
	}
	/**
	* Whether the original range [start, end) has had any of its characters
	* removed. `replace`/`replaceAll` search `original`, so a match can land on
	* text that is no longer in the output - overwriting it would resurrect the
	* removed characters, so such matches are skipped instead.
	*
	* @internal
	*/
	_hasRemovedContent(start, end) {
		let chunk = this.byStart.get(start);
		if (!chunk) {
			chunk = this.lastSearchedChunk;
			const searchForward = start >= chunk.end;
			while (chunk && !chunk.contains(start)) chunk = searchForward ? this.byStart.get(chunk.end) : this.byEnd.get(chunk.start);
			if (chunk) this.lastSearchedChunk = chunk;
		}
		while (chunk && chunk.start < end) {
			if (chunk.content === "") return true;
			chunk = this.byStart.get(chunk.end);
		}
		return false;
	}
	/** @internal */
	_replaceRegexp(searchValue, replacement) {
		function getReplacement(match, str) {
			if (typeof replacement === "string") return expandReplacement(replacement, match[0], match.index, str, match.slice(1), match.groups);
			else return match.groups === void 0 ? replacement(match[0], ...match.slice(1), match.index, str) : replacement(match[0], ...match.slice(1), match.index, str, match.groups);
		}
		const replaceMatch = (match) => {
			/* v8 ignore next 2 -- `match.index` is always defined for matches from `matchAll` */
			if (match.index == null) return false;
			if (this._hasRemovedContent(match.index, match.index + match[0].length)) return false;
			const replacement = getReplacement(match, this.original);
			if (replacement === match[0]) return true;
			if (match[0].length === 0) this.appendRight(match.index, replacement);
			else this.overwrite(match.index, match.index + match[0].length, replacement);
			return true;
		};
		if (searchValue.global) {
			searchValue.lastIndex = 0;
			for (const match of this.original.matchAll(searchValue)) replaceMatch(match);
		} else {
			const match = this.original.match(searchValue);
			if (match && !replaceMatch(match)) {
				const global = new RegExp(searchValue.source, `${searchValue.flags}g`);
				for (const next of this.original.matchAll(global)) if (replaceMatch(next)) break;
			}
		}
		return this;
	}
	/** @internal */
	_replaceString(string, replacement) {
		const { original } = this;
		let index = original.indexOf(string);
		while (index !== -1) {
			if (this._hasRemovedContent(index, index + string.length)) {
				index = original.indexOf(string, index + string.length);
				continue;
			}
			if (typeof replacement === "function") replacement = replacement(string, index, original);
			else replacement = expandReplacement(replacement, string, index, original, [], void 0);
			if (string !== replacement) {
				if (string.length === 0) this.appendRight(index, replacement);
				else this.overwrite(index, index + string.length, replacement);
			}
			break;
		}
		return this;
	}
	/**
	* String replacement with RegExp or string.
	*/
	replace(searchValue, replacement) {
		if (typeof searchValue === "string") return this._replaceString(searchValue, replacement);
		return this._replaceRegexp(searchValue, replacement);
	}
	/** @internal */
	_replaceAllString(string, replacement) {
		const { original } = this;
		const stringLength = string.length;
		if (stringLength === 0) {
			for (let index = 0; index <= original.length; index += 1) {
				if (this._hasRemovedContent(index, index)) continue;
				const _replacement = typeof replacement === "function" ? replacement("", index, original) : expandReplacement(replacement, "", index, original, [], void 0);
				if (_replacement !== "") this.appendRight(index, _replacement);
			}
			return this;
		}
		for (let index = original.indexOf(string); index !== -1; index = original.indexOf(string, index + stringLength)) {
			if (this._hasRemovedContent(index, index + stringLength)) continue;
			const previous = original.slice(index, index + stringLength);
			const _replacement = typeof replacement === "function" ? replacement(previous, index, original) : expandReplacement(replacement, previous, index, original, [], void 0);
			if (previous !== _replacement) this.overwrite(index, index + stringLength, _replacement);
		}
		return this;
	}
	/**
	* Same as `s.replace`, but replace all matched strings instead of just one.
	*/
	replaceAll(searchValue, replacement) {
		if (typeof searchValue === "string") return this._replaceAllString(searchValue, replacement);
		if (!searchValue.global) throw new MagicStringError("replaceAll() requires a global RegExp");
		return this._replaceRegexp(searchValue, replacement);
	}
};
//#endregion
//#region src/Bundle.ts
const hasOwnProp = Object.prototype.hasOwnProperty;
var Bundle = class Bundle {
	constructor(options = {}) {
		this.intro = options.intro || "";
		this.separator = options.separator !== void 0 ? options.separator : "\n";
		this.sources = [];
		this.uniqueSources = [];
		this.uniqueSourceIndexByFilename = {};
	}
	/**
	* Adds the specified source to the bundle, which can either be a `MagicString` object directly,
	* or an options object that holds a magic string `content` property and optionally provides
	* a `filename` for the source within the bundle, as well as an optional `ignoreList` hint
	* (which defaults to `false`). The `filename` is used when constructing the source map for the
	* bundle, to identify this `source` in the source map's `sources` field. The `ignoreList` hint
	* is used to populate the `x_google_ignoreList` extension field in the source map, which is a
	* mechanism for tools to signal to debuggers that certain sources should be ignored by default
	* (depending on user preferences).
	*/
	addSource(source) {
		if (source instanceof MagicString) return this.addSource({
			content: source,
			filename: source.filename,
			separator: this.separator
		});
		if (!isObject(source) || !source.content) throw new MagicStringError("addSource() requires a `content` property that is a MagicString");
		[
			"filename",
			"ignoreList",
			"indentExclusionRanges",
			"separator"
		].forEach((option) => {
			if (!hasOwnProp.call(source, option)) source[option] = source.content[option];
		});
		if (source.separator === void 0) source.separator = this.separator;
		if (source.filename) {
			if (!hasOwnProp.call(this.uniqueSourceIndexByFilename, source.filename)) {
				this.uniqueSourceIndexByFilename[source.filename] = this.uniqueSources.length;
				this.uniqueSources.push({
					filename: source.filename,
					content: source.content.original
				});
			} else {
				const uniqueSource = this.uniqueSources[this.uniqueSourceIndexByFilename[source.filename]];
				if (source.content.original !== uniqueSource.content) throw new MagicStringError(`duplicate filename "${source.filename}" with different content, use unique filenames`);
			}
		}
		this.sources.push(source);
		return this;
	}
	append(str, options) {
		this.addSource({
			content: new MagicString(str),
			separator: options && options.separator || ""
		});
		return this;
	}
	clone() {
		const bundle = new Bundle({
			intro: this.intro,
			separator: this.separator
		});
		this.sources.forEach((source) => {
			bundle.addSource({
				filename: source.filename,
				content: source.content.clone(),
				ignoreList: source.ignoreList,
				indentExclusionRanges: source.indentExclusionRanges,
				separator: source.separator
			});
		});
		return bundle;
	}
	/**
	* Flattens the bundle into a single `MagicString`, so the concatenated result can be
	* processed further with the full `MagicString` API. The returned string's `original`
	* is the concatenation of every source's `original`, and all existing edits, inserts,
	* intros, outros and separators are preserved as inserted content, so its `toString()`
	* equals the bundle's `toString()` and its sourcemap maps back to that combined original.
	*
	* Because a `MagicString` maps to a single source, per-source `filename`s are not carried
	* over; generate the bundle's map before flattening if you need the multi-source mapping.
	*/
	toMagicString() {
		const combined = new MagicString(this.sources.map((source) => source.content.original).join(""));
		let offset = 0;
		let pending = this.intro;
		let first = null;
		let last = null;
		const byStart = /* @__PURE__ */ new Map();
		const byEnd = /* @__PURE__ */ new Map();
		let hasMovedChunks = false;
		this.sources.forEach((source, i) => {
			const magicString = source.content;
			const separator = i > 0 ? source.separator !== void 0 ? source.separator : this.separator : "";
			if (magicString.original.length === 0) {
				pending += separator + magicString.toString();
				return;
			}
			pending += separator + magicString.intro;
			let sourceFirst = null;
			let previous = null;
			let originalChunk = magicString.firstChunk;
			while (originalChunk) {
				const chunk = originalChunk.clone();
				chunk.start += offset;
				chunk.end += offset;
				chunk.previous = previous;
				chunk.next = null;
				if (previous) previous.next = chunk;
				byStart.set(chunk.start, chunk);
				byEnd.set(chunk.end, chunk);
				sourceFirst ??= chunk;
				previous = chunk;
				originalChunk = originalChunk.next;
			}
			const sourceLast = previous;
			sourceFirst.intro = pending + sourceFirst.intro;
			pending = "";
			sourceLast.outro += magicString.outro;
			if (last) {
				last.next = sourceFirst;
				sourceFirst.previous = last;
			} else first = sourceFirst;
			last = sourceLast;
			for (let index = 0; index < magicString.original.length; index += 1) if (magicString.sourcemapLocations.has(index)) combined.sourcemapLocations.add(index + offset);
			Object.keys(magicString.storedNames).forEach((name) => {
				Object.defineProperty(combined.storedNames, name, {
					writable: true,
					value: true,
					enumerable: true
				});
			});
			if (magicString.hasMovedChunks) hasMovedChunks = true;
			offset += magicString.original.length;
		});
		if (!first) {
			combined.intro = pending;
			return combined;
		}
		if (pending) last.outro += pending;
		combined.firstChunk = first;
		combined.lastChunk = last;
		combined.lastSearchedChunk = first;
		combined.byStart = byStart;
		combined.byEnd = byEnd;
		combined.hasMovedChunks = hasMovedChunks;
		return combined;
	}
	generateDecodedMap(options = {}) {
		const mappings = new Mappings(options.hires);
		const { names, x_google_ignoreList } = this._generateMappings(mappings);
		return {
			...this._mapProperties(options, names, x_google_ignoreList),
			mappings: mappings.raw,
			rangeMappings: mappings.rawRangeMappings
		};
	}
	generateMap(options = {}) {
		const encoder = new MappingsEncoder();
		const mappings = new Mappings(options.hires, encoder);
		const { names, x_google_ignoreList } = this._generateMappings(mappings);
		return new SourceMap({
			...this._mapProperties(options, names, x_google_ignoreList),
			mappings: encoder.finish(mappings.rawSegments),
			rangeMappings: mappings.rawRangeMappings
		});
	}
	/** @internal */
	_generateMappings(mappings) {
		const names = [];
		let x_google_ignoreList;
		this.sources.forEach((source) => {
			Object.keys(source.content.storedNames).forEach((name) => {
				if (!names.includes(name)) names.push(name);
			});
		});
		if (this.intro) mappings.advance(this.intro);
		this.sources.forEach((source, i) => {
			if (i > 0)
 /* v8 ignore next -- addSource always normalizes source.separator */
			mappings.advance(source.separator !== void 0 ? source.separator : this.separator);
			const sourceIndex = source.filename ? this.uniqueSourceIndexByFilename[source.filename] : -1;
			const magicString = source.content;
			const locate = getLocator(magicString.original);
			if (magicString.intro) mappings.advance(magicString.intro);
			magicString.firstChunk.eachNext((chunk) => {
				const loc = locate(chunk.start);
				if (chunk.intro.length) mappings.advance(chunk.intro);
				if (source.filename) {
					if (chunk.edited) mappings.addEdit(sourceIndex, chunk.content, loc, chunk.storeName ? names.indexOf(chunk.original) : -1);
					else mappings.addUneditedChunk(sourceIndex, chunk, magicString.original, loc, magicString.sourcemapLocations);
				} else mappings.advance(chunk.content);
				if (chunk.outro.length) mappings.advance(chunk.outro);
			});
			if (magicString.outro) mappings.advance(magicString.outro);
			if (source.ignoreList && sourceIndex !== -1) {
				if (x_google_ignoreList === void 0) x_google_ignoreList = [];
				x_google_ignoreList.push(sourceIndex);
			}
		});
		return {
			names,
			x_google_ignoreList
		};
	}
	/** @internal */
	_mapProperties(options, names, x_google_ignoreList) {
		return {
			file: options.file ? options.file.split(/[/\\]/).pop() : void 0,
			sources: this.uniqueSources.map((source) => {
				return options.file ? getRelativePath(options.file, source.filename) : source.filename;
			}),
			sourcesContent: this.uniqueSources.map((source) => {
				return (typeof options.includeContent === "function" ? options.includeContent(source) : options.includeContent) ? source.content : null;
			}),
			names,
			x_google_ignoreList
		};
	}
	getIndentString() {
		const indentStringCounts = {};
		this.sources.forEach((source) => {
			const indentStr = source.content._getRawIndentString();
			if (indentStr === null) return;
			if (!indentStringCounts[indentStr]) indentStringCounts[indentStr] = 0;
			indentStringCounts[indentStr] += 1;
		});
		return Object.keys(indentStringCounts).sort((a, b) => {
			return indentStringCounts[b] - indentStringCounts[a];
		})[0] || "	";
	}
	indent(indentStr) {
		if (!arguments.length) indentStr = this.getIndentString();
		if (indentStr === "") return this;
		let trailingNewline = !this.intro || this.intro.slice(-1) === "\n";
		this.sources.forEach((source, i) => {
			/* v8 ignore next -- addSource always normalizes source.separator */
			const separator = source.separator !== void 0 ? source.separator : this.separator;
			const indentStart = trailingNewline || i > 0 && /\r?\n$/.test(separator);
			source.content.indent(indentStr, {
				exclude: source.indentExclusionRanges,
				indentStart
			});
			trailingNewline = source.content.lastChar() === "\n";
		});
		if (this.intro) this.intro = indentStr + this.intro.replace(/^[^\n]/gm, (match, index) => {
			return index > 0 ? indentStr + match : match;
		});
		return this;
	}
	prepend(str) {
		this.intro = str + this.intro;
		return this;
	}
	toString() {
		const body = this.sources.map((source, i) => {
			/* v8 ignore next -- addSource always normalizes source.separator */
			const separator = source.separator !== void 0 ? source.separator : this.separator;
			return (i > 0 ? separator : "") + source.content.toString();
		}).join("");
		return this.intro + body;
	}
	isEmpty() {
		if (this.intro.length && this.intro.trim()) return false;
		if (this.sources.some((source, i) => {
			/* v8 ignore next -- addSource always normalizes source.separator */
			const separator = source.separator !== void 0 ? source.separator : this.separator;
			return i > 0 && separator.trim() !== "" || !source.content.isEmpty();
		})) return false;
		return true;
	}
	length() {
		return this.sources.reduce((length, source, i) => {
			/* v8 ignore next -- addSource always normalizes source.separator */
			const separator = source.separator !== void 0 ? source.separator : this.separator;
			return length + (i > 0 ? separator.length : 0) + source.content.toString().length;
		}, this.intro.length);
	}
	trimLines() {
		return this.trim("[\\r\\n]");
	}
	trim(charType) {
		return this.trimStart(charType).trimEnd(charType);
	}
	trimStart(charType) {
		const rx = new RegExp(`^${charType || "\\s"}+`);
		this.intro = this.intro.replace(rx, "");
		if (!this.intro) for (let i = 0; i < this.sources.length; i += 1) {
			const source = this.sources[i];
			if (i > 0) {
				source.separator = (source.separator !== void 0 ? source.separator : this.separator).replace(rx, "");
				if (source.separator) break;
			}
			if (source.content.trimStartAborted(charType)) break;
		}
		return this;
	}
	trimEnd(charType) {
		const rx = new RegExp(`${charType || "\\s"}+$`);
		for (let i = this.sources.length - 1; i >= 0; i -= 1) {
			const source = this.sources[i];
			if (source.content.trimEndAborted(charType)) return this;
			if (i > 0) {
				source.separator = (source.separator !== void 0 ? source.separator : this.separator).replace(rx, "");
				if (source.separator) return this;
			}
		}
		this.intro = this.intro.replace(rx, "");
		return this;
	}
};
//#endregion
export { Bundle, MagicString, MagicString as default, MagicStringError, SourceMap };