UNPKG

@coderline/alphatab

Version:

alphaTab is a music notation and guitar tablature rendering library

1,748 lines (1,478 loc) 86.8 kB
/*! * alphaTab v1.6.3 (, build 22) * * Copyright © 2025, Daniel Kuschny and Contributors, All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Integrated Libraries: * * Library: TinySoundFont * License: MIT * Copyright: Copyright (C) 2017, 2018 Bernhard Schelling * URL: https://github.com/schellingb/TinySoundFont * Purpose: SoundFont loading and Audio Synthesis * * Library: SFZero * License: MIT * Copyright: Copyright (C) 2012 Steve Folta () * URL: https://github.com/stevefolta/SFZero * Purpose: TinySoundFont is based on SFZEro * * Library: Haxe Standard Library * License: MIT * Copyright: Copyright (C)2005-2025 Haxe Foundation * URL: https://github.com/HaxeFoundation/haxe/tree/development/std * Purpose: XML Parser & Zip Inflate Algorithm * * Library: SharpZipLib * License: MIT * Copyright: Copyright © 2000-2018 SharpZipLib Contributors * URL: https://github.com/icsharpcode/SharpZipLib * Purpose: Zip Deflate Algorithm for writing compressed Zips * * Library: NVorbis * License: MIT * Copyright: Copyright (c) 2020 Andrew Ward * URL: https://github.com/NVorbis/NVorbis * Purpose: Vorbis Stream Decoding * * Library: libvorbis * License: BSD-3-Clause * Copyright: Copyright (c) 2002-2020 Xiph.org Foundation * URL: https://github.com/xiph/vorbis * Purpose: NVorbis adopted some code from libvorbis. * * @preserve * @license */ import * as path from 'node:path'; import path__default from 'node:path'; import * as fs from 'node:fs'; import { createHash } from 'node:crypto'; import { normalizePath, BuildEnvironment } from 'vite'; import * as url from 'node:url'; var sourcemapCodec_umd$1 = {exports: {}}; var sourcemapCodec_umd = sourcemapCodec_umd$1.exports; var hasRequiredSourcemapCodec_umd; function requireSourcemapCodec_umd () { if (hasRequiredSourcemapCodec_umd) return sourcemapCodec_umd$1.exports; hasRequiredSourcemapCodec_umd = 1; (function (module, exports) { (function (global, factory) { factory(exports) ; })(sourcemapCodec_umd, (function (exports) { const comma = ','.charCodeAt(0); const semicolon = ';'.charCodeAt(0); const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; const intToChar = new Uint8Array(64); // 64 possible chars. const charToInt = new Uint8Array(128); // z is 122 in ASCII for (let i = 0; i < chars.length; i++) { const c = chars.charCodeAt(i); intToChar[i] = c; charToInt[c] = i; } function decodeInteger(reader, relative) { let value = 0; let shift = 0; let integer = 0; do { const c = reader.next(); integer = charToInt[c]; value |= (integer & 31) << shift; shift += 5; } while (integer & 32); const shouldNegate = value & 1; value >>>= 1; if (shouldNegate) { value = -2147483648 | -value; } return relative + value; } function encodeInteger(builder, num, relative) { let delta = num - relative; delta = delta < 0 ? (-delta << 1) | 1 : delta << 1; do { let clamped = delta & 0b011111; delta >>>= 5; if (delta > 0) clamped |= 0b100000; builder.write(intToChar[clamped]); } while (delta > 0); return num; } function hasMoreVlq(reader, max) { if (reader.pos >= max) return false; return reader.peek() !== comma; } const bufLength = 1024 * 16; // Provide a fallback for older environments. const td = typeof TextDecoder !== 'undefined' ? /* #__PURE__ */ new TextDecoder() : typeof Buffer !== 'undefined' ? { decode(buf) { const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); return out.toString(); }, } : { decode(buf) { let out = ''; for (let i = 0; i < buf.length; i++) { out += String.fromCharCode(buf[i]); } return out; }, }; class StringWriter { constructor() { this.pos = 0; this.out = ''; this.buffer = new Uint8Array(bufLength); } write(v) { const { buffer } = this; buffer[this.pos++] = v; if (this.pos === bufLength) { this.out += td.decode(buffer); this.pos = 0; } } flush() { const { buffer, out, pos } = this; return pos > 0 ? out + td.decode(buffer.subarray(0, pos)) : out; } } class StringReader { constructor(buffer) { this.pos = 0; this.buffer = buffer; } next() { return this.buffer.charCodeAt(this.pos++); } peek() { return this.buffer.charCodeAt(this.pos); } indexOf(char) { const { buffer, pos } = this; const idx = buffer.indexOf(char, pos); return idx === -1 ? buffer.length : idx; } } const EMPTY = []; function decodeOriginalScopes(input) { const { length } = input; const reader = new StringReader(input); const scopes = []; const stack = []; let line = 0; for (; reader.pos < length; reader.pos++) { line = decodeInteger(reader, line); const column = decodeInteger(reader, 0); if (!hasMoreVlq(reader, length)) { const last = stack.pop(); last[2] = line; last[3] = column; continue; } const kind = decodeInteger(reader, 0); const fields = decodeInteger(reader, 0); const hasName = fields & 0b0001; const scope = (hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind]); let vars = EMPTY; if (hasMoreVlq(reader, length)) { vars = []; do { const varsIndex = decodeInteger(reader, 0); vars.push(varsIndex); } while (hasMoreVlq(reader, length)); } scope.vars = vars; scopes.push(scope); stack.push(scope); } return scopes; } function encodeOriginalScopes(scopes) { const writer = new StringWriter(); for (let i = 0; i < scopes.length;) { i = _encodeOriginalScopes(scopes, i, writer, [0]); } return writer.flush(); } function _encodeOriginalScopes(scopes, index, writer, state) { const scope = scopes[index]; const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, 4: kind, vars } = scope; if (index > 0) writer.write(comma); state[0] = encodeInteger(writer, startLine, state[0]); encodeInteger(writer, startColumn, 0); encodeInteger(writer, kind, 0); const fields = scope.length === 6 ? 0b0001 : 0; encodeInteger(writer, fields, 0); if (scope.length === 6) encodeInteger(writer, scope[5], 0); for (const v of vars) { encodeInteger(writer, v, 0); } for (index++; index < scopes.length;) { const next = scopes[index]; const { 0: l, 1: c } = next; if (l > endLine || (l === endLine && c >= endColumn)) { break; } index = _encodeOriginalScopes(scopes, index, writer, state); } writer.write(comma); state[0] = encodeInteger(writer, endLine, state[0]); encodeInteger(writer, endColumn, 0); return index; } function decodeGeneratedRanges(input) { const { length } = input; const reader = new StringReader(input); const ranges = []; const stack = []; let genLine = 0; let definitionSourcesIndex = 0; let definitionScopeIndex = 0; let callsiteSourcesIndex = 0; let callsiteLine = 0; let callsiteColumn = 0; let bindingLine = 0; let bindingColumn = 0; do { const semi = reader.indexOf(';'); let genColumn = 0; for (; reader.pos < semi; reader.pos++) { genColumn = decodeInteger(reader, genColumn); if (!hasMoreVlq(reader, semi)) { const last = stack.pop(); last[2] = genLine; last[3] = genColumn; continue; } const fields = decodeInteger(reader, 0); const hasDefinition = fields & 0b0001; const hasCallsite = fields & 0b0010; const hasScope = fields & 0b0100; let callsite = null; let bindings = EMPTY; let range; if (hasDefinition) { const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex); definitionScopeIndex = decodeInteger(reader, definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0); definitionSourcesIndex = defSourcesIndex; range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex]; } else { range = [genLine, genColumn, 0, 0]; } range.isScope = !!hasScope; if (hasCallsite) { const prevCsi = callsiteSourcesIndex; const prevLine = callsiteLine; callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex); const sameSource = prevCsi === callsiteSourcesIndex; callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0); callsiteColumn = decodeInteger(reader, sameSource && prevLine === callsiteLine ? callsiteColumn : 0); callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn]; } range.callsite = callsite; if (hasMoreVlq(reader, semi)) { bindings = []; do { bindingLine = genLine; bindingColumn = genColumn; const expressionsCount = decodeInteger(reader, 0); let expressionRanges; if (expressionsCount < -1) { expressionRanges = [[decodeInteger(reader, 0)]]; for (let i = -1; i > expressionsCount; i--) { const prevBl = bindingLine; bindingLine = decodeInteger(reader, bindingLine); bindingColumn = decodeInteger(reader, bindingLine === prevBl ? bindingColumn : 0); const expression = decodeInteger(reader, 0); expressionRanges.push([expression, bindingLine, bindingColumn]); } } else { expressionRanges = [[expressionsCount]]; } bindings.push(expressionRanges); } while (hasMoreVlq(reader, semi)); } range.bindings = bindings; ranges.push(range); stack.push(range); } genLine++; reader.pos = semi + 1; } while (reader.pos < length); return ranges; } function encodeGeneratedRanges(ranges) { if (ranges.length === 0) return ''; const writer = new StringWriter(); for (let i = 0; i < ranges.length;) { i = _encodeGeneratedRanges(ranges, i, writer, [0, 0, 0, 0, 0, 0, 0]); } return writer.flush(); } function _encodeGeneratedRanges(ranges, index, writer, state) { const range = ranges[index]; const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, isScope, callsite, bindings, } = range; if (state[0] < startLine) { catchupLine(writer, state[0], startLine); state[0] = startLine; state[1] = 0; } else if (index > 0) { writer.write(comma); } state[1] = encodeInteger(writer, range[1], state[1]); const fields = (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0); encodeInteger(writer, fields, 0); if (range.length === 6) { const { 4: sourcesIndex, 5: scopesIndex } = range; if (sourcesIndex !== state[2]) { state[3] = 0; } state[2] = encodeInteger(writer, sourcesIndex, state[2]); state[3] = encodeInteger(writer, scopesIndex, state[3]); } if (callsite) { const { 0: sourcesIndex, 1: callLine, 2: callColumn } = range.callsite; if (sourcesIndex !== state[4]) { state[5] = 0; state[6] = 0; } else if (callLine !== state[5]) { state[6] = 0; } state[4] = encodeInteger(writer, sourcesIndex, state[4]); state[5] = encodeInteger(writer, callLine, state[5]); state[6] = encodeInteger(writer, callColumn, state[6]); } if (bindings) { for (const binding of bindings) { if (binding.length > 1) encodeInteger(writer, -binding.length, 0); const expression = binding[0][0]; encodeInteger(writer, expression, 0); let bindingStartLine = startLine; let bindingStartColumn = startColumn; for (let i = 1; i < binding.length; i++) { const expRange = binding[i]; bindingStartLine = encodeInteger(writer, expRange[1], bindingStartLine); bindingStartColumn = encodeInteger(writer, expRange[2], bindingStartColumn); encodeInteger(writer, expRange[0], 0); } } } for (index++; index < ranges.length;) { const next = ranges[index]; const { 0: l, 1: c } = next; if (l > endLine || (l === endLine && c >= endColumn)) { break; } index = _encodeGeneratedRanges(ranges, index, writer, state); } if (state[0] < endLine) { catchupLine(writer, state[0], endLine); state[0] = endLine; state[1] = 0; } else { writer.write(comma); } state[1] = encodeInteger(writer, endColumn, state[1]); return index; } function catchupLine(writer, lastLine, line) { do { writer.write(semicolon); } while (++lastLine < line); } function decode(mappings) { const { length } = mappings; const reader = new StringReader(mappings); const decoded = []; let genColumn = 0; let sourcesIndex = 0; let sourceLine = 0; let sourceColumn = 0; let namesIndex = 0; do { const semi = reader.indexOf(';'); const line = []; let sorted = true; let lastCol = 0; genColumn = 0; while (reader.pos < semi) { let seg; genColumn = decodeInteger(reader, genColumn); if (genColumn < lastCol) sorted = false; lastCol = genColumn; if (hasMoreVlq(reader, semi)) { sourcesIndex = decodeInteger(reader, sourcesIndex); sourceLine = decodeInteger(reader, sourceLine); sourceColumn = decodeInteger(reader, sourceColumn); if (hasMoreVlq(reader, semi)) { namesIndex = decodeInteger(reader, namesIndex); seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]; } else { seg = [genColumn, sourcesIndex, sourceLine, sourceColumn]; } } else { seg = [genColumn]; } line.push(seg); reader.pos++; } if (!sorted) sort(line); decoded.push(line); reader.pos = semi + 1; } while (reader.pos <= length); return decoded; } function sort(line) { line.sort(sortComparator); } function sortComparator(a, b) { return a[0] - b[0]; } function encode(decoded) { const writer = new StringWriter(); let sourcesIndex = 0; let sourceLine = 0; let sourceColumn = 0; let namesIndex = 0; for (let i = 0; i < decoded.length; i++) { const line = decoded[i]; if (i > 0) writer.write(semicolon); if (line.length === 0) continue; let genColumn = 0; for (let j = 0; j < line.length; j++) { const segment = line[j]; if (j > 0) writer.write(comma); genColumn = encodeInteger(writer, segment[0], genColumn); if (segment.length === 1) continue; sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex); sourceLine = encodeInteger(writer, segment[2], sourceLine); sourceColumn = encodeInteger(writer, segment[3], sourceColumn); if (segment.length === 4) continue; namesIndex = encodeInteger(writer, segment[4], namesIndex); } } return writer.flush(); } exports.decode = decode; exports.decodeGeneratedRanges = decodeGeneratedRanges; exports.decodeOriginalScopes = decodeOriginalScopes; exports.encode = encode; exports.encodeGeneratedRanges = encodeGeneratedRanges; exports.encodeOriginalScopes = encodeOriginalScopes; Object.defineProperty(exports, '__esModule', { value: true }); })); } (sourcemapCodec_umd$1, sourcemapCodec_umd$1.exports)); return sourcemapCodec_umd$1.exports; } var sourcemapCodec_umdExports = requireSourcemapCodec_umd(); 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))); } } 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) { let chunk = this; while (chunk) { fn(chunk); chunk = chunk.next; } } eachPrevious(fn) { let chunk = this; 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) { // after split we should save the edit content record into the correct chunk // to make sure sourcemap correct // For example: // ' test'.trim() // split -> ' ' + 'test' // ✔️ edit -> '' + 'test' // ✖️ edit -> 'test' + '' // TODO is this block necessary?... newChunk.edit('', false); 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) { this.split(this.start + trimmed.length).edit('', undefined, true); if (this.edited) { // save the change, if it has been edited this.edit(trimmed, this.storeName, true); } } return true; } else { this.edit('', undefined, 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) { const newChunk = this.split(this.end - trimmed.length); if (this.edited) { // save the change, if it has been edited newChunk.edit(trimmed, this.storeName, true); } this.edit('', undefined, true); } return true; } else { this.edit('', undefined, true); this.outro = this.outro.replace(rx, ''); if (this.outro.length) return true; } } } function getBtoa() { if (typeof globalThis !== 'undefined' && typeof globalThis.btoa === 'function') { return (str) => globalThis.btoa(unescape(encodeURIComponent(str))); } else if (typeof Buffer === 'function') { return (str) => Buffer.from(str, 'utf-8').toString('base64'); } else { return () => { throw new Error('Unsupported environment: `window.btoa` or `Buffer` should be supported.'); }; } } const btoa = /*#__PURE__*/ getBtoa(); class SourceMap { constructor(properties) { this.version = 3; this.file = properties.file; this.sources = properties.sources; this.sourcesContent = properties.sourcesContent; this.names = properties.names; this.mappings = sourcemapCodec_umdExports.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; } } toString() { return JSON.stringify(this); } toUrl() { return 'data:application/json;charset=utf-8;base64,' + btoa(this.toString()); } } 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; } // More lines tabbed than spaced? Assume tabs, and // default to tabs in the case of a tie (or nothing // to go on) if (tabbed.length >= spaced.length) { return '\t'; } // Otherwise, we need to guess the multiple const min = spaced.reduce((previous, current) => { const numSpaces = /^ +/.exec(current)[0].length; return Math.min(numSpaces, previous); }, Infinity); return new Array(min + 1).join(' '); } function getRelativePath(from, to) { const fromParts = from.split(/[/\\]/); const toParts = to.split(/[/\\]/); fromParts.pop(); // get dirname 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('/'); } const toString = Object.prototype.toString; function isObject(thing) { return toString.call(thing) === '[object Object]'; } function getLocator(source) { const originalLines = source.split('\n'); const lineOffsets = []; for (let i = 0, pos = 0; i < originalLines.length; i++) { lineOffsets.push(pos); pos += originalLines[i].length + 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; const column = index - lineOffsets[line]; return { line, column }; }; } const wordRegex = /\w/; class Mappings { constructor(hires) { this.hires = hires; this.generatedCodeLine = 0; this.generatedCodeColumn = 0; this.raw = []; this.rawSegments = this.raw[this.generatedCodeLine] = []; this.pending = null; } addEdit(sourceIndex, content, loc, nameIndex) { if (content.length) { const contentLengthMinusOne = content.length - 1; let contentLineEnd = content.indexOf('\n', 0); let previousContentLineEnd = -1; // Loop through each line in the content and add a segment, but stop if the last line is empty, // else code afterwards would fill one line too many 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.generatedCodeLine += 1; this.raw[this.generatedCodeLine] = this.rawSegments = []; this.generatedCodeColumn = 0; 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)); } else if (this.pending) { this.rawSegments.push(this.pending); this.advance(content); } this.pending = null; } addUneditedChunk(sourceIndex, chunk, original, loc, sourcemapLocations) { let originalCharIndex = chunk.start; let first = true; // when iterating each char, check if it's in a word boundary let charInHiresBoundary = false; while (originalCharIndex < chunk.end) { if (original[originalCharIndex] === '\n') { loc.line += 1; loc.column = 0; this.generatedCodeLine += 1; this.raw[this.generatedCodeLine] = this.rawSegments = []; this.generatedCodeColumn = 0; first = true; charInHiresBoundary = false; } else { if (this.hires || first || sourcemapLocations.has(originalCharIndex)) { const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column]; if (this.hires === 'boundary') { // in hires "boundary", group segments per word boundary than per char if (wordRegex.test(original[originalCharIndex])) { // for first char in the boundary found, start the boundary by pushing a segment if (!charInHiresBoundary) { this.rawSegments.push(segment); charInHiresBoundary = true; } } else { // for non-word char, end the boundary by pushing a segment this.rawSegments.push(segment); charInHiresBoundary = false; } } else { this.rawSegments.push(segment); } } loc.column += 1; this.generatedCodeColumn += 1; first = false; } originalCharIndex += 1; } this.pending = null; } advance(str) { if (!str) return; const lines = str.split('\n'); if (lines.length > 1) { for (let i = 0; i < lines.length - 1; i++) { this.generatedCodeLine++; this.raw[this.generatedCodeLine] = this.rawSegments = []; } this.generatedCodeColumn = 0; } this.generatedCodeColumn += lines[lines.length - 1].length; } } const n = '\n'; const warned = { insertLeft: false, insertRight: false, storeName: false, }; 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: {} }, byEnd: { writable: true, value: {} }, 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: undefined }, ignoreList: { writable: true, value: options.ignoreList }, offset: { writable: true, value: options.offset || 0 }, }); this.byStart[0] = chunk; this.byEnd[string.length] = chunk; } addSourcemapLocation(char) { this.sourcemapLocations.add(char); } append(content) { if (typeof content !== 'string') throw new TypeError('outro content must be a string'); this.outro += content; return this; } appendLeft(index, content) { index = index + this.offset; if (typeof content !== 'string') throw new TypeError('inserted content must be a string'); this._split(index); const chunk = this.byEnd[index]; if (chunk) { chunk.appendLeft(content); } else { this.intro += content; } return this; } appendRight(index, content) { index = index + this.offset; if (typeof content !== 'string') throw new TypeError('inserted content must be a string'); this._split(index); const chunk = this.byStart[index]; if (chunk) { chunk.appendRight(content); } else { this.outro += content; } return this; } clone() { const cloned = new MagicString(this.original, { filename: this.filename, offset: this.offset }); let originalChunk = this.firstChunk; let clonedChunk = (cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone()); while (originalChunk) { cloned.byStart[clonedChunk.start] = clonedChunk; cloned.byEnd[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.intro = this.intro; cloned.outro = this.outro; return cloned; } generateDecodedMap(options) { options = options || {}; const sourceIndex = 0; const names = Object.keys(this.storedNames); const mappings = new Mappings(options.hires); 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); }); return { file: options.file ? options.file.split(/[/\\]/).pop() : undefined, sources: [ options.source ? getRelativePath(options.file || '', options.source) : options.file || '', ], sourcesContent: options.includeContent ? [this.original] : undefined, names, mappings: mappings.raw, x_google_ignoreList: this.ignoreList ? [sourceIndex] : undefined, }; } generateMap(options) { return new SourceMap(this.generateDecodedMap(options)); } _ensureindentStr() { if (this.indentStr === undefined) { this.indentStr = guessIndent(this.original); } } _getRawIndentString() { this._ensureindentStr(); return this.indentStr; } getIndentString() { this._ensureindentStr(); return this.indentStr === null ? '\t' : this.indentStr; } indent(indentStr, options) { const pattern = /^[^\r\n]/gm; if (isObject(indentStr)) { options = indentStr; indentStr = undefined; } if (indentStr === undefined) { this._ensureindentStr(); indentStr = this.indentStr || '\t'; } if (indentStr === '') return this; // noop options = options || {}; // Process exclusion ranges const isExcluded = {}; if (options.exclude) { const exclusions = typeof options.exclude[0] === 'number' ? [options.exclude] : options.exclude; exclusions.forEach((exclusion) => { for (let i = exclusion[0]; i < exclusion[1]; i += 1) { isExcluded[i] = true; } }); } let shouldIndentNextCharacter = options.indentStart !== false; const replacer = (match) => { if (shouldIndentNextCharacter) return `${indentStr}${match}`; shouldIndentNextCharacter = true; return match; }; this.intro = this.intro.replace(pattern, replacer); let charIndex = 0; let chunk = this.firstChunk; while (chunk) { const end = chunk.end; if (chunk.edited) { if (!isExcluded[charIndex]) { chunk.content = chunk.content.replace(pattern, replacer); if (chunk.content.length) { shouldIndentNextCharacter = chunk.content[chunk.content.length - 1] === '\n'; } } } else { charIndex = chunk.start; while (charIndex < end) { if (!isExcluded[charIndex]) { const char = this.original[charIndex]; if (char === '\n') { shouldIndentNextCharacter = true; } else if (char !== '\r' && shouldIndentNextCharacter) { shouldIndentNextCharacter = false; if (charIndex === chunk.start) { chunk.prependRight(indentStr); } else { this._splitChunk(chunk, charIndex); chunk = chunk.next; chunk.prependRight(indentStr); } } } charIndex += 1; } } charIndex = chunk.end; chunk = chunk.next; } this.outro = this.outro.replace(pattern, replacer); return this; } insert() { throw new Error( 'magicString.insert(...) is deprecated. Use prependRight(...) or appendLeft(...)', ); } insertLeft(index, content) { if (!warned.insertLeft) { console.warn( 'magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead', ); warned.insertLeft = true; } return this.appendLeft(index, content); } insertRight(index, content) { if (!warned.insertRight) { console.warn( 'magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead', ); warned.insertRight = true; } return this.prependRight(index, content); } move(start, end, index) { start = start + this.offset; end = end + this.offset; index = index + this.offset; if (index >= start && index <= end) throw new Error('Cannot move a selection inside itself'); this._split(start); this._split(end); this._split(index); const first = this.byStart[start]; const last = this.byEnd[end]; const oldLeft = first.previous; const oldRight = last.next; const newRight = this.byStart[index]; if (!newRight && last === this.lastChunk) return this; const newLeft = newRight ? newRight.previous : this.lastChunk; 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; return this; } overwrite(start, end, content, options) { options = options || {}; return this.update(start, end, content, { ...options, overwrite: !options.contentOnly }); } update(start, end, content, options) { start = start + this.offset; end = end + this.offset; if (typeof content !== 'string') throw new TypeError('replacement content must be a string'); if (this.original.length !== 0) { while (start < 0) start += this.original.length; while (end < 0) end += this.original.length; } if (end > this.original.length) throw new Error('end is out of bounds'); if (start === end) throw new Error( 'Cannot overwrite a zero-length range – use appendLeft or prependRight instead', ); 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 storeName = options !== undefined ? options.storeName : false; const overwrite = options !== undefined ? options.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[start]; const last = this.byEnd[end]; if (first) { let chunk = first; while (chunk !== last) { if (chunk.next !== this.byStart[chunk.end]) { throw new Error('Cannot overwrite across a split point'); } chunk = chunk.next; chunk.edit('', false); } first.edit(content, storeName, !overwrite); } else { // must be inserting at the end const newChunk = new Chunk(start, end, '').edit(content, storeName); // TODO last chunk in the array may not be the last chunk, if it's moved... last.next = newChunk; newChunk.previous = last; } return this; } prepend(content) { if (typeof content !== 'string') throw new TypeError('outro content must be a string'); this.intro = content + this.intro; return this; } prependLeft(index, content) { index = index + this.offset; if (typeof content !== 'string') throw new TypeError('inserted content must be a string'); this._split(index); const chunk = this.byEnd[index]; if (chunk) { chunk.prependLeft(content); } else { this.intro = content + this.intro; } return this; } prependRight(index, content) { index = index + this.offset; if (typeof content !== 'string') throw new TypeError('inserted content must be a string'); this._split(index); const chunk = this.byStart[index]; if (chunk) { chunk.prependRight(content); } else { this.outro = content + this.outro; } return this; } remove(start, end) { start = start + this.offset; end = end + this.offset; if (this.original.length !== 0) { while (start < 0) start += this.original.length; while (end < 0) end += this.original.length; } if (start === end) return this; if (start < 0 || end > this.original.length) throw new Error('Character is out of bounds'); if (start > end) throw new Error('end must be greater than start'); this._split(start); this._split(end); let chunk = this.byStart[start]; while (chunk) { chunk.intro = ''; chunk.outro = ''; chunk.edit(''); chunk = end > chunk.end ? this.byStart[chunk.end] : null; } return this; } reset(start, end) { start = start + this.offset; end = end + this.offset; if (this.original.length !== 0) { while (start < 0) start += this.original.length; while (end < 0) end += this.original.length; } if (start === end) return this; if (start < 0 || end > this.original.length) throw new Error('Character is out of bounds'); if (start > end) throw new Error('end must be greater than start'); this._split(start); this._split(end); let chunk = this.byStart[start]; while (chunk) { chunk.reset(); chunk = end > chunk.end ? this.byStart[chunk.end] : null; } return this; } lastChar() { if (this.outro.length) return this.outro[this.outro.length - 1]; let chunk = this.lastChunk; do { 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]; } while ((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; do { 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; } } while ((chunk = chunk.previous)); lineIndex = this.intro.lastIndexOf(n); if (lineIndex !== -1) return this.intro.substr(lineIndex + 1) + lineStr; return this.intro + lineStr; } slice(start = 0, end = this.original.length - this.offset) { start = start + this.offset; end = end + this.offset; if (this.original.length !== 0) { while (start < 0) start += this.original.length; while (end < 0) end += this.original.length; } let result = ''; // find start chunk let chunk = this.firstChunk; while (chunk && (chunk.start > start || chunk.end <= start)) { // found end chunk before start if (chunk.start < end && chunk.end >= end) { return result; } chunk = chunk.next; } if (chunk && chunk.edited && chunk.start !== start) throw new Error(`Cannot use replaced character ${start} as slice start anchor.`); const startChunk = chunk; while (chunk) { 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 Error(`Cannot use replaced 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; } // TODO deprecate this? not really very useful snip(start, end) { const clone = this.clone(); clone.remove(0, start); clone.remove(end, clone.original.length); return clone; } _split(index) { if (this.byStart[index] || this.byEnd[index]) return; let chunk = this.lastSearchedChunk; const searchForward = index > chunk.end; while (chunk) { if (chunk.contains(index)) return this._splitChunk(chunk, index); chunk = searchForward ? this.byStart[chunk.end] : this.byEnd[chunk.start]; } } _splitChunk(chunk, index) { if (chunk.edited && chunk.content.length) { // zero-length edited chunks are a special case (overlapping replacements) const loc = getLocator(this.original)(index); throw new Error( `Cannot split a chunk that has already been edited (${loc.line}:${loc.column} – "${chunk.original}")`, ); } const newChunk = chunk.split(index); this.byEnd[index] = chunk; this.byStart[index] = newChunk; this.byEnd[newChunk.end] = newChunk; if (chunk === this.lastChunk) this.lastChunk = newChunk; this.lastSearchedChunk = chunk; return true; } toString() { let str = this.intro; let chunk = this.firstChunk; while (chunk) { str += chunk.toString(); chunk = chunk.next; } return str + this.outro; } isEmpty() { let chunk = this.firstChunk; do { if ( (chunk.intro.length && chunk.intro.trim()) || (chunk.content.length && chunk.content.trim()) || (chunk.outro.length && chunk.outro.trim()) ) return false; } while ((chunk = chunk.next)); return true; } length() { let chunk = this.firstChunk; let length = 0; do { length += chunk.intro.length + chunk.content.length + chunk.outro.length; } while ((chunk = chunk.next)); return length; } trimLines() { return this.trim('[\\r\\n]'); } trim(charType) { return this.trimStart(charType).trimEnd(charType); } 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 was trimmed, we have a new lastChunk if (chunk.end !== end) { if (this.lastChunk === chunk) { this.lastChunk = chunk.next; } this.byEnd[chunk.end] = chunk; this.byStart[chunk.next.start] = chunk.next; this.byEnd[chunk.next.end] = chunk.next; } if (aborted) return true; chunk = chunk.previous; } while (chunk); return false; } trimEnd(charType) { this.trimEndAborted(charType); return this; } 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) { // special case... if (chunk === this.lastChunk) this.lastChunk = chunk.next; this.byEnd[chunk.end] = chunk; this.byStart[chunk.next.start] = chunk.next; this.byEnd[chunk.next.end] = chunk.next; } if (aborted) return true; chunk = chunk.next; } while (chunk); return false; } trimStart(charType) { this.trimStartAborted(charType); return this; } hasChanged() { return this.original !== this.toString(); } _replaceRegexp(searchValue, replacement) { function getReplacement(match, str) { if (typeof replacement === 'string') { return replacement.replace(/\$(\$|&|\d+)/g, (_, i) => { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter if (i === '$') return '$'; if (i === '&') return match[0]; const num = +i; if (num < match.length) return match[+i]; return `$${i}`; }); } else { return replacement(...match, match.index, str, match.groups); } } function matchAll(re, str) { let match; const matches = []; while ((match = re.exec(str))) { matches.push(match); } return matches; } if (searchValue.global) { const matches = matchAll(searchValue, this.original); matches.forEach((match) => { if (match.index != null) { const replacement = getReplacement(match, this.original); if (replacement !== match[0]) { this.overwrite(match.index, match.index + match[0].length, replacement); } } }); } else { const match = this.original.match(searchValue); if (match && match.index != null) { const replacement = getReplacement(match, this.original); if (replacement !== match[0]) { this.overwrite(match.index, match.index + match[0].length, replacement); } } } return this; } _replaceString(string, replacement) { const { original } = this; const index = original.indexOf(string); if (index !== -1) { this.overwrite(index, index + string.length, replacement); } return this; } replace(searchValue, replacement) {