tenebrous-dicebot/api/node_modules/webpack-sources/lib/ReplaceSource.js

407 lines
10 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const Source = require("./Source");
const { SourceNode } = require("source-map");
const { getSourceAndMap, getMap, getNode, getListMap } = require("./helpers");
class Replacement {
constructor(start, end, content, insertIndex, name) {
this.start = start;
this.end = end;
this.content = content;
this.insertIndex = insertIndex;
this.name = name;
}
}
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() {
const replacements = Array.from(this._replacements);
replacements.sort((a, b) => {
return a.insertIndex - b.insertIndex;
});
return 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, this._replacements.length, 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, this._replacements.length, name)
);
this._isSorted = false;
}
source() {
return this._replaceString(this._source.source());
}
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;
this._replacements.sort(function (a, b) {
const diff1 = b.end - a.end;
if (diff1 !== 0) return diff1;
const diff2 = b.start - a.start;
if (diff2 !== 0) return diff2;
return b.insertIndex - a.insertIndex;
});
this._isSorted = true;
}
_replaceString(str) {
if (typeof str !== "string")
throw new Error(
"str must be a string, but is a " + typeof str + ": " + str
);
this._sortReplacements();
const result = [str];
this._replacements.forEach(function (repl) {
const remSource = result.pop();
const splitted1 = this._splitString(remSource, Math.floor(repl.end + 1));
const splitted2 = this._splitString(splitted1[0], Math.floor(repl.start));
result.push(splitted1[1], repl.content, splitted2[0]);
}, this);
// write out result array in reverse order
let resultStr = "";
for (let i = result.length - 1; i >= 0; --i) {
resultStr += result[i];
}
return resultStr;
}
node(options) {
const node = getNode(this._source, options);
if (this._replacements.length === 0) {
return node;
}
this._sortReplacements();
const replace = new ReplacementEnumerator(this._replacements);
const output = [];
let position = 0;
const sources = Object.create(null);
const sourcesInLines = Object.create(null);
// We build a new list of SourceNodes in "output"
// from the original mapping data
const result = new SourceNode();
// We need to add source contents manually
// because "walk" will not handle it
node.walkSourceContents(function (sourceFile, sourceContent) {
result.setSourceContent(sourceFile, sourceContent);
sources["$" + sourceFile] = sourceContent;
});
const replaceInStringNode = this._replaceInStringNode.bind(
this,
output,
replace,
function getOriginalSource(mapping) {
const key = "$" + mapping.source;
let lines = sourcesInLines[key];
if (!lines) {
const source = sources[key];
if (!source) return null;
lines = source.split("\n").map(function (line) {
return line + "\n";
});
sourcesInLines[key] = lines;
}
// line is 1-based
if (mapping.line > lines.length) return null;
const line = lines[mapping.line - 1];
return line.substr(mapping.column);
}
);
node.walk(function (chunk, mapping) {
position = replaceInStringNode(chunk, position, mapping);
});
// If any replacements occur after the end of the original file, then we append them
// directly to the end of the output
const remaining = replace.footer();
if (remaining) {
output.push(remaining);
}
result.add(output);
return result;
}
listMap(options) {
let map = getListMap(this._source, options);
this._sortReplacements();
let currentIndex = 0;
const replacements = this._replacements;
let idxReplacement = replacements.length - 1;
let removeChars = 0;
map = map.mapGeneratedCode(function (str) {
const newCurrentIndex = currentIndex + str.length;
if (removeChars > str.length) {
removeChars -= str.length;
str = "";
} else {
if (removeChars > 0) {
str = str.substr(removeChars);
currentIndex += removeChars;
removeChars = 0;
}
let finalStr = "";
while (
idxReplacement >= 0 &&
replacements[idxReplacement].start < newCurrentIndex
) {
const repl = replacements[idxReplacement];
const start = Math.floor(repl.start);
const end = Math.floor(repl.end + 1);
const before = str.substr(0, Math.max(0, start - currentIndex));
if (end <= newCurrentIndex) {
const after = str.substr(Math.max(0, end - currentIndex));
finalStr += before + repl.content;
str = after;
currentIndex = Math.max(currentIndex, end);
} else {
finalStr += before + repl.content;
str = "";
removeChars = end - newCurrentIndex;
}
idxReplacement--;
}
str = finalStr + str;
}
currentIndex = newCurrentIndex;
return str;
});
let extraCode = "";
while (idxReplacement >= 0) {
extraCode += replacements[idxReplacement].content;
idxReplacement--;
}
if (extraCode) {
map.add(extraCode);
}
return map;
}
_splitString(str, position) {
return position <= 0
? ["", str]
: [str.substr(0, position), str.substr(position)];
}
_replaceInStringNode(
output,
replace,
getOriginalSource,
node,
position,
mapping
) {
let original = undefined;
do {
let splitPosition = replace.position - position;
// If multiple replaces occur in the same location then the splitPosition may be
// before the current position for the subsequent splits. Ensure it is >= 0
if (splitPosition < 0) {
splitPosition = 0;
}
if (splitPosition >= node.length || replace.done) {
if (replace.emit) {
const nodeEnd = new SourceNode(
mapping.line,
mapping.column,
mapping.source,
node,
mapping.name
);
output.push(nodeEnd);
}
return position + node.length;
}
const originalColumn = mapping.column;
// Try to figure out if generated code matches original code of this segement
// If this is the case we assume that it's allowed to move mapping.column
// Because getOriginalSource can be expensive we only do it when neccessary
let nodePart;
if (splitPosition > 0) {
nodePart = node.slice(0, splitPosition);
if (original === undefined) {
original = getOriginalSource(mapping);
}
if (
original &&
original.length >= splitPosition &&
original.startsWith(nodePart)
) {
mapping.column += splitPosition;
original = original.substr(splitPosition);
}
}
const emit = replace.next();
if (!emit) {
// Stop emitting when we have found the beginning of the string to replace.
// Emit the part of the string before splitPosition
if (splitPosition > 0) {
const nodeStart = new SourceNode(
mapping.line,
originalColumn,
mapping.source,
nodePart,
mapping.name
);
output.push(nodeStart);
}
// Emit the replacement value
if (replace.value) {
output.push(
new SourceNode(
mapping.line,
mapping.column,
mapping.source,
replace.value,
mapping.name || replace.name
)
);
}
}
// Recurse with remainder of the string as there may be multiple replaces within a single node
node = node.substr(splitPosition);
position += splitPosition;
// eslint-disable-next-line no-constant-condition
} while (true);
}
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}`);
hash.update(`${repl.end}`);
hash.update(`${repl.content}`);
hash.update(`${repl.insertIndex}`);
hash.update(`${repl.name}`);
}
}
}
class ReplacementEnumerator {
/**
* @param {Replacement[]} replacements list of replacements
*/
constructor(replacements) {
this.replacements = replacements || [];
this.index = this.replacements.length;
this.done = false;
this.emit = false;
// Set initial start position
this.next();
}
next() {
if (this.done) return true;
if (this.emit) {
// Start point found. stop emitting. set position to find end
const repl = this.replacements[this.index];
const end = Math.floor(repl.end + 1);
this.position = end;
this.value = repl.content;
this.name = repl.name;
} else {
// End point found. start emitting. set position to find next start
this.index--;
if (this.index < 0) {
this.done = true;
} else {
const nextRepl = this.replacements[this.index];
const start = Math.floor(nextRepl.start);
this.position = start;
}
}
if (this.position < 0) this.position = 0;
this.emit = !this.emit;
return this.emit;
}
footer() {
if (!this.done && !this.emit) this.next(); // If we finished _replaceInNode mid emit we advance to next entry
if (this.done) {
return [];
} else {
let resultStr = "";
for (let i = this.index; i >= 0; i--) {
const repl = this.replacements[i];
// this doesn't need to handle repl.name, because in SourceMaps generated code
// without pointer to original source can't have a name
resultStr += repl.content;
}
return resultStr;
}
}
}
module.exports = ReplaceSource;