| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const Source = require("./Source");
|
|---|
| 9 | const { getMap, getSourceAndMap } = require("./helpers/getFromStreamChunks");
|
|---|
| 10 | const splitIntoLines = require("./helpers/splitIntoLines");
|
|---|
| 11 | const streamChunks = require("./helpers/streamChunks");
|
|---|
| 12 |
|
|---|
| 13 | /** @typedef {import("./Source").HashLike} HashLike */
|
|---|
| 14 | /** @typedef {import("./Source").MapOptions} MapOptions */
|
|---|
| 15 | /** @typedef {import("./Source").RawSourceMap} RawSourceMap */
|
|---|
| 16 | /** @typedef {import("./Source").SourceAndMap} SourceAndMap */
|
|---|
| 17 | /** @typedef {import("./Source").SourceValue} SourceValue */
|
|---|
| 18 | /** @typedef {import("./helpers/getGeneratedSourceInfo").GeneratedSourceInfo} GeneratedSourceInfo */
|
|---|
| 19 | /** @typedef {import("./helpers/streamChunks").OnChunk} OnChunk */
|
|---|
| 20 | /** @typedef {import("./helpers/streamChunks").OnName} OnName */
|
|---|
| 21 | /** @typedef {import("./helpers/streamChunks").OnSource} OnSource */
|
|---|
| 22 | /** @typedef {import("./helpers/streamChunks").Options} Options */
|
|---|
| 23 |
|
|---|
| 24 | // since v8 7.0, Array.prototype.sort is stable
|
|---|
| 25 | const hasStableSort =
|
|---|
| 26 | typeof process === "object" &&
|
|---|
| 27 | process.versions &&
|
|---|
| 28 | typeof process.versions.v8 === "string" &&
|
|---|
| 29 | !/^[0-6]\./.test(process.versions.v8);
|
|---|
| 30 |
|
|---|
| 31 | // This is larger than max string length
|
|---|
| 32 | const MAX_SOURCE_POSITION = 0x20000000;
|
|---|
| 33 |
|
|---|
| 34 | /**
|
|---|
| 35 | * Stable comparator hoisted to module scope so each `_sortReplacements()`
|
|---|
| 36 | * call doesn't allocate a fresh closure.
|
|---|
| 37 | * @param {Replacement} a a
|
|---|
| 38 | * @param {Replacement} b b
|
|---|
| 39 | * @returns {number} order
|
|---|
| 40 | */
|
|---|
| 41 | const compareStable = (a, b) => {
|
|---|
| 42 | const diff1 = a.start - b.start;
|
|---|
| 43 | if (diff1 !== 0) return diff1;
|
|---|
| 44 | const diff2 = a.end - b.end;
|
|---|
| 45 | if (diff2 !== 0) return diff2;
|
|---|
| 46 | return 0;
|
|---|
| 47 | };
|
|---|
| 48 |
|
|---|
| 49 | /**
|
|---|
| 50 | * Index-stabilising comparator for v8 < 7.0 (pre-stable Array.prototype.sort).
|
|---|
| 51 | * @param {Replacement} a a
|
|---|
| 52 | * @param {Replacement} b b
|
|---|
| 53 | * @returns {number} order
|
|---|
| 54 | */
|
|---|
| 55 | const compareUnstableFallback = (a, b) => {
|
|---|
| 56 | const diff1 = a.start - b.start;
|
|---|
| 57 | if (diff1 !== 0) return diff1;
|
|---|
| 58 | const diff2 = a.end - b.end;
|
|---|
| 59 | if (diff2 !== 0) return diff2;
|
|---|
| 60 | return /** @type {number} */ (a.index) - /** @type {number} */ (b.index);
|
|---|
| 61 | };
|
|---|
| 62 |
|
|---|
| 63 | class Replacement {
|
|---|
| 64 | /**
|
|---|
| 65 | * @param {number} start start
|
|---|
| 66 | * @param {number} end end
|
|---|
| 67 | * @param {string} content content
|
|---|
| 68 | * @param {string=} name name
|
|---|
| 69 | */
|
|---|
| 70 | constructor(start, end, content, name) {
|
|---|
| 71 | this.start = start;
|
|---|
| 72 | this.end = end;
|
|---|
| 73 | this.content = content;
|
|---|
| 74 | this.name = name;
|
|---|
| 75 | if (!hasStableSort) {
|
|---|
| 76 | this.index = -1;
|
|---|
| 77 | }
|
|---|
| 78 | }
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | class ReplaceSource extends Source {
|
|---|
| 82 | /**
|
|---|
| 83 | * @param {Source} source source
|
|---|
| 84 | * @param {string=} name name
|
|---|
| 85 | */
|
|---|
| 86 | constructor(source, name) {
|
|---|
| 87 | super();
|
|---|
| 88 | /**
|
|---|
| 89 | * @private
|
|---|
| 90 | * @type {Source}
|
|---|
| 91 | */
|
|---|
| 92 | this._source = source;
|
|---|
| 93 | /**
|
|---|
| 94 | * @private
|
|---|
| 95 | * @type {string | undefined}
|
|---|
| 96 | */
|
|---|
| 97 | this._name = name;
|
|---|
| 98 | /** @type {Replacement[]} */
|
|---|
| 99 | this._replacements = [];
|
|---|
| 100 | /**
|
|---|
| 101 | * @private
|
|---|
| 102 | * @type {boolean}
|
|---|
| 103 | */
|
|---|
| 104 | this._isSorted = true;
|
|---|
| 105 | }
|
|---|
| 106 |
|
|---|
| 107 | getName() {
|
|---|
| 108 | return this._name;
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | getReplacements() {
|
|---|
| 112 | this._sortReplacements();
|
|---|
| 113 | return this._replacements;
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | /**
|
|---|
| 117 | * @param {number} start start
|
|---|
| 118 | * @param {number} end end
|
|---|
| 119 | * @param {string} newValue new value
|
|---|
| 120 | * @param {string=} name name
|
|---|
| 121 | * @returns {void}
|
|---|
| 122 | */
|
|---|
| 123 | replace(start, end, newValue, name) {
|
|---|
| 124 | if (typeof newValue !== "string") {
|
|---|
| 125 | throw new Error(
|
|---|
| 126 | `insertion must be a string, but is a ${typeof newValue}`,
|
|---|
| 127 | );
|
|---|
| 128 | }
|
|---|
| 129 | this._replacements.push(new Replacement(start, end, newValue, name));
|
|---|
| 130 | this._isSorted = false;
|
|---|
| 131 | }
|
|---|
| 132 |
|
|---|
| 133 | /**
|
|---|
| 134 | * @param {number} pos pos
|
|---|
| 135 | * @param {string} newValue new value
|
|---|
| 136 | * @param {string=} name name
|
|---|
| 137 | * @returns {void}
|
|---|
| 138 | */
|
|---|
| 139 | insert(pos, newValue, name) {
|
|---|
| 140 | if (typeof newValue !== "string") {
|
|---|
| 141 | throw new Error(
|
|---|
| 142 | `insertion must be a string, but is a ${typeof newValue}: ${newValue}`,
|
|---|
| 143 | );
|
|---|
| 144 | }
|
|---|
| 145 | this._replacements.push(new Replacement(pos, pos - 1, newValue, name));
|
|---|
| 146 | this._isSorted = false;
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | /**
|
|---|
| 150 | * @returns {SourceValue} source
|
|---|
| 151 | */
|
|---|
| 152 | source() {
|
|---|
| 153 | if (this._replacements.length === 0) {
|
|---|
| 154 | return this._source.source();
|
|---|
| 155 | }
|
|---|
| 156 | const current = /** @type {string} */ (this._source.source());
|
|---|
| 157 | let pos = 0;
|
|---|
| 158 | const result = [];
|
|---|
| 159 |
|
|---|
| 160 | this._sortReplacements();
|
|---|
| 161 | for (const replacement of this._replacements) {
|
|---|
| 162 | const start = Math.floor(replacement.start);
|
|---|
| 163 | const end = Math.floor(replacement.end + 1);
|
|---|
| 164 | if (pos < start) {
|
|---|
| 165 | // slice directly from the original string rather than repeatedly
|
|---|
| 166 | // producing smaller intermediate strings, which avoids O(n) copies.
|
|---|
| 167 | result.push(current.slice(pos, start));
|
|---|
| 168 | pos = start;
|
|---|
| 169 | }
|
|---|
| 170 | result.push(replacement.content);
|
|---|
| 171 | if (pos < end) {
|
|---|
| 172 | pos = end;
|
|---|
| 173 | }
|
|---|
| 174 | }
|
|---|
| 175 | if (pos < current.length) {
|
|---|
| 176 | result.push(pos === 0 ? current : current.slice(pos));
|
|---|
| 177 | }
|
|---|
| 178 | return result.join("");
|
|---|
| 179 | }
|
|---|
| 180 |
|
|---|
| 181 | /**
|
|---|
| 182 | * @returns {Buffer} buffer
|
|---|
| 183 | */
|
|---|
| 184 | buffer() {
|
|---|
| 185 | if (this._replacements.length === 0) {
|
|---|
| 186 | return this._source.buffer();
|
|---|
| 187 | }
|
|---|
| 188 | return super.buffer();
|
|---|
| 189 | }
|
|---|
| 190 |
|
|---|
| 191 | /**
|
|---|
| 192 | * @returns {Buffer[]} buffers
|
|---|
| 193 | */
|
|---|
| 194 | buffers() {
|
|---|
| 195 | if (this._replacements.length === 0) {
|
|---|
| 196 | // TODO remove in the next major release
|
|---|
| 197 | return typeof this._source.buffers === "function"
|
|---|
| 198 | ? this._source.buffers()
|
|---|
| 199 | : [this._source.buffer()];
|
|---|
| 200 | }
|
|---|
| 201 | return [this.buffer()];
|
|---|
| 202 | }
|
|---|
| 203 |
|
|---|
| 204 | /**
|
|---|
| 205 | * @param {MapOptions=} options map options
|
|---|
| 206 | * @returns {RawSourceMap | null} map
|
|---|
| 207 | */
|
|---|
| 208 | map(options) {
|
|---|
| 209 | if (this._replacements.length === 0) {
|
|---|
| 210 | return this._source.map(options);
|
|---|
| 211 | }
|
|---|
| 212 | return getMap(this, options);
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | /**
|
|---|
| 216 | * @param {MapOptions=} options map options
|
|---|
| 217 | * @returns {SourceAndMap} source and map
|
|---|
| 218 | */
|
|---|
| 219 | sourceAndMap(options) {
|
|---|
| 220 | if (this._replacements.length === 0) {
|
|---|
| 221 | return this._source.sourceAndMap(options);
|
|---|
| 222 | }
|
|---|
| 223 | return getSourceAndMap(this, options);
|
|---|
| 224 | }
|
|---|
| 225 |
|
|---|
| 226 | original() {
|
|---|
| 227 | return this._source;
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | _sortReplacements() {
|
|---|
| 231 | if (this._isSorted) return;
|
|---|
| 232 | if (hasStableSort) {
|
|---|
| 233 | this._replacements.sort(compareStable);
|
|---|
| 234 | } else {
|
|---|
| 235 | for (const [i, repl] of this._replacements.entries()) repl.index = i;
|
|---|
| 236 | this._replacements.sort(compareUnstableFallback);
|
|---|
| 237 | }
|
|---|
| 238 | this._isSorted = true;
|
|---|
| 239 | }
|
|---|
| 240 |
|
|---|
| 241 | /**
|
|---|
| 242 | * @param {Options} options options
|
|---|
| 243 | * @param {OnChunk} onChunk called for each chunk of code
|
|---|
| 244 | * @param {OnSource} onSource called for each source
|
|---|
| 245 | * @param {OnName} onName called for each name
|
|---|
| 246 | * @returns {GeneratedSourceInfo} generated source info
|
|---|
| 247 | */
|
|---|
| 248 | streamChunks(options, onChunk, onSource, onName) {
|
|---|
| 249 | this._sortReplacements();
|
|---|
| 250 | const replacements = this._replacements;
|
|---|
| 251 | let pos = 0;
|
|---|
| 252 | let i = 0;
|
|---|
| 253 | let replacementEnd = -1;
|
|---|
| 254 | let nextReplacement =
|
|---|
| 255 | i < replacements.length
|
|---|
| 256 | ? Math.floor(replacements[i].start)
|
|---|
| 257 | : MAX_SOURCE_POSITION;
|
|---|
| 258 | let generatedLineOffset = 0;
|
|---|
| 259 | let generatedColumnOffset = 0;
|
|---|
| 260 | let generatedColumnOffsetLine = 0;
|
|---|
| 261 | /** @type {(string | string[] | undefined)[]} */
|
|---|
| 262 | const sourceContents = [];
|
|---|
| 263 | /** @type {Map<string, number>} */
|
|---|
| 264 | const nameMapping = new Map();
|
|---|
| 265 | /** @type {number[]} */
|
|---|
| 266 | const nameIndexMapping = [];
|
|---|
| 267 | /**
|
|---|
| 268 | * @param {number} sourceIndex source index
|
|---|
| 269 | * @param {number} line line
|
|---|
| 270 | * @param {number} column column
|
|---|
| 271 | * @param {string} expectedChunk expected chunk
|
|---|
| 272 | * @returns {boolean} result
|
|---|
| 273 | */
|
|---|
| 274 | const checkOriginalContent = (sourceIndex, line, column, expectedChunk) => {
|
|---|
| 275 | /** @type {undefined | string | string[]} */
|
|---|
| 276 | let content =
|
|---|
| 277 | sourceIndex < sourceContents.length
|
|---|
| 278 | ? sourceContents[sourceIndex]
|
|---|
| 279 | : undefined;
|
|---|
| 280 | if (content === undefined) return false;
|
|---|
| 281 | if (typeof content === "string") {
|
|---|
| 282 | content = splitIntoLines(content);
|
|---|
| 283 | sourceContents[sourceIndex] = content;
|
|---|
| 284 | }
|
|---|
| 285 | const contentLine = line <= content.length ? content[line - 1] : null;
|
|---|
| 286 | if (contentLine === null) return false;
|
|---|
| 287 | return (
|
|---|
| 288 | contentLine.slice(column, column + expectedChunk.length) ===
|
|---|
| 289 | expectedChunk
|
|---|
| 290 | );
|
|---|
| 291 | };
|
|---|
| 292 | const { generatedLine, generatedColumn } = streamChunks(
|
|---|
| 293 | this._source,
|
|---|
| 294 | { ...options, finalSource: false },
|
|---|
| 295 | (
|
|---|
| 296 | _chunk,
|
|---|
| 297 | generatedLine,
|
|---|
| 298 | generatedColumn,
|
|---|
| 299 | sourceIndex,
|
|---|
| 300 | originalLine,
|
|---|
| 301 | originalColumn,
|
|---|
| 302 | nameIndex,
|
|---|
| 303 | ) => {
|
|---|
| 304 | let chunkPos = 0;
|
|---|
| 305 | const chunk = /** @type {string} */ (_chunk);
|
|---|
| 306 | const endPos = pos + chunk.length;
|
|---|
| 307 |
|
|---|
| 308 | // Skip over when it has been replaced
|
|---|
| 309 | if (replacementEnd > pos) {
|
|---|
| 310 | // Skip over the whole chunk
|
|---|
| 311 | if (replacementEnd >= endPos) {
|
|---|
| 312 | const line = generatedLine + generatedLineOffset;
|
|---|
| 313 | if (chunk.endsWith("\n")) {
|
|---|
| 314 | generatedLineOffset--;
|
|---|
| 315 | if (generatedColumnOffsetLine === line) {
|
|---|
| 316 | // undo exiting corrections form the current line
|
|---|
| 317 | generatedColumnOffset += generatedColumn;
|
|---|
| 318 | }
|
|---|
| 319 | } else if (generatedColumnOffsetLine === line) {
|
|---|
| 320 | generatedColumnOffset -= chunk.length;
|
|---|
| 321 | } else {
|
|---|
| 322 | generatedColumnOffset = -chunk.length;
|
|---|
| 323 | generatedColumnOffsetLine = line;
|
|---|
| 324 | }
|
|---|
| 325 | pos = endPos;
|
|---|
| 326 | return;
|
|---|
| 327 | }
|
|---|
| 328 |
|
|---|
| 329 | // Partially skip over chunk
|
|---|
| 330 | chunkPos = replacementEnd - pos;
|
|---|
| 331 | if (
|
|---|
| 332 | checkOriginalContent(
|
|---|
| 333 | sourceIndex,
|
|---|
| 334 | originalLine,
|
|---|
| 335 | originalColumn,
|
|---|
| 336 | chunk.slice(0, chunkPos),
|
|---|
| 337 | )
|
|---|
| 338 | ) {
|
|---|
| 339 | originalColumn += chunkPos;
|
|---|
| 340 | }
|
|---|
| 341 | pos += chunkPos;
|
|---|
| 342 | const line = generatedLine + generatedLineOffset;
|
|---|
| 343 | if (generatedColumnOffsetLine === line) {
|
|---|
| 344 | generatedColumnOffset -= chunkPos;
|
|---|
| 345 | } else {
|
|---|
| 346 | generatedColumnOffset = -chunkPos;
|
|---|
| 347 | generatedColumnOffsetLine = line;
|
|---|
| 348 | }
|
|---|
| 349 | generatedColumn += chunkPos;
|
|---|
| 350 | }
|
|---|
| 351 |
|
|---|
| 352 | // Is a replacement in the chunk?
|
|---|
| 353 | if (nextReplacement < endPos) {
|
|---|
| 354 | do {
|
|---|
| 355 | let line = generatedLine + generatedLineOffset;
|
|---|
| 356 | if (nextReplacement > pos) {
|
|---|
| 357 | // Emit chunk until replacement
|
|---|
| 358 | const offset = nextReplacement - pos;
|
|---|
| 359 | const chunkSlice = chunk.slice(chunkPos, chunkPos + offset);
|
|---|
| 360 | onChunk(
|
|---|
| 361 | chunkSlice,
|
|---|
| 362 | line,
|
|---|
| 363 | generatedColumn +
|
|---|
| 364 | (line === generatedColumnOffsetLine
|
|---|
| 365 | ? generatedColumnOffset
|
|---|
| 366 | : 0),
|
|---|
| 367 | sourceIndex,
|
|---|
| 368 | originalLine,
|
|---|
| 369 | originalColumn,
|
|---|
| 370 | nameIndex < 0 || nameIndex >= nameIndexMapping.length
|
|---|
| 371 | ? -1
|
|---|
| 372 | : nameIndexMapping[nameIndex],
|
|---|
| 373 | );
|
|---|
| 374 | generatedColumn += offset;
|
|---|
| 375 | chunkPos += offset;
|
|---|
| 376 | pos = nextReplacement;
|
|---|
| 377 | if (
|
|---|
| 378 | checkOriginalContent(
|
|---|
| 379 | sourceIndex,
|
|---|
| 380 | originalLine,
|
|---|
| 381 | originalColumn,
|
|---|
| 382 | chunkSlice,
|
|---|
| 383 | )
|
|---|
| 384 | ) {
|
|---|
| 385 | originalColumn += chunkSlice.length;
|
|---|
| 386 | }
|
|---|
| 387 | }
|
|---|
| 388 |
|
|---|
| 389 | // Insert replacement content splitted into chunks by lines
|
|---|
| 390 | const { content, name } = replacements[i];
|
|---|
| 391 | const matches = splitIntoLines(content);
|
|---|
| 392 | let replacementNameIndex = nameIndex;
|
|---|
| 393 | if (sourceIndex >= 0 && name) {
|
|---|
| 394 | let globalIndex = nameMapping.get(name);
|
|---|
| 395 | if (globalIndex === undefined) {
|
|---|
| 396 | globalIndex = nameMapping.size;
|
|---|
| 397 | nameMapping.set(name, globalIndex);
|
|---|
| 398 | onName(globalIndex, name);
|
|---|
| 399 | }
|
|---|
| 400 | replacementNameIndex = globalIndex;
|
|---|
| 401 | }
|
|---|
| 402 | for (let m = 0; m < matches.length; m++) {
|
|---|
| 403 | const contentLine = matches[m];
|
|---|
| 404 | onChunk(
|
|---|
| 405 | contentLine,
|
|---|
| 406 | line,
|
|---|
| 407 | generatedColumn +
|
|---|
| 408 | (line === generatedColumnOffsetLine
|
|---|
| 409 | ? generatedColumnOffset
|
|---|
| 410 | : 0),
|
|---|
| 411 | sourceIndex,
|
|---|
| 412 | originalLine,
|
|---|
| 413 | originalColumn,
|
|---|
| 414 | replacementNameIndex,
|
|---|
| 415 | );
|
|---|
| 416 |
|
|---|
| 417 | // Only the first chunk has name assigned
|
|---|
| 418 | replacementNameIndex = -1;
|
|---|
| 419 |
|
|---|
| 420 | if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
|
|---|
| 421 | if (generatedColumnOffsetLine === line) {
|
|---|
| 422 | generatedColumnOffset += contentLine.length;
|
|---|
| 423 | } else {
|
|---|
| 424 | generatedColumnOffset = contentLine.length;
|
|---|
| 425 | generatedColumnOffsetLine = line;
|
|---|
| 426 | }
|
|---|
| 427 | } else {
|
|---|
| 428 | generatedLineOffset++;
|
|---|
| 429 | line++;
|
|---|
| 430 | generatedColumnOffset = -generatedColumn;
|
|---|
| 431 | generatedColumnOffsetLine = line;
|
|---|
| 432 | }
|
|---|
| 433 | }
|
|---|
| 434 |
|
|---|
| 435 | // Remove replaced content by settings this variable
|
|---|
| 436 | replacementEnd = Math.max(
|
|---|
| 437 | replacementEnd,
|
|---|
| 438 | Math.floor(replacements[i].end + 1),
|
|---|
| 439 | );
|
|---|
| 440 |
|
|---|
| 441 | // Move to next replacement
|
|---|
| 442 | i++;
|
|---|
| 443 | nextReplacement =
|
|---|
| 444 | i < replacements.length
|
|---|
| 445 | ? Math.floor(replacements[i].start)
|
|---|
| 446 | : MAX_SOURCE_POSITION;
|
|---|
| 447 |
|
|---|
| 448 | // Skip over when it has been replaced
|
|---|
| 449 | const offset = chunk.length - endPos + replacementEnd - chunkPos;
|
|---|
| 450 | if (offset > 0) {
|
|---|
| 451 | // Skip over whole chunk
|
|---|
| 452 | if (replacementEnd >= endPos) {
|
|---|
| 453 | const line = generatedLine + generatedLineOffset;
|
|---|
| 454 | if (chunk.endsWith("\n")) {
|
|---|
| 455 | generatedLineOffset--;
|
|---|
| 456 | if (generatedColumnOffsetLine === line) {
|
|---|
| 457 | // undo exiting corrections form the current line
|
|---|
| 458 | generatedColumnOffset += generatedColumn;
|
|---|
| 459 | }
|
|---|
| 460 | } else if (generatedColumnOffsetLine === line) {
|
|---|
| 461 | generatedColumnOffset -= chunk.length - chunkPos;
|
|---|
| 462 | } else {
|
|---|
| 463 | generatedColumnOffset = chunkPos - chunk.length;
|
|---|
| 464 | generatedColumnOffsetLine = line;
|
|---|
| 465 | }
|
|---|
| 466 | pos = endPos;
|
|---|
| 467 | return;
|
|---|
| 468 | }
|
|---|
| 469 |
|
|---|
| 470 | // Partially skip over chunk
|
|---|
| 471 | const line = generatedLine + generatedLineOffset;
|
|---|
| 472 | if (
|
|---|
| 473 | checkOriginalContent(
|
|---|
| 474 | sourceIndex,
|
|---|
| 475 | originalLine,
|
|---|
| 476 | originalColumn,
|
|---|
| 477 | chunk.slice(chunkPos, chunkPos + offset),
|
|---|
| 478 | )
|
|---|
| 479 | ) {
|
|---|
| 480 | originalColumn += offset;
|
|---|
| 481 | }
|
|---|
| 482 | chunkPos += offset;
|
|---|
| 483 | pos += offset;
|
|---|
| 484 | if (generatedColumnOffsetLine === line) {
|
|---|
| 485 | generatedColumnOffset -= offset;
|
|---|
| 486 | } else {
|
|---|
| 487 | generatedColumnOffset = -offset;
|
|---|
| 488 | generatedColumnOffsetLine = line;
|
|---|
| 489 | }
|
|---|
| 490 | generatedColumn += offset;
|
|---|
| 491 | }
|
|---|
| 492 | } while (nextReplacement < endPos);
|
|---|
| 493 | }
|
|---|
| 494 |
|
|---|
| 495 | // Emit remaining chunk
|
|---|
| 496 | if (chunkPos < chunk.length) {
|
|---|
| 497 | const chunkSlice = chunkPos === 0 ? chunk : chunk.slice(chunkPos);
|
|---|
| 498 | const line = generatedLine + generatedLineOffset;
|
|---|
| 499 | onChunk(
|
|---|
| 500 | chunkSlice,
|
|---|
| 501 | line,
|
|---|
| 502 | generatedColumn +
|
|---|
| 503 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
|---|
| 504 | sourceIndex,
|
|---|
| 505 | originalLine,
|
|---|
| 506 | originalColumn,
|
|---|
| 507 | nameIndex < 0 ? -1 : nameIndexMapping[nameIndex],
|
|---|
| 508 | );
|
|---|
| 509 | }
|
|---|
| 510 | pos = endPos;
|
|---|
| 511 | },
|
|---|
| 512 | (sourceIndex, source, sourceContent) => {
|
|---|
| 513 | while (sourceContents.length < sourceIndex) {
|
|---|
| 514 | sourceContents.push(undefined);
|
|---|
| 515 | }
|
|---|
| 516 | sourceContents[sourceIndex] = sourceContent;
|
|---|
| 517 | onSource(sourceIndex, source, sourceContent);
|
|---|
| 518 | },
|
|---|
| 519 | (nameIndex, name) => {
|
|---|
| 520 | let globalIndex = nameMapping.get(name);
|
|---|
| 521 | if (globalIndex === undefined) {
|
|---|
| 522 | globalIndex = nameMapping.size;
|
|---|
| 523 | nameMapping.set(name, globalIndex);
|
|---|
| 524 | onName(globalIndex, name);
|
|---|
| 525 | }
|
|---|
| 526 | nameIndexMapping[nameIndex] = globalIndex;
|
|---|
| 527 | },
|
|---|
| 528 | );
|
|---|
| 529 |
|
|---|
| 530 | // Handle remaining replacements
|
|---|
| 531 | let remainer = "";
|
|---|
| 532 | for (; i < replacements.length; i++) {
|
|---|
| 533 | remainer += replacements[i].content;
|
|---|
| 534 | }
|
|---|
| 535 |
|
|---|
| 536 | // Insert remaining replacements content splitted into chunks by lines
|
|---|
| 537 | let line = /** @type {number} */ (generatedLine) + generatedLineOffset;
|
|---|
| 538 | const matches = splitIntoLines(remainer);
|
|---|
| 539 | for (let m = 0; m < matches.length; m++) {
|
|---|
| 540 | const contentLine = matches[m];
|
|---|
| 541 | onChunk(
|
|---|
| 542 | contentLine,
|
|---|
| 543 | line,
|
|---|
| 544 | /** @type {number} */
|
|---|
| 545 | (generatedColumn) +
|
|---|
| 546 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
|---|
| 547 | -1,
|
|---|
| 548 | -1,
|
|---|
| 549 | -1,
|
|---|
| 550 | -1,
|
|---|
| 551 | );
|
|---|
| 552 |
|
|---|
| 553 | if (m === matches.length - 1 && !contentLine.endsWith("\n")) {
|
|---|
| 554 | if (generatedColumnOffsetLine === line) {
|
|---|
| 555 | generatedColumnOffset += contentLine.length;
|
|---|
| 556 | } else {
|
|---|
| 557 | generatedColumnOffset = contentLine.length;
|
|---|
| 558 | generatedColumnOffsetLine = line;
|
|---|
| 559 | }
|
|---|
| 560 | } else {
|
|---|
| 561 | generatedLineOffset++;
|
|---|
| 562 | line++;
|
|---|
| 563 | generatedColumnOffset = -(/** @type {number} */ (generatedColumn));
|
|---|
| 564 | generatedColumnOffsetLine = line;
|
|---|
| 565 | }
|
|---|
| 566 | }
|
|---|
| 567 |
|
|---|
| 568 | return {
|
|---|
| 569 | generatedLine: line,
|
|---|
| 570 | generatedColumn:
|
|---|
| 571 | /** @type {number} */
|
|---|
| 572 | (generatedColumn) +
|
|---|
| 573 | (line === generatedColumnOffsetLine ? generatedColumnOffset : 0),
|
|---|
| 574 | };
|
|---|
| 575 | }
|
|---|
| 576 |
|
|---|
| 577 | /**
|
|---|
| 578 | * @param {HashLike} hash hash
|
|---|
| 579 | * @returns {void}
|
|---|
| 580 | */
|
|---|
| 581 | updateHash(hash) {
|
|---|
| 582 | this._sortReplacements();
|
|---|
| 583 | hash.update("ReplaceSource");
|
|---|
| 584 | this._source.updateHash(hash);
|
|---|
| 585 | hash.update(this._name || "");
|
|---|
| 586 | // Feed each replacement as multiple updates instead of building one
|
|---|
| 587 | // combined template literal per replacement. The resulting digest is
|
|---|
| 588 | // identical (hash.update is additive over bytes), but we avoid
|
|---|
| 589 | // allocating a new string per replacement.
|
|---|
| 590 | for (const repl of this._replacements) {
|
|---|
| 591 | hash.update(`${repl.start}${repl.end}`);
|
|---|
| 592 | hash.update(repl.content);
|
|---|
| 593 | if (repl.name) hash.update(repl.name);
|
|---|
| 594 | }
|
|---|
| 595 | }
|
|---|
| 596 | }
|
|---|
| 597 |
|
|---|
| 598 | module.exports = ReplaceSource;
|
|---|
| 599 | module.exports.Replacement = Replacement;
|
|---|