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